Imported Upstream version 20.2.0 upstream upstream/20.2.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 06:59:25 +0000 (15:59 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 06:59:25 +0000 (15:59 +0900)
87 files changed:
.github/CODE_OF_CONDUCT.rst [new file with mode: 0644]
.github/CONTRIBUTING.rst [new file with mode: 0644]
.github/FUNDING.yml [new file with mode: 0644]
.github/PULL_REQUEST_TEMPLATE.md [new file with mode: 0644]
.github/SECURITY.yml [new file with mode: 0644]
.github/workflows/main.yml [new file with mode: 0644]
.pre-commit-config.yaml [new file with mode: 0644]
.readthedocs.yml [new file with mode: 0644]
AUTHORS.rst [new file with mode: 0644]
CHANGELOG.rst [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.rst [new file with mode: 0644]
changelog.d/towncrier_template.rst [new file with mode: 0644]
codecov.yml [new file with mode: 0644]
conftest.py [new file with mode: 0644]
docs/Makefile [new file with mode: 0644]
docs/_static/attrs_logo.png [new file with mode: 0644]
docs/_static/attrs_logo.svg [new file with mode: 0644]
docs/_static/attrs_logo_white.png [new file with mode: 0644]
docs/api.rst [new file with mode: 0644]
docs/backward-compatibility.rst [new file with mode: 0644]
docs/changelog.rst [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/contributing.rst [new file with mode: 0644]
docs/docutils.conf [new file with mode: 0644]
docs/examples.rst [new file with mode: 0644]
docs/extending.rst [new file with mode: 0644]
docs/glossary.rst [new file with mode: 0644]
docs/hashing.rst [new file with mode: 0644]
docs/how-does-it-work.rst [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/init.rst [new file with mode: 0644]
docs/license.rst [new file with mode: 0644]
docs/overview.rst [new file with mode: 0644]
docs/python-2.rst [new file with mode: 0644]
docs/types.rst [new file with mode: 0644]
docs/why.rst [new file with mode: 0644]
pyproject.toml [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
src/attr/__init__.py [new file with mode: 0644]
src/attr/__init__.pyi [new file with mode: 0644]
src/attr/_compat.py [new file with mode: 0644]
src/attr/_config.py [new file with mode: 0644]
src/attr/_funcs.py [new file with mode: 0644]
src/attr/_make.py [new file with mode: 0644]
src/attr/_next_gen.py [new file with mode: 0644]
src/attr/_version_info.py [new file with mode: 0644]
src/attr/_version_info.pyi [new file with mode: 0644]
src/attr/converters.py [new file with mode: 0644]
src/attr/converters.pyi [new file with mode: 0644]
src/attr/exceptions.py [new file with mode: 0644]
src/attr/exceptions.pyi [new file with mode: 0644]
src/attr/filters.py [new file with mode: 0644]
src/attr/filters.pyi [new file with mode: 0644]
src/attr/py.typed [new file with mode: 0644]
src/attr/setters.py [new file with mode: 0644]
src/attr/setters.pyi [new file with mode: 0644]
src/attr/validators.py [new file with mode: 0644]
src/attr/validators.pyi [new file with mode: 0644]
src/attrs.egg-info/PKG-INFO [new file with mode: 0644]
src/attrs.egg-info/SOURCES.txt [new file with mode: 0644]
src/attrs.egg-info/dependency_links.txt [new file with mode: 0644]
src/attrs.egg-info/not-zip-safe [new file with mode: 0644]
src/attrs.egg-info/requires.txt [new file with mode: 0644]
src/attrs.egg-info/top_level.txt [new file with mode: 0644]
tests/__init__.py [new file with mode: 0644]
tests/strategies.py [new file with mode: 0644]
tests/test_annotations.py [new file with mode: 0644]
tests/test_config.py [new file with mode: 0644]
tests/test_converters.py [new file with mode: 0644]
tests/test_dunders.py [new file with mode: 0644]
tests/test_filters.py [new file with mode: 0644]
tests/test_funcs.py [new file with mode: 0644]
tests/test_functional.py [new file with mode: 0644]
tests/test_init_subclass.py [new file with mode: 0644]
tests/test_make.py [new file with mode: 0644]
tests/test_next_gen.py [new file with mode: 0644]
tests/test_setattr.py [new file with mode: 0644]
tests/test_slots.py [new file with mode: 0644]
tests/test_validators.py [new file with mode: 0644]
tests/test_version_info.py [new file with mode: 0644]
tests/typing_example.py [new file with mode: 0644]
tests/utils.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]

diff --git a/.github/CODE_OF_CONDUCT.rst b/.github/CODE_OF_CONDUCT.rst
new file mode 100644 (file)
index 0000000..56e8914
--- /dev/null
@@ -0,0 +1,55 @@
+Contributor Covenant Code of Conduct
+====================================
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+Our Responsibilities
+--------------------
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+Scope
+-----
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+Representation of a project may be further defined and clarified by project maintainers.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hs@ox.cx.
+All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
+The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+Attribution
+-----------
+
+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>.
diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst
new file mode 100644 (file)
index 0000000..231e7a7
--- /dev/null
@@ -0,0 +1,250 @@
+How To Contribute
+=================
+
+First off, thank you for considering contributing to ``attrs``!
+It's people like *you* who make it such a great tool for everyone.
+
+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
+-------
+
+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 official tag is ``python-attrs`` and helping out in support frees us up to improve ``attrs`` instead!
+
+
+Workflow
+--------
+
+- No contribution is too small!
+  Please submit as many fixes for typos and grammar bloopers as you can!
+- Try to limit each pull request to *one* change only.
+- Since we squash on merge, it's up to you how you handle updates to the master branch.
+  Whether you prefer to rebase on master or merge master into your branch, do whatever is more comfortable for you.
+- *Always* add tests and docs for your code.
+  This is a hard rule; patches with missing tests or documentation can't be merged.
+- Make sure your changes pass our CI_.
+  You won't get any feedback until it's green unless you ask for it.
+- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done.
+- Don’t break `backward compatibility`_.
+
+
+Code
+----
+
+- Obey `PEP 8`_ and `PEP 257`_.
+  We use the ``"""``\ -on-separate-lines style for docstrings:
+
+  .. code-block:: python
+
+     def func(x):
+         """
+         Do something.
+
+         :param str x: A very important parameter.
+
+         :rtype: str
+         """
+- If you add or change public APIs, tag the docstring using ``..  versionadded:: 16.0.0 WHAT`` or ``..  versionchanged:: 16.2.0 WHAT``.
+- We use isort_ to sort our imports, and we follow the Black_ code style with a line length of 79 characters.
+  As long as you run our full tox suite before committing, or install our pre-commit_ hooks (ideally you'll do both -- see below "Local Development Environment"), you won't have to spend any time on formatting your code at all.
+  If you don't, CI will catch it for you -- but that seems like a waste of your time!
+
+
+Tests
+-----
+
+- Write your asserts as ``expected == actual`` to line them up nicely:
+
+  .. code-block:: python
+
+     x = f()
+
+     assert 42 == x.some_attribute
+     assert "foo" == x._a_private_attribute
+
+- To run the test suite, all you need is a recent tox_.
+  It will ensure the test suite runs with all dependencies against all Python versions just as it will in our CI.
+  If you lack some Python versions, you can can always limit the environments like ``tox -e py27,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 is found in ``tests/strategies.py``.
+- If you've changed or added public APIs, please update our type stubs (files ending in ``.pyi``).
+
+
+Documentation
+-------------
+
+- Use `semantic newlines`_ in reStructuredText_ files (files ending in ``.rst``):
+
+  .. code-block:: rst
+
+     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:
+
+  .. code-block:: rst
+
+     Last line of previous section.
+
+
+     Header of New Top Section
+     -------------------------
+
+     Header of New Section
+     ^^^^^^^^^^^^^^^^^^^^^
+
+     First line of new section.
+
+- 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!
+
+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``.
+
+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 #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 ``monospace font``.
+- Wrap arguments into asterisks like in docstrings: *these* or *attributes*.
+- 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 tense or constructions with "now".
+  For example:
+
+  + Added ``attr.validators.func()``.
+  + ``attr.func()`` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument.
+- If you want to reference multiple issues, copy the news fragment to another filename.
+  ``towncrier`` will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests.
+
+Example entries:
+
+  .. code-block:: rst
+
+     Added ``attr.validators.func()``.
+     The feature really *is* awesome.
+
+or:
+
+  .. code-block:: rst
+
+     ``attr.func()`` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument.
+     The bug really *was* nasty.
+
+----
+
+``tox -e changelog`` will render the current changelog to the terminal if you have any doubts.
+
+
+Local Development Environment
+-----------------------------
+
+You can (and should) run our test suite using tox_.
+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 already have a pet way, take some time to look at tools like `pew <https://github.com/berdario/pew>`_, `virtualfish <https://virtualfish.readthedocs.io/>`_, and `virtualenvwrapper <https://virtualenvwrapper.readthedocs.io/>`_.
+
+Next, get an up to date checkout of the ``attrs`` repository:
+
+.. code-block:: bash
+
+    $ git clone git@github.com:python-attrs/attrs.git
+
+or if you want to use git via ``https``:
+
+.. code-block:: bash
+
+    $ git clone https://github.com/python-attrs/attrs.git
+
+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 '.[dev]'
+
+At this point,
+
+.. code-block:: bash
+
+   $ python -m pytest
+
+should work and pass, as should:
+
+.. code-block:: bash
+
+   $ cd docs
+   $ make html
+
+The built documentation can then be found in ``docs/_build/html/``.
+
+To avoid committing code that violates our style guide, we strongly advise you to install pre-commit_ [#f1]_ hooks:
+
+.. code-block:: bash
+
+   $ pre-commit install
+
+You can also run them anytime (as our tox does) using:
+
+.. code-block:: bash
+
+   $ pre-commit run --all-files
+
+
+.. [#f1] pre-commit should have been installed into your virtualenv automatically when you ran ``pip install -e '.[dev]'`` above. If pre-commit is missing, it may be that you need to re-run ``pip install -e '.[dev]'``.
+
+
+Governance
+----------
+
+``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.**
+
+`Hynek Schlawack`_ acts reluctantly as the BDFL_ and has the final say over design decisions.
+
+
+****
+
+Please note that this project is released with a Contributor `Code of Conduct`_.
+By participating in this project you agree to abide by its terms.
+Please report any harm to `Hynek Schlawack`_ in any way you find appropriate.
+
+Thank you for considering contributing to ``attrs``!
+
+
+.. _`Hynek Schlawack`: https://hynek.me/about/
+.. _`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/.github/CODE_OF_CONDUCT.rst
+.. _changelog: https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst
+.. _`backward compatibility`: https://www.attrs.org/en/latest/backward-compatibility.html
+.. _tox: https://tox.readthedocs.io/
+.. _pyenv: https://github.com/pyenv/pyenv
+.. _reStructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
+.. _semantic newlines: https://rhodesmill.org/brandon/2012/one-sentence-per-line/
+.. _examples page: https://github.com/python-attrs/attrs/blob/master/docs/examples.rst
+.. _Hypothesis: https://hypothesis.readthedocs.io/
+.. _CI: https://github.com/python-attrs/attrs/actions?query=workflow%3ACI
+.. _`team of volunteers`: https://github.com/python-attrs
+.. _BDFL: https://en.wikipedia.org/wiki/Benevolent_dictator_for_life
+.. _towncrier: https://pypi.org/project/towncrier
+.. _black: https://github.com/psf/black
+.. _pre-commit: https://pre-commit.com/
+.. _isort: https://github.com/timothycrosley/isort
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644 (file)
index 0000000..ef4f212
--- /dev/null
@@ -0,0 +1,5 @@
+---
+
+github: hynek
+ko_fi: the_hynek
+tidelift: "pypi/attrs"
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644 (file)
index 0000000..349590e
--- /dev/null
@@ -0,0 +1,18 @@
+# Pull Request Check List
+
+This is just a friendly reminder about the most common mistakes.  Please make sure that you tick all boxes.  But please read our [contribution guide](https://www.attrs.org/en/latest/contributing.html) at least once, it will save you unnecessary review cycles!
+
+If an item doesn't apply to your pull request, **check it anyway** to make it apparent that there's nothing left to do.  If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing.
+
+- [ ] Added **tests** for changed code.
+- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/master/tests/strategies.py).
+- [ ] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``).
+    - [ ] ...and used in the stub test file `tests/typing_example.py`.
+- [ ] Updated **documentation** for changed code.
+    - [ ] New functions/classes have to be added to `docs/api.rst` by hand.
+    - [ ] Changes to the signature of `@attr.s()` have to be added by hand too.
+    - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).  Find the appropriate next version in our [``__init__.py``](https://github.com/python-attrs/attrs/blob/master/src/attr/__init__.py) file.
+- [ ] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
+- [ ] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/master/changelog.d).
+
+If you have *any* questions to *any* of the points above, just **submit and ask**!  This checklist is here to *help* you, not to deter you from contributing!
diff --git a/.github/SECURITY.yml b/.github/SECURITY.yml
new file mode 100644 (file)
index 0000000..5e565ec
--- /dev/null
@@ -0,0 +1,2 @@
+To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644 (file)
index 0000000..0722007
--- /dev/null
@@ -0,0 +1,92 @@
+---
+name: CI
+
+on:
+  push:
+    branches: ["master"]
+  pull_request:
+    branches: ["master"]
+  workflow_dispatch:
+
+jobs:
+  tests:
+    name: "Python ${{ matrix.python-version }}"
+    runs-on: "ubuntu-latest"
+    env:
+      USING_COVERAGE: "2.7,3.7,3.8"
+
+    strategy:
+      matrix:
+        python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9.0-alpha - 3.9.0", "pypy2", "pypy3"]
+
+    steps:
+      - uses: "actions/checkout@v2"
+      - uses: "actions/setup-python@v2"
+        with:
+          python-version: "${{ matrix.python-version }}"
+      - name: "Install dependencies"
+        run: |
+          set -xe
+          python -VV
+          python -m site
+          python -m pip install --upgrade pip setuptools wheel
+          python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions
+
+      - name: "Run tox targets for ${{ matrix.python-version }}"
+        run: "python -m tox"
+
+      # We always use a modern Python version for combining coverage to prevent
+      # parsing errors in older versions for modern code.
+      - uses: "actions/setup-python@v2"
+        with:
+          python-version: "3.8"
+
+      - name: "Combine coverage"
+        run: |
+          set -xe
+          python -m pip install coverage[toml]
+          python -m coverage combine
+          python -m coverage xml
+        if: "contains(env.USING_COVERAGE, matrix.python-version)"
+      - name: "Upload coverage to Codecov"
+        if: "contains(env.USING_COVERAGE, matrix.python-version)"
+        uses: "codecov/codecov-action@v1"
+        with:
+          fail_ci_if_error: true
+
+  package:
+    name: "Build & verify package"
+    runs-on: "ubuntu-latest"
+
+    steps:
+      - uses: "actions/checkout@v2"
+      - uses: "actions/setup-python@v1"
+        with:
+          python-version: "3.8"
+
+      - name: "Install pep517 and twine"
+        run: "python -m pip install pep517 twine"
+      - name: "Build package"
+        run: "python -m pep517.build --source --binary ."
+      - name: "List result"
+        run: "ls -l dist"
+      - name: "Check long_description"
+        run: "python -m twine check dist/*"
+
+  install-dev:
+    strategy:
+      matrix:
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+
+    name: "Verify dev env"
+    runs-on: "${{ matrix.os }}"
+
+    steps:
+      - uses: "actions/checkout@v2"
+      - uses: "actions/setup-python@v1"
+        with:
+          python-version: "3.8"
+      - name: "Install in dev mode"
+        run: "python -m pip install -e .[dev]"
+      - name: "Import package"
+        run: "python -c 'import attr; print(attr.__version__)'"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644 (file)
index 0000000..5134c18
--- /dev/null
@@ -0,0 +1,42 @@
+---
+repos:
+  - repo: https://github.com/psf/black
+    rev: 20.8b1
+    hooks:
+      - id: black
+        language_version: python3.8
+        # override until resolved: https://github.com/ambv/black/issues/402
+        files: \.pyi?$
+        types: []
+
+  - repo: https://github.com/asottile/seed-isort-config
+    rev: v2.2.0
+    hooks:
+      - id: seed-isort-config
+
+  - repo: https://github.com/pre-commit/mirrors-isort
+    rev: v5.4.2
+    hooks:
+      - id: isort
+        additional_dependencies: [toml]
+
+  - repo: https://gitlab.com/pycqa/flake8
+    rev: 3.8.3
+    hooks:
+      - id: flake8
+        language_version: python3.8
+
+  - repo: https://github.com/econchick/interrogate
+    rev: 1.3.0
+    hooks:
+      - id: interrogate
+        args: [tests]
+        language_version: python3.8
+
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v3.2.0
+    hooks:
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+      - id: debug-statements
+      - id: check-toml
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644 (file)
index 0000000..511ae16
--- /dev/null
@@ -0,0 +1,11 @@
+---
+version: 2
+python:
+  # Keep version in sync with tox.ini (docs and gh-actions).
+  version: 3.7
+
+  install:
+    - method: pip
+      path: .
+      extra_requirements:
+        - docs
diff --git a/AUTHORS.rst b/AUTHORS.rst
new file mode 100644 (file)
index 0000000..f14ef6c
--- /dev/null
@@ -0,0 +1,11 @@
+Credits
+=======
+
+``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+
+The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+
+A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+
+It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644 (file)
index 0000000..7c3fcd1
--- /dev/null
@@ -0,0 +1,793 @@
+Changelog
+=========
+
+Versions follow `CalVer <https://calver.org>`_ with a strict backwards compatibility policy.
+The third digit is only for regressions.
+
+.. towncrier release notes start
+
+20.2.0 (2020-09-05)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**.
+
+  This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged.
+
+  If you wish to use them together with mypy, you can simply drop `this plugin <https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py>`_ into your project.
+
+  Feel free to provide feedback to them in the linked issue #668.
+
+  We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled.
+  `#668 <https://github.com/python-attrs/attrs/issues/668>`_
+
+
+Changes
+^^^^^^^
+
+- ``attr.define()`` et al now correct detect ``__eq__`` and ``__ne__``.
+  `#671 <https://github.com/python-attrs/attrs/issues/671>`_
+- ``attr.define()`` et al's hybrid behavior now also works correctly when arguments are passed.
+  `#675 <https://github.com/python-attrs/attrs/issues/675>`_
+- It's possible to define custom ``__setattr__`` methods on slotted classes again.
+  `#681 <https://github.com/python-attrs/attrs/issues/681>`_
+- In 20.1.0 we introduced the ``inherited`` attribute on the ``attr.Attribute`` class to differentiate attributes that have been inherited and those that have been defined directly on the class.
+
+  It has shown to be problematic to involve that attribute when comparing instances of ``attr.Attribute`` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class.
+
+  Therefore the ``inherited`` attribute will now be ignored when hashing and comparing instances of ``attr.Attribute``.
+  `#684 <https://github.com/python-attrs/attrs/issues/684>`_
+- ``zope.interface`` is now a "soft dependency" when running the test suite; if ``zope.interface`` is not installed when running the test suite, the interface-related tests will be automatically skipped.
+  `#685 <https://github.com/python-attrs/attrs/issues/685>`_
+- The ergonomics of creating frozen classes using ``@define(frozen=True)`` and sub-classing frozen classes has been improved:
+  you don't have to set ``on_setattr=None`` anymore.
+  `#687 <https://github.com/python-attrs/attrs/issues/687>`_
+
+
+----
+
+
+20.1.0 (2020-08-20)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Python 3.4 is not supported anymore.
+  It has been unsupported by the Python core team for a while now, its PyPI downloads are negligible, and our CI provider removed it as a supported option.
+
+  It's very unlikely that ``attrs`` will break under 3.4 anytime soon, which is why we do *not* block its installation on Python 3.4.
+  But we don't test it anymore and will block it once someone reports breakage.
+  `#608 <https://github.com/python-attrs/attrs/issues/608>`_
+
+
+Deprecations
+^^^^^^^^^^^^
+
+- Less of a deprecation and more of a heads up: the next release of ``attrs`` will introduce an ``attrs`` namespace.
+  That means that you'll finally be able to run ``import attrs`` with new functions that aren't cute abbreviations and that will carry better defaults.
+
+  This should not break any of your code, because project-local packages have priority before installed ones.
+  If this is a problem for you for some reason, please report it to our bug tracker and we'll figure something out.
+
+  The old ``attr`` namespace isn't going anywhere and its defaults are not changing â€“ this is a purely additive measure.
+  Please check out the linked issue for more details.
+
+  These new APIs have been added *provisionally* as part of #666 so you can try them out today and provide feedback.
+  Learn more in the `API docs <https://www.attrs.org/en/stable/api.html#provisional-apis>`_.
+  `#408 <https://github.com/python-attrs/attrs/issues/408>`_
+
+
+Changes
+^^^^^^^
+
+- Added ``attr.resolve_types()``.
+  It ensures that all forward-references and types in string form are resolved into concrete types.
+
+  You need this only if you need concrete types at runtime.
+  That means that if you only use types for static type checking, you do **not** need this function.
+  `#288 <https://github.com/python-attrs/attrs/issues/288>`_,
+  `#302 <https://github.com/python-attrs/attrs/issues/302>`_
+- Added ``@attr.s(collect_by_mro=False)`` argument that if set to ``True`` fixes the collection of attributes from base classes.
+
+  It's only necessary for certain cases of multiple-inheritance but is kept off for now for backward-compatibility reasons.
+  It will be turned on by default in the future.
+
+  As a side-effect, ``attr.Attribute`` now *always* has an ``inherited`` attribute indicating whether an attribute on a class was directly defined or inherited.
+  `#428 <https://github.com/python-attrs/attrs/issues/428>`_,
+  `#635 <https://github.com/python-attrs/attrs/issues/635>`_
+- On Python 3, all generated methods now have a docstring explaining that they have been created by ``attrs``.
+  `#506 <https://github.com/python-attrs/attrs/issues/506>`_
+- It is now possible to prevent ``attrs`` from auto-generating the ``__setstate__`` and ``__getstate__`` methods that are required for pickling of slotted classes.
+
+  Either pass ``@attr.s(getstate_setstate=False)`` or pass ``@attr.s(auto_detect=True)`` and implement them yourself:
+  if ``attrs`` finds either of the two methods directly on the decorated class, it assumes implicitly ``getstate_setstate=False`` (and implements neither).
+
+  This option works with dict classes but should never be necessary.
+  `#512 <https://github.com/python-attrs/attrs/issues/512>`_,
+  `#513 <https://github.com/python-attrs/attrs/issues/513>`_,
+  `#642 <https://github.com/python-attrs/attrs/issues/642>`_
+- Fixed a ``ValueError: Cell is empty`` bug that could happen in some rare edge cases.
+  `#590 <https://github.com/python-attrs/attrs/issues/590>`_
+- ``attrs`` can now automatically detect your own implementations and infer ``init=False``, ``repr=False``, ``eq=False``, ``order=False``, and ``hash=False`` if you set ``@attr.s(auto_detect=True)``.
+  ``attrs`` will ignore inherited methods.
+  If the argument implies more than one method (e.g. ``eq=True`` creates both ``__eq__`` and ``__ne__``), it's enough for *one* of them to exist and ``attrs`` will create *neither*.
+
+  This feature requires Python 3.
+  `#607 <https://github.com/python-attrs/attrs/issues/607>`_
+- Added ``attr.converters.pipe()``.
+  The feature allows combining multiple conversion callbacks into one by piping the value through all of them, and retuning the last result.
+
+  As part of this feature, we had to relax the type information for converter callables.
+  `#618 <https://github.com/python-attrs/attrs/issues/618>`_
+- Fixed serialization behavior of non-slots classes with ``cache_hash=True``.
+  The hash cache will be cleared on operations which make "deep copies" of instances of classes with hash caching,
+  though the cache will not be cleared with shallow copies like those made by ``copy.copy()``.
+
+  Previously, ``copy.deepcopy()`` or serialization and deserialization with ``pickle`` would result in an un-initialized object.
+
+  This change also allows the creation of ``cache_hash=True`` classes with a custom ``__setstate__``,
+  which was previously forbidden (`#494 <https://github.com/python-attrs/attrs/issues/494>`_).
+  `#620 <https://github.com/python-attrs/attrs/issues/620>`_
+- It is now possible to specify hooks that are called whenever an attribute is set **after** a class has been instantiated.
+
+  You can pass ``on_setattr`` both to ``@attr.s()`` to set the default for all attributes on a class, and to ``@attr.ib()`` to overwrite it for individual attributes.
+
+  ``attrs`` also comes with a new module ``attr.setters`` that brings helpers that run validators, converters, or allow to freeze a subset of attributes.
+  `#645 <https://github.com/python-attrs/attrs/issues/645>`_,
+  `#660 <https://github.com/python-attrs/attrs/issues/660>`_
+- **Provisional** APIs called ``attr.define()``, ``attr.mutable()``, and ``attr.frozen()`` have been added.
+
+  They are only available on Python 3.6 and later, and call ``attr.s()`` with different default values.
+
+  If nothing comes up, they will become the official way for creating classes in 20.2.0 (see above).
+
+  **Please note** that it may take some time until mypy â€“ and other tools that have dedicated support for ``attrs`` â€“ recognize these new APIs.
+  Please **do not** open issues on our bug tracker, there is nothing we can do about it.
+  `#666 <https://github.com/python-attrs/attrs/issues/666>`_
+- We have also provisionally added ``attr.field()`` that supplants ``attr.ib()``.
+  It also requires at least Python 3.6 and is keyword-only.
+  Other than that, it only dropped a few arguments, but changed no defaults.
+
+  As with ``attr.s()``: ``attr.ib()`` is not going anywhere.
+  `#669 <https://github.com/python-attrs/attrs/issues/669>`_
+
+
+----
+
+
+19.3.0 (2019-10-15)
+-------------------
+
+Changes
+^^^^^^^
+
+- Fixed ``auto_attribs`` usage when default values cannot be compared directly with ``==``, such as ``numpy`` arrays.
+  `#585 <https://github.com/python-attrs/attrs/issues/585>`_
+
+
+----
+
+
+19.2.0 (2019-10-01)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Removed deprecated ``Attribute`` attribute ``convert`` per scheduled removal on 2019/1.
+  This planned deprecation is tracked in issue `#307 <https://github.com/python-attrs/attrs/issues/307>`_.
+  `#504 <https://github.com/python-attrs/attrs/issues/504>`_
+- ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` do not consider subclasses comparable anymore.
+
+  This has been deprecated since 18.2.0 and was raising a ``DeprecationWarning`` for over a year.
+  `#570 <https://github.com/python-attrs/attrs/issues/570>`_
+
+
+Deprecations
+^^^^^^^^^^^^
+
+- The ``cmp`` argument to ``attr.s()`` and ``attr.ib()`` is now deprecated.
+
+  Please use ``eq`` to add equality methods (``__eq__`` and ``__ne__``) and ``order`` to add ordering methods (``__lt__``, ``__le__``, ``__gt__``, and ``__ge__``) instead â€“ just like with `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_.
+
+  Both are effectively ``True`` by default but it's enough to set ``eq=False`` to disable both at once.
+  Passing ``eq=False, order=True`` explicitly will raise a ``ValueError`` though.
+
+  Since this is arguably a deeper backward-compatibility break, it will have an extended deprecation period until 2021-06-01.
+  After that day, the ``cmp`` argument will be removed.
+
+  ``attr.Attribute`` also isn't orderable anymore.
+  `#574 <https://github.com/python-attrs/attrs/issues/574>`_
+
+
+Changes
+^^^^^^^
+
+- Updated ``attr.validators.__all__`` to include new validators added in `#425`_.
+  `#517 <https://github.com/python-attrs/attrs/issues/517>`_
+- Slotted classes now use a pure Python mechanism to rewrite the ``__class__`` cell when rebuilding the class, so ``super()`` works even on environments where ``ctypes`` is not installed.
+  `#522 <https://github.com/python-attrs/attrs/issues/522>`_
+- When collecting attributes using ``@attr.s(auto_attribs=True)``, attributes with a default of ``None`` are now deleted too.
+  `#523 <https://github.com/python-attrs/attrs/issues/523>`_,
+  `#556 <https://github.com/python-attrs/attrs/issues/556>`_
+- Fixed ``attr.validators.deep_iterable()`` and ``attr.validators.deep_mapping()`` type stubs.
+  `#533 <https://github.com/python-attrs/attrs/issues/533>`_
+- ``attr.validators.is_callable()`` validator now raises an exception ``attr.exceptions.NotCallableError``, a subclass of ``TypeError``, informing the received value.
+  `#536 <https://github.com/python-attrs/attrs/issues/536>`_
+- ``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would.
+  `#543 <https://github.com/python-attrs/attrs/issues/543>`_,
+  `#563 <https://github.com/python-attrs/attrs/issues/563>`_
+- Added ``attr.validators.matches_re()`` that checks string attributes whether they match a regular expression.
+  `#552 <https://github.com/python-attrs/attrs/issues/552>`_
+- Keyword-only attributes (``kw_only=True``) and attributes that are excluded from the ``attrs``'s ``__init__`` (``init=False``) now can appear before mandatory attributes.
+  `#559 <https://github.com/python-attrs/attrs/issues/559>`_
+- The fake filename for generated methods is now more stable.
+  It won't change when you restart the process.
+  `#560 <https://github.com/python-attrs/attrs/issues/560>`_
+- The value passed to ``@attr.ib(repr=…)`` can now be either a boolean (as before) or a callable.
+  That callable must return a string and is then used for formatting the attribute by the generated ``__repr__()`` method.
+  `#568 <https://github.com/python-attrs/attrs/issues/568>`_
+- Added ``attr.__version_info__`` that can be used to reliably check the version of ``attrs`` and write forward- and backward-compatible code.
+  Please check out the `section on deprecated APIs <http://www.attrs.org/en/stable/api.html#deprecated-apis>`_ on how to use it.
+  `#580 <https://github.com/python-attrs/attrs/issues/580>`_
+
+ .. _`#425`: https://github.com/python-attrs/attrs/issues/425
+
+
+----
+
+
+19.1.0 (2019-03-03)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed a bug where deserialized objects with ``cache_hash=True`` could have incorrect hash code values.
+  This change breaks classes with ``cache_hash=True`` when a custom ``__setstate__`` is present.
+  An exception will be thrown when applying the ``attrs`` annotation to such a class.
+  This limitation is tracked in issue `#494 <https://github.com/python-attrs/attrs/issues/494>`_.
+  `#482 <https://github.com/python-attrs/attrs/issues/482>`_
+
+
+Changes
+^^^^^^^
+
+- Add ``is_callable``, ``deep_iterable``, and ``deep_mapping`` validators.
+
+  * ``is_callable``: validates that a value is callable
+  * ``deep_iterable``: Allows recursion down into an iterable,
+    applying another validator to every member in the iterable
+    as well as applying an optional validator to the iterable itself.
+  * ``deep_mapping``: Allows recursion down into the items in a mapping object,
+    applying a key validator and a value validator to the key and value in every item.
+    Also applies an optional validator to the mapping object itself.
+
+  You can find them in the ``attr.validators`` package.
+  `#425`_
+- Fixed stub files to prevent errors raised by mypy's ``disallow_any_generics = True`` option.
+  `#443 <https://github.com/python-attrs/attrs/issues/443>`_
+- Attributes with ``init=False`` now can follow after ``kw_only=True`` attributes.
+  `#450 <https://github.com/python-attrs/attrs/issues/450>`_
+- ``attrs`` now has first class support for defining exception classes.
+
+  If you define a class using ``@attr.s(auto_exc=True)`` and subclass an exception, the class will behave like a well-behaved exception class including an appropriate ``__str__`` method, and all attributes additionally available in an ``args`` attribute.
+  `#500 <https://github.com/python-attrs/attrs/issues/500>`_
+- Clarified documentation for hashing to warn that hashable objects should be deeply immutable (in their usage, even if this is not enforced).
+  `#503 <https://github.com/python-attrs/attrs/issues/503>`_
+
+
+----
+
+
+18.2.0 (2018-09-01)
+-------------------
+
+Deprecations
+^^^^^^^^^^^^
+
+- Comparing subclasses using ``<``, ``>``, ``<=``, and ``>=`` is now deprecated.
+  The docs always claimed that instances are only compared if the types are identical, so this is a first step to conform to the docs.
+
+  Equality operators (``==`` and ``!=``) were always strict in this regard.
+  `#394 <https://github.com/python-attrs/attrs/issues/394>`_
+
+
+Changes
+^^^^^^^
+
+- ``attrs`` now ships its own `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ type hints.
+  Together with `mypy <http://mypy-lang.org>`_'s ``attrs`` plugin, you've got all you need for writing statically typed code in both Python 2 and 3!
+
+  At that occasion, we've also added `narrative docs <https://www.attrs.org/en/stable/types.html>`_ about type annotations in ``attrs``.
+  `#238 <https://github.com/python-attrs/attrs/issues/238>`_
+- Added *kw_only* arguments to ``attr.ib`` and ``attr.s``, and a corresponding *kw_only* attribute to ``attr.Attribute``.
+  This change makes it possible to have a generated ``__init__`` with keyword-only arguments on Python 3, relaxing the required ordering of default and non-default valued attributes.
+  `#281 <https://github.com/python-attrs/attrs/issues/281>`_,
+  `#411 <https://github.com/python-attrs/attrs/issues/411>`_
+- The test suite now runs with ``hypothesis.HealthCheck.too_slow`` disabled to prevent CI breakage on slower computers.
+  `#364 <https://github.com/python-attrs/attrs/issues/364>`_,
+  `#396 <https://github.com/python-attrs/attrs/issues/396>`_
+- ``attr.validators.in_()`` now raises a ``ValueError`` with a useful message even if the options are a string and the value is not a string.
+  `#383 <https://github.com/python-attrs/attrs/issues/383>`_
+- ``attr.asdict()`` now properly handles deeply nested lists and dictionaries.
+  `#395 <https://github.com/python-attrs/attrs/issues/395>`_
+- Added ``attr.converters.default_if_none()`` that allows to replace ``None`` values in attributes.
+  For example ``attr.ib(converter=default_if_none(""))`` replaces ``None`` by empty strings.
+  `#400 <https://github.com/python-attrs/attrs/issues/400>`_,
+  `#414 <https://github.com/python-attrs/attrs/issues/414>`_
+- Fixed a reference leak where the original class would remain live after being replaced when ``slots=True`` is set.
+  `#407 <https://github.com/python-attrs/attrs/issues/407>`_
+- Slotted classes can now be made weakly referenceable by passing ``@attr.s(weakref_slot=True)``.
+  `#420 <https://github.com/python-attrs/attrs/issues/420>`_
+- Added *cache_hash* option to ``@attr.s`` which causes the hash code to be computed once and stored on the object.
+  `#426 <https://github.com/python-attrs/attrs/issues/426>`_
+- Attributes can be named ``property`` and ``itemgetter`` now.
+  `#430 <https://github.com/python-attrs/attrs/issues/430>`_
+- It is now possible to override a base class' class variable using only class annotations.
+  `#431 <https://github.com/python-attrs/attrs/issues/431>`_
+
+
+----
+
+
+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 no longer attempt to remove attributes with the same name from the class body.
+
+  `#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 <https://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
+
+  So far, we've added chapters on `initialization <https://www.attrs.org/en/stable/init.html>`_ and `hashing <https://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.
+  Because of 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 ``attrs``-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)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Attributes are no longer defined on the class body.
+
+  This means that if you define a class ``C`` with an attribute ``x``, the class will *not* have an attribute ``x`` for introspection.
+  Instead of ``C.x``, use ``attr.fields(C).x`` or look at ``C.__attrs_attrs__``.
+  The old behavior has been deprecated since version 16.1.
+  (`#253 <https://github.com/python-attrs/attrs/issues/253>`_)
+
+
+Changes
+^^^^^^^
+
+- ``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``.
+
+  This change paves the way for automatic type checking and serialization (though as of this release ``attrs`` does not make use of it).
+  In Python 3.6 or higher, the value of ``attr.Attribute.type`` can alternately be set using variable type annotations
+  (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_).
+  (`#151 <https://github.com/python-attrs/attrs/issues/151>`_, `#214 <https://github.com/python-attrs/attrs/issues/214>`_, `#215 <https://github.com/python-attrs/attrs/issues/215>`_, `#239 <https://github.com/python-attrs/attrs/issues/239>`_)
+- The combination of ``str=True`` and ``slots=True`` now works on Python 2.
+  (`#198 <https://github.com/python-attrs/attrs/issues/198>`_)
+- ``attr.Factory`` is hashable again.
+  (`#204 <https://github.com/python-attrs/attrs/issues/204>`_)
+- Subclasses now can overwrite attribute definitions of their base classes.
+
+  That means that you can -- for example -- change the default value for an attribute by redefining it.
+  (`#221 <https://github.com/python-attrs/attrs/issues/221>`_, `#229 <https://github.com/python-attrs/attrs/issues/229>`_)
+- Added new option *auto_attribs* to ``@attr.s`` that allows to collect annotated fields without setting them to ``attr.ib()``.
+
+  Setting a field to an ``attr.ib()`` is still possible to supply options like validators.
+  Setting it to any other value is treated like it was passed as ``attr.ib(default=value)`` -- passing an instance of ``attr.Factory`` also works as expected.
+  (`#262 <https://github.com/python-attrs/attrs/issues/262>`_, `#277 <https://github.com/python-attrs/attrs/issues/277>`_)
+- Instances of classes created using ``attr.make_class()`` can now be pickled.
+  (`#282 <https://github.com/python-attrs/attrs/issues/282>`_)
+
+
+----
+
+
+17.2.0 (2017-05-24)
+-------------------
+
+
+Changes:
+^^^^^^^^
+
+- Validators are hashable again.
+  Note that validators may become frozen in the future, pending availability of no-overhead frozen classes.
+  `#192 <https://github.com/python-attrs/attrs/issues/192>`_
+
+
+----
+
+
+17.1.0 (2017-05-16)
+-------------------
+
+To encourage more participation, the project has also been moved into a `dedicated GitHub organization <https://github.com/python-attrs/>`_ and everyone is most welcome to join!
+
+``attrs`` also has a logo now!
+
+.. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs logo
+
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``attrs`` will set the ``__hash__()`` method to ``None`` by default now.
+  The way hashes were handled before was in conflict with `Python's specification <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_.
+  This *may* break some software although this breakage is most likely just surfacing of latent bugs.
+  You can always make ``attrs`` create the ``__hash__()`` method using ``@attr.s(hash=True)``.
+  See `#136`_ for the rationale of this change.
+
+  .. warning::
+
+    Please *do not* upgrade blindly and *do* test your software!
+    *Especially* if you use instances as dict keys or put them into sets!
+
+- Correspondingly, ``attr.ib``'s *hash* argument is ``None`` by default too and mirrors the *cmp* argument as it should.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- ``attr.assoc()`` is now deprecated in favor of ``attr.evolve()`` and will stop working in 2018.
+
+
+Changes:
+^^^^^^^^
+
+- Fix default hashing behavior.
+  Now *hash* mirrors the value of *cmp* and classes are unhashable by default.
+  `#136`_
+  `#142 <https://github.com/python-attrs/attrs/issues/142>`_
+- Added ``attr.evolve()`` that, given an instance of an ``attrs`` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied.
+  ``evolve()`` replaces ``assoc()``, which is now deprecated.
+  ``evolve()`` is significantly faster than ``assoc()``, and requires the class have an initializer that can take the field values as keyword arguments (like ``attrs`` itself can generate).
+  `#116 <https://github.com/python-attrs/attrs/issues/116>`_
+  `#124 <https://github.com/python-attrs/attrs/pull/124>`_
+  `#135 <https://github.com/python-attrs/attrs/pull/135>`_
+- ``FrozenInstanceError`` is now raised when trying to delete an attribute from a frozen class.
+  `#118 <https://github.com/python-attrs/attrs/pull/118>`_
+- Frozen-ness of classes is now inherited.
+  `#128 <https://github.com/python-attrs/attrs/pull/128>`_
+- ``__attrs_post_init__()`` is now run if validation is disabled.
+  `#130 <https://github.com/python-attrs/attrs/pull/130>`_
+- Added ``attr.validators.in_(options)`` that, given the allowed ``options``, checks whether the attribute value is in it.
+  This can be used to check constants, enums, mappings, etc.
+  `#181 <https://github.com/python-attrs/attrs/pull/181>`_
+- Added ``attr.validators.and_()`` that composes multiple validators into one.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- For convenience, the *validator* argument of ``@attr.s`` now can take a list of validators that are wrapped using ``and_()``.
+  `#138 <https://github.com/python-attrs/attrs/issues/138>`_
+- Accordingly, ``attr.validators.optional()`` now can take a list of validators too.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- Validators can now be defined conveniently inline by using the attribute as a decorator.
+  Check out the `validator examples <http://www.attrs.org/en/stable/init.html#decorator>`_ to see it in action!
+  `#143 <https://github.com/python-attrs/attrs/issues/143>`_
+- ``attr.Factory()`` now has a *takes_self* argument that makes the initializer to pass the partially initialized instance into the factory.
+  In other words you can define attribute defaults based on other attributes.
+  `#165`_
+  `#189 <https://github.com/python-attrs/attrs/issues/189>`_
+- Default factories can now also be defined inline using decorators.
+  They are *always* passed the partially initialized instance.
+  `#165`_
+- Conversion can now be made optional using ``attr.converters.optional()``.
+  `#105 <https://github.com/python-attrs/attrs/issues/105>`_
+  `#173 <https://github.com/python-attrs/attrs/pull/173>`_
+- ``attr.make_class()`` now accepts the keyword argument ``bases`` which allows for subclassing.
+  `#152 <https://github.com/python-attrs/attrs/pull/152>`_
+- Metaclasses are now preserved with ``slots=True``.
+  `#155 <https://github.com/python-attrs/attrs/pull/155>`_
+
+.. _`#136`: https://github.com/python-attrs/attrs/issues/136
+.. _`#165`: https://github.com/python-attrs/attrs/issues/165
+
+
+----
+
+
+16.3.0 (2016-11-24)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Attributes now can have user-defined metadata which greatly improves ``attrs``'s extensibility.
+  `#96 <https://github.com/python-attrs/attrs/pull/96>`_
+- Allow for a ``__attrs_post_init__()`` method that -- if defined -- will get called at the end of the ``attrs``-generated ``__init__()`` method.
+  `#111 <https://github.com/python-attrs/attrs/pull/111>`_
+- Added ``@attr.s(str=True)`` that will optionally create a ``__str__()`` method that is identical to ``__repr__()``.
+  This is mainly useful with ``Exception``\ s and other classes that rely on a useful ``__str__()`` implementation but overwrite the default one through a poor own one.
+  Default Python class behavior is to use ``__repr__()`` as ``__str__()`` anyways.
+
+  If you tried using ``attrs`` with ``Exception``\ s and were puzzled by the tracebacks: this option is for you.
+- ``__name__`` is no longer overwritten with ``__qualname__`` for ``attr.s(slots=True)`` classes.
+  `#99 <https://github.com/python-attrs/attrs/issues/99>`_
+
+
+----
+
+
+16.2.0 (2016-09-17)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.astuple()`` that -- similarly to ``attr.asdict()`` -- returns the instance as a tuple.
+  `#77 <https://github.com/python-attrs/attrs/issues/77>`_
+- Converters now work with frozen classes.
+  `#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 slotted classes.
+  `#81 <https://github.com/python-attrs/attrs/issues/81>`_
+- ``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 slotted classes.
+  `#88 <https://github.com/python-attrs/attrs/issues/88>`_
+
+
+----
+
+
+16.1.0 (2016-08-30)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- All instances where function arguments were called ``cl`` have been changed to the more Pythonic ``cls``.
+  Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form.
+  If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- Accessing ``Attribute`` instances on class objects is now deprecated and will stop working in 2017.
+  If you need introspection please use the ``__attrs_attrs__`` attribute or the ``attr.fields()`` function that carry them too.
+  In the future, the attributes that are defined on the class body and are usually overwritten in your ``__init__`` method are simply removed after ``@attr.s`` has been applied.
+
+  This will remove the confusing error message if you write your own ``__init__`` and forget to initialize some attribute.
+  Instead you will get a straightforward ``AttributeError``.
+  In other words: decorated classes will work more like plain Python classes which was always ``attrs``'s goal.
+- The serious business aliases ``attr.attributes`` and ``attr.attr`` have been deprecated in favor of ``attr.attrs`` and ``attr.attrib`` which are much more consistent and frankly obvious in hindsight.
+  They will be purged from documentation immediately but there are no plans to actually remove them.
+
+
+Changes:
+^^^^^^^^
+
+- ``attr.asdict()``\ 's ``dict_factory`` arguments is now propagated on recursion.
+  `#45 <https://github.com/python-attrs/attrs/issues/45>`_
+- ``attr.asdict()``, ``attr.has()`` and ``attr.fields()`` are significantly faster.
+  `#48 <https://github.com/python-attrs/attrs/issues/48>`_
+  `#51 <https://github.com/python-attrs/attrs/issues/51>`_
+- Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``.
+- Add *frozen* option to ``attr.s`` that will make instances best-effort immutable.
+  `#60 <https://github.com/python-attrs/attrs/issues/60>`_
+- ``attr.asdict()`` now takes ``retain_collection_types`` as an argument.
+  If ``True``, it does not convert attributes of type ``tuple`` or ``set`` to ``list``.
+  `#69 <https://github.com/python-attrs/attrs/issues/69>`_
+
+
+----
+
+
+16.0.0 (2016-05-23)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Python 3.3 and 2.6 are no longer supported.
+  They may work by chance but any effort to keep them working has ceased.
+
+  The last Python 2.6 release was on October 29, 2013 and is no longer supported by the CPython core team.
+  Major Python packages like Django and Twisted dropped Python 2.6 a while ago already.
+
+  Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release.
+
+Changes:
+^^^^^^^^
+
+- ``__slots__`` have arrived!
+  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.
+  `#32 <https://github.com/python-attrs/attrs/issues/32>`_
+- ``attr.asdict()`` can now produce arbitrary mappings instead of Python ``dict``\ s when provided with a ``dict_factory`` argument.
+  `#40 <https://github.com/python-attrs/attrs/issues/40>`_
+- Multiple performance improvements.
+
+
+----
+
+
+15.2.0 (2015-12-08)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added a ``convert`` argument to ``attr.ib``, which allows specifying a function to run on arguments.
+  This allows for simple type conversions, e.g. with ``attr.ib(convert=int)``.
+  `#26 <https://github.com/python-attrs/attrs/issues/26>`_
+- Speed up object creation when attribute validators are used.
+  `#28 <https://github.com/python-attrs/attrs/issues/28>`_
+
+
+----
+
+
+15.1.0 (2015-08-20)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.validators.optional()`` that wraps other validators allowing attributes to be ``None``.
+  `#16 <https://github.com/python-attrs/attrs/issues/16>`_
+- Multi-level inheritance now works.
+  `#24 <https://github.com/python-attrs/attrs/issues/24>`_
+- ``__repr__()`` now works with non-redecorated subclasses.
+  `#20 <https://github.com/python-attrs/attrs/issues/20>`_
+
+
+----
+
+
+15.0.0 (2015-04-15)
+-------------------
+
+Changes:
+^^^^^^^^
+
+Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..7ae3df9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..46c9dbb
--- /dev/null
@@ -0,0 +1,23 @@
+include LICENSE *.rst *.toml *.yml *.yaml *.ini
+graft .github
+
+# Stubs
+include src/attr/py.typed
+recursive-include src *.pyi
+
+# Tests
+include tox.ini conftest.py
+recursive-include tests *.py
+
+# Documentation
+include docs/Makefile docs/docutils.conf
+recursive-include docs *.png
+recursive-include docs *.svg
+recursive-include docs *.py
+recursive-include docs *.rst
+prune docs/_build
+
+# 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
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..04ad542
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,218 @@
+Metadata-Version: 2.1
+Name: attrs
+Version: 20.2.0
+Summary: Classes Without Boilerplate
+Home-page: https://www.attrs.org/
+Author: Hynek Schlawack
+Author-email: hs@ox.cx
+Maintainer: Hynek Schlawack
+Maintainer-email: hs@ox.cx
+License: MIT
+Project-URL: Documentation, https://www.attrs.org/
+Project-URL: Bug Tracker, https://github.com/python-attrs/attrs/issues
+Project-URL: Source Code, https://github.com/python-attrs/attrs
+Description: .. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png
+           :alt: attrs Logo
+        
+        ======================================
+        ``attrs``: Classes Without Boilerplate
+        ======================================
+        
+        .. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+           :target: https://www.attrs.org/en/stable/?badge=stable
+           :alt: Documentation Status
+        
+        .. image:: https://github.com/python-attrs/attrs/workflows/CI/badge.svg?branch=master
+           :target: https://github.com/python-attrs/attrs/actions?workflow=CI
+           :alt: CI Status
+        
+        .. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+           :target: https://codecov.io/github/python-attrs/attrs
+           :alt: Test Coverage
+        
+        .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+           :target: https://github.com/psf/black
+           :alt: Code style: black
+        
+        .. teaser-begin
+        
+        ``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+        
+        Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+        
+        .. teaser-end
+        
+        For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+        
+        .. -code-begin-
+        
+        .. code-block:: pycon
+        
+           >>> import attr
+        
+           >>> @attr.s
+           ... class SomeClass(object):
+           ...     a_number = attr.ib(default=42)
+           ...     list_of_numbers = attr.ib(factory=list)
+           ...
+           ...     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')
+        
+        
+        After *declaring* your attributes ``attrs`` gives you:
+        
+        - a concise and explicit overview of the class's attributes,
+        - a nice human-readable ``__repr__``,
+        - a complete set of comparison methods (equality and ordering),
+        - an initializer,
+        - and much more,
+        
+        *without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+        
+        On Python 3.6 and later, you can often even drop the calls to ``attr.ib()`` by using `type annotations <https://www.attrs.org/en/latest/types.html>`_.
+        
+        This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <https://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+        Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+        Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+        
+        
+        .. -getting-help-
+        
+        Getting Help
+        ============
+        
+        Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+        
+        Answering questions of your fellow developers is also great way to help the project!
+        
+        
+        .. -project-information-
+        
+        Project Information
+        ===================
+        
+        ``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+        its documentation lives at `Read the Docs <https://www.attrs.org/>`_,
+        the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+        and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+        It’s rigorously tested on Python 2.7, 3.5+, and PyPy.
+        
+        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 <https://www.attrs.org/en/latest/contributing.html>`_ to get you started!
+        
+        
+        ``attrs`` for Enterprise
+        ------------------------
+        
+        Available as part of the Tidelift Subscription.
+        
+        The maintainers of ``attrs`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
+        Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
+        `Learn more. <https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_
+        
+        
+        Release Information
+        ===================
+        
+        20.2.0 (2020-09-05)
+        -------------------
+        
+        Backward-incompatible Changes
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        - ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**.
+        
+          This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged.
+        
+          If you wish to use them together with mypy, you can simply drop `this plugin <https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py>`_ into your project.
+        
+          Feel free to provide feedback to them in the linked issue #668.
+        
+          We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled.
+          `#668 <https://github.com/python-attrs/attrs/issues/668>`_
+        
+        
+        Changes
+        ^^^^^^^
+        
+        - ``attr.define()`` et al now correct detect ``__eq__`` and ``__ne__``.
+          `#671 <https://github.com/python-attrs/attrs/issues/671>`_
+        - ``attr.define()`` et al's hybrid behavior now also works correctly when arguments are passed.
+          `#675 <https://github.com/python-attrs/attrs/issues/675>`_
+        - It's possible to define custom ``__setattr__`` methods on slotted classes again.
+          `#681 <https://github.com/python-attrs/attrs/issues/681>`_
+        - In 20.1.0 we introduced the ``inherited`` attribute on the ``attr.Attribute`` class to differentiate attributes that have been inherited and those that have been defined directly on the class.
+        
+          It has shown to be problematic to involve that attribute when comparing instances of ``attr.Attribute`` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class.
+        
+          Therefore the ``inherited`` attribute will now be ignored when hashing and comparing instances of ``attr.Attribute``.
+          `#684 <https://github.com/python-attrs/attrs/issues/684>`_
+        - ``zope.interface`` is now a "soft dependency" when running the test suite; if ``zope.interface`` is not installed when running the test suite, the interface-related tests will be automatically skipped.
+          `#685 <https://github.com/python-attrs/attrs/issues/685>`_
+        - The ergonomics of creating frozen classes using ``@define(frozen=True)`` and sub-classing frozen classes has been improved:
+          you don't have to set ``on_setattr=None`` anymore.
+          `#687 <https://github.com/python-attrs/attrs/issues/687>`_
+        
+        `Full changelog <https://www.attrs.org/en/stable/changelog.html>`_.
+        
+        Credits
+        =======
+        
+        ``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+        
+        The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+        
+        A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+        
+        It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+        Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
+        
+Keywords: class,attribute,boilerplate
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Description-Content-Type: text/x-rst
+Provides-Extra: docs
+Provides-Extra: tests_no_zope
+Provides-Extra: tests
+Provides-Extra: dev
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..496b7ed
--- /dev/null
@@ -0,0 +1,122 @@
+.. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs Logo
+
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+.. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+   :target: https://www.attrs.org/en/stable/?badge=stable
+   :alt: Documentation Status
+
+.. image:: https://github.com/python-attrs/attrs/workflows/CI/badge.svg?branch=master
+   :target: https://github.com/python-attrs/attrs/actions?workflow=CI
+   :alt: CI Status
+
+.. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+   :target: https://codecov.io/github/python-attrs/attrs
+   :alt: Test Coverage
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+   :alt: Code style: black
+
+.. teaser-begin
+
+``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+
+Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+
+.. teaser-end
+
+For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. -code-begin-
+
+.. code-block:: pycon
+
+   >>> import attr
+
+   >>> @attr.s
+   ... class SomeClass(object):
+   ...     a_number = attr.ib(default=42)
+   ...     list_of_numbers = attr.ib(factory=list)
+   ...
+   ...     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')
+
+
+After *declaring* your attributes ``attrs`` gives you:
+
+- a concise and explicit overview of the class's attributes,
+- a nice human-readable ``__repr__``,
+- a complete set of comparison methods (equality and ordering),
+- an initializer,
+- and much more,
+
+*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+
+On Python 3.6 and later, you can often even drop the calls to ``attr.ib()`` by using `type annotations <https://www.attrs.org/en/latest/types.html>`_.
+
+This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <https://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+
+
+.. -getting-help-
+
+Getting Help
+============
+
+Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+
+Answering questions of your fellow developers is also great way to help the project!
+
+
+.. -project-information-
+
+Project Information
+===================
+
+``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+its documentation lives at `Read the Docs <https://www.attrs.org/>`_,
+the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+It’s rigorously tested on Python 2.7, 3.5+, and PyPy.
+
+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 <https://www.attrs.org/en/latest/contributing.html>`_ to get you started!
+
+
+``attrs`` for Enterprise
+------------------------
+
+Available as part of the Tidelift Subscription.
+
+The maintainers of ``attrs`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
+Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
+`Learn more. <https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_
diff --git a/changelog.d/towncrier_template.rst b/changelog.d/towncrier_template.rst
new file mode 100644 (file)
index 0000000..29ca74c
--- /dev/null
@@ -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 %}
+----
diff --git a/codecov.yml b/codecov.yml
new file mode 100644 (file)
index 0000000..60a1e5c
--- /dev/null
@@ -0,0 +1,10 @@
+---
+comment: false
+coverage:
+    status:
+        patch:
+            default:
+                target: "100"
+        project:
+            default:
+                target: "100"
diff --git a/conftest.py b/conftest.py
new file mode 100644 (file)
index 0000000..7d3d1f8
--- /dev/null
@@ -0,0 +1,24 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+
+from hypothesis import HealthCheck, settings
+
+
+def pytest_configure(config):
+    # HealthCheck.too_slow causes more trouble than good -- especially in CIs.
+    settings.register_profile(
+        "patience", settings(suppress_health_check=[HealthCheck.too_slow])
+    )
+    settings.load_profile("patience")
+
+
+collect_ignore = []
+if sys.version_info[:2] < (3, 6):
+    collect_ignore.extend(
+        [
+            "tests/test_annotations.py",
+            "tests/test_init_subclass.py",
+            "tests/test_next_gen.py",
+        ]
+    )
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..3143891
--- /dev/null
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html       to make standalone HTML files"
+       @echo "  dirhtml    to make HTML files named index.html in directories"
+       @echo "  singlehtml to make a single large HTML file"
+       @echo "  pickle     to make pickle files"
+       @echo "  json       to make JSON files"
+       @echo "  htmlhelp   to make HTML files and a HTML help project"
+       @echo "  qthelp     to make HTML files and a qthelp project"
+       @echo "  devhelp    to make HTML files and a Devhelp project"
+       @echo "  epub       to make an epub"
+       @echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+       @echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+       @echo "  text       to make text files"
+       @echo "  man        to make manual pages"
+       @echo "  texinfo    to make Texinfo files"
+       @echo "  info       to make Texinfo files and run them through makeinfo"
+       @echo "  gettext    to make PO message catalogs"
+       @echo "  changes    to make an overview of all changed/added/deprecated items"
+       @echo "  xml        to make Docutils-native XML files"
+       @echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+       @echo "  linkcheck  to check all external links for integrity"
+       @echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       rm -rf $(BUILDDIR)/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+       $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+       @echo
+       @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/attrs.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc"
+
+devhelp:
+       $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+       @echo
+       @echo "Build finished."
+       @echo "To view the help file:"
+       @echo "# mkdir -p $$HOME/.local/share/devhelp/attrs"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs"
+       @echo "# devhelp"
+
+epub:
+       $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+       @echo
+       @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make' in that directory to run these through (pdf)latex" \
+             "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through pdflatex..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through platex and dvipdfmx..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+       $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+       @echo
+       @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+       @echo
+       @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo
+       @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+       @echo "Run \`make' in that directory to run these through makeinfo" \
+             "(use \`make info' here to do that automatically)."
+
+info:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo "Running Texinfo files through makeinfo..."
+       make -C $(BUILDDIR)/texinfo info
+       @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+       $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+       @echo
+       @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+       $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+       @echo
+       @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+       $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+       @echo
+       @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/_static/attrs_logo.png b/docs/_static/attrs_logo.png
new file mode 100644 (file)
index 0000000..11b6e6f
Binary files /dev/null and b/docs/_static/attrs_logo.png differ
diff --git a/docs/_static/attrs_logo.svg b/docs/_static/attrs_logo.svg
new file mode 100644 (file)
index 0000000..4326ae1
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 142 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><rect id="ArtBoard1" x="0" y="0" width="141.578" height="117.638" style="fill:none;"/><path d="M32.55,82.516c-0.191,4.027 -0.832,8.503 -1.793,13.238c-1.023,4.793 -2.301,9.269 -3.836,13.234c-3.324,1.215 -7.035,1.922 -10.808,1.922c-3.77,0 -7.161,-0.707 -9.973,-1.922c0,0 -0.387,-3.133 0.637,-7.863c1.023,-4.797 2.687,-7.93 2.687,-7.93c3.324,-1.215 7.098,-1.918 10.871,-1.918c1.856,0 3.645,0.192 5.309,0.512c0.445,-2.688 0.832,-5.309 0.832,-5.309c0,0 -2.496,-0.703 -4.988,-0.703c-4.864,0 -9.465,1.086 -11.45,1.664c0.579,-1.726 1.09,-3.328 1.727,-4.925c3.328,-1.153 7.035,-1.856 10.809,-1.856c3.773,0 7.16,0.703 9.976,1.856Zm-15.348,23.277c2.75,0 5.309,-0.703 5.309,-0.703c0,0 1.34,-4.668 2.109,-7.996c-1.152,-0.317 -3.132,-0.704 -5.371,-0.704c-2.496,0 -5.437,0.704 -5.437,0.704c0,0 -0.703,1.664 -1.215,4.031c-0.512,2.301 -0.445,3.965 -0.445,3.965c0,0 2.554,0.703 5.05,0.703Zm32.676,0c2.813,0 5.629,-0.512 7.867,-1.024c-0.769,1.981 -1.664,3.774 -2.621,5.5c-2.047,0.383 -4.16,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.914,-1.922c0.129,-3.965 0.77,-8.441 1.793,-13.234l1.918,-9.082l-6.461,0c0.707,-1.789 1.473,-3.453 2.496,-5.051l5.051,0l1.344,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.723,0l-1.918,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm27.496,0c2.817,0 5.629,-0.512 7.867,-1.024c-0.765,1.981 -1.664,3.774 -2.621,5.5c-2.046,0.383 -4.156,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.91,-1.922c0.125,-3.965 0.766,-8.441 1.789,-13.234l1.918,-9.082l-6.457,0c0.703,-1.789 1.469,-3.453 2.492,-5.051l5.055,0l1.34,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.719,0l-1.922,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm28.137,-25.133c1.984,0 3.84,0.191 5.629,0.578c-0.703,1.727 -1.469,3.324 -2.367,4.86c-1.406,-0.192 -2.813,-0.321 -4.348,-0.321c-2.492,0 -5.242,0.703 -5.242,0.703c0,0 -1.856,6.075 -2.496,9.274l-3.067,14.515l-5.82,0l3.07,-14.515c0.957,-4.735 2.301,-9.211 3.836,-13.238c3.325,-1.153 7.035,-1.856 10.805,-1.856Zm28.715,13.621c0,0 0.445,2.621 -0.574,7.356c-1.024,4.796 -2.559,7.351 -2.559,7.351c-3.328,1.215 -7.035,1.922 -10.809,1.922c-3.773,0 -7.16,-0.707 -9.976,-1.922c0,0 -0.383,-1.726 0.129,-4.988c1.406,0.387 6.457,1.793 10.933,1.793c2.497,0 5.375,-0.703 5.375,-0.703c0,0 0.704,-1.153 1.149,-3.453c0.512,-2.305 0.32,-3.454 0.32,-3.454c0,0 -2.558,-0.703 -5.051,-0.703c-3.902,0 -7.226,-0.64 -10.039,-1.855c0,0 -0.386,-2.879 0.383,-6.524c0.766,-3.707 2.43,-6.585 2.43,-6.585c3.324,-1.153 7.035,-1.856 10.808,-1.856c3.77,0 7.161,0.703 9.973,1.856c0,0.128 0.387,2.046 -0.062,5.179c-1.536,-0.449 -7.165,-1.918 -11,-1.918c-2.493,0 -5.372,0.703 -5.372,0.703c0,0 -0.64,1.024 -0.957,2.621c-0.32,1.598 -0.195,2.622 -0.195,2.622c0,0 2.496,0.64 5.117,0.703c3.774,0 7.164,0.64 9.977,1.855Z" style="fill:#222;fill-rule:nonzero;"/><path d="M79.12,44.539c0,-0.914 -0.066,-1.805 -0.195,-2.68l2.832,-1.687l-0.058,-0.266c-0.434,-2.004 -1.145,-3.902 -2.086,-5.66l-0.137,-0.254l-3.156,0.723c-0.766,-1.235 -1.676,-2.375 -2.703,-3.395l1.285,-3.012l-0.2,-0.175c-1.5,-1.293 -3.168,-2.391 -4.976,-3.246l-0.254,-0.118l-2.137,2.442c-1.386,-0.551 -2.855,-0.934 -4.386,-1.137l-0.727,-3.176l-0.277,-0.015c-0.477,-0.035 -0.965,-0.059 -1.457,-0.059c-1.52,0 -3,0.16 -4.43,0.457l-0.277,0.051l-0.293,3.25c-1.465,0.41 -2.864,0.996 -4.164,1.73l-2.438,-2.132l-0.238,0.156c-1.672,1.09 -3.188,2.402 -4.496,3.894l-0.18,0.207l1.684,2.821c-0.86,1.125 -1.602,2.363 -2.188,3.675l-3.219,-0.289l-0.093,0.266c-0.707,1.863 -1.157,3.852 -1.313,5.918l-0.023,0.266l3.035,1.296c-0.004,0.047 -0.004,0.098 -0.004,0.149c0,1.289 0.137,2.555 0.387,3.766l-2.782,1.656l0.071,0.273c0.55,2.028 1.379,3.926 2.453,5.672l0.14,0.227l3.223,-0.731c0.703,0.973 1.496,1.883 2.371,2.703l-1.277,2.993l0.211,0.179c1.609,1.328 3.41,2.434 5.359,3.258l0.242,0.105l2.176,-2.48c1.059,0.367 2.176,0.641 3.313,0.809l0.726,3.187l0.281,0.02c0.563,0.047 1.149,0.078 1.743,0.078c1.554,0 3.07,-0.168 4.535,-0.481l0.273,-0.058l0.293,-3.254c1.113,-0.317 2.18,-0.738 3.203,-1.242l2.477,2.164l0.234,-0.137c1.813,-1.07 3.449,-2.391 4.875,-3.918l0.196,-0.203l-1.665,-2.789c0.774,-0.938 1.45,-1.965 2.028,-3.047l3.293,0.297l0.105,-0.25c0.821,-1.84 1.391,-3.836 1.672,-5.922l0.035,-0.285l-2.976,-1.27c0.031,-0.429 0.054,-0.871 0.054,-1.32Zm-12.921,0c0,3.156 -2.555,5.711 -5.711,5.715c-3.161,-0.004 -5.711,-2.559 -5.715,-5.715c0.004,-3.152 2.554,-5.703 5.715,-5.715c3.156,0.012 5.711,2.563 5.711,5.715Z" style="fill:#222;fill-rule:nonzero;"/><path d="M103.519,15.5l-0.067,-0.242l-2.253,-0.012c-0.317,-0.934 -0.747,-1.832 -1.258,-2.664l1.351,-1.844l-0.136,-0.191c-0.809,-1.121 -1.778,-2.133 -2.868,-3.004l-0.195,-0.156l-1.84,1.316c-0.832,-0.578 -1.75,-1.059 -2.722,-1.437l0.011,-2.266l-0.23,-0.074c-0.328,-0.098 -0.649,-0.192 -0.992,-0.274c-1.028,-0.242 -2.063,-0.367 -3.086,-0.402l-0.25,-0.004l-0.715,2.16c-1.028,0.047 -2.047,0.215 -3.024,0.496l-1.308,-1.84l-0.238,0.09c-1.317,0.469 -2.559,1.121 -3.692,1.934l-0.195,0.14l0.695,2.18c-0.742,0.617 -1.422,1.313 -2.016,2.09l-2.14,-0.711l-0.137,0.211c-0.773,1.156 -1.402,2.434 -1.844,3.816l-0.066,0.223l1.852,1.363c-0.012,0.02 -0.016,0.043 -0.02,0.059c-0.207,0.867 -0.312,1.726 -0.34,2.578l-2.156,0.684l0.012,0.246c0.054,1.468 0.308,2.902 0.761,4.261l0.075,0.227l2.308,0.012c0.313,0.75 0.692,1.465 1.137,2.148l-1.348,1.828l0.149,0.196c0.886,1.16 1.929,2.207 3.129,3.074l0.191,0.14l1.867,-1.335c0.641,0.402 1.328,0.75 2.047,1.039l-0.016,2.281l0.231,0.074c0.387,0.121 0.777,0.234 1.18,0.332c1.062,0.246 2.121,0.379 3.168,0.402l0.242,0.004l0.718,-2.164c0.782,-0.035 1.547,-0.144 2.297,-0.316l1.336,1.863l0.231,-0.074c1.398,-0.43 2.73,-1.074 3.945,-1.887l0.203,-0.137l-0.679,-2.16c0.648,-0.504 1.257,-1.066 1.804,-1.687l2.184,0.726l0.144,-0.191c0.86,-1.117 1.567,-2.387 2.09,-3.762l0.09,-0.238l-1.816,-1.336c0.086,-0.277 0.16,-0.559 0.23,-0.84c0.141,-0.613 0.238,-1.219 0.293,-1.82l2.191,-0.695l0,-0.239c0,-0.086 0.008,-0.172 0.008,-0.261c-0.004,-1.34 -0.183,-2.661 -0.523,-3.93Zm-10.766,3.941c0,0.301 -0.031,0.602 -0.101,0.903c-0.43,1.8 -2.032,3.015 -3.805,3.015c-0.301,0 -0.598,-0.035 -0.898,-0.109c-1.805,-0.414 -3.012,-2.024 -3.012,-3.797c0,-0.297 0.031,-0.594 0.097,-0.898c0.422,-1.801 2.032,-3.016 3.809,-3.016c0.289,0 0.594,0.031 0.898,0.105c1.793,0.418 3.012,2.032 3.012,3.797Z" style="fill:#222;fill-rule:nonzero;"/></svg>
diff --git a/docs/_static/attrs_logo_white.png b/docs/_static/attrs_logo_white.png
new file mode 100644 (file)
index 0000000..5b4de7c
Binary files /dev/null and b/docs/_static/attrs_logo_white.png differ
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644 (file)
index 0000000..63640dc
--- /dev/null
@@ -0,0 +1,666 @@
+API Reference
+=============
+
+.. currentmodule:: attr
+
+``attrs`` works by decorating a class using `attr.s` and then optionally defining attributes on the class using `attr.ib`.
+
+.. note::
+
+   When this documentation speaks about "``attrs`` attributes" it means those attributes that are defined using `attr.ib` in the class body.
+
+What follows is the API explanation, if you'd like a more hands-on introduction, have a look at `examples`.
+
+
+
+Core
+----
+
+
+.. warning::
+   As of ``attrs`` 20.1.0, it also ships with a bunch of provisional APIs that are intended to become the main way of defining classes in the future.
+
+   Please have a look at :ref:`prov`.
+
+.. autodata:: attr.NOTHING
+
+.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None)
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrs``.
+
+   For example:
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     _private = attr.ib()
+      >>> C(private=42)
+      C(_private=42)
+      >>> class D(object):
+      ...     def __init__(self, x):
+      ...         self.x = x
+      >>> D(1)
+      <D object at ...>
+      >>> D = attr.s(these={"x": attr.ib()}, init=False)(D)
+      >>> D(1)
+      D(x=1)
+      >>> @attr.s(auto_exc=True)
+      ... class Error(Exception):
+      ...     x = attr.ib()
+      ...     y = attr.ib(default=42, init=False)
+      >>> Error("foo")
+      Error(x='foo', y=42)
+      >>> raise Error("foo")
+      Traceback (most recent call last):
+         ...
+      Error: ('foo', 42)
+      >>> raise ValueError("foo", 42)   # for comparison
+      Traceback (most recent call last):
+         ...
+      ValueError: ('foo', 42)
+
+
+.. autofunction:: attr.ib
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrib``.
+
+   The object returned by `attr.ib` also allows for setting the default and the validator using decorators:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      ...     @x.validator
+      ...     def _any_name_except_a_name_of_an_attribute(self, attribute, value):
+      ...         if value < 0:
+      ...             raise ValueError("x must be positive")
+      ...     @y.default
+      ...     def _any_name_except_a_name_of_an_attribute(self):
+      ...         return self.x + 1
+      >>> C(1)
+      C(x=1, y=2)
+      >>> C(-1)
+      Traceback (most recent call last):
+          ...
+      ValueError: x must be positive
+
+.. autoclass:: attr.Attribute
+
+   Instances of this class are frequently used for introspection purposes like:
+
+   - `fields` returns a tuple of them.
+   - Validators get them passed as the first argument.
+
+   .. warning::
+
+       You should never instantiate this class yourself!
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      >>> attr.fields(C).x
+      Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)
+
+
+.. autofunction:: attr.make_class
+
+   This is handy if you want to programmatically create classes.
+
+   For example:
+
+   .. doctest::
+
+      >>> C1 = attr.make_class("C1", ["x", "y"])
+      >>> C1(1, 2)
+      C1(x=1, y=2)
+      >>> C2 = attr.make_class("C2", {"x": attr.ib(default=42),
+      ...                             "y": attr.ib(default=attr.Factory(list))})
+      >>> C2()
+      C2(x=42, y=[])
+
+
+.. autoclass:: attr.Factory
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(default=attr.Factory(list))
+      ...     y = attr.ib(default=attr.Factory(
+      ...         lambda self: set(self.x),
+      ...         takes_self=True)
+      ...     )
+      >>> C()
+      C(x=[], y=set())
+      >>> C([1, 2, 3])
+      C(x=[1, 2, 3], y={1, 2, 3})
+
+
+Exceptions
+----------
+
+.. autoexception:: attr.exceptions.PythonTooOldError
+.. autoexception:: attr.exceptions.FrozenError
+.. autoexception:: attr.exceptions.FrozenInstanceError
+.. autoexception:: attr.exceptions.FrozenAttributeError
+.. autoexception:: attr.exceptions.AttrsAttributeNotFoundError
+.. autoexception:: attr.exceptions.NotAnAttrsClassError
+.. autoexception:: attr.exceptions.DefaultAlreadySetError
+.. autoexception:: attr.exceptions.UnannotatedAttributeError
+.. autoexception:: attr.exceptions.NotCallableError
+
+   For example::
+
+       @attr.s(auto_attribs=True)
+       class C:
+           x: int
+           y = attr.ib()  # <- ERROR!
+
+
+.. _helpers:
+
+Helpers
+-------
+
+``attrs`` comes with a bunch of helper methods that make working with it easier:
+
+.. autofunction:: attr.fields
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.fields(C)
+      (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None))
+      >>> attr.fields(C)[1]
+      Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=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, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)}
+      >>> attr.fields_dict(C)['y']
+      Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)
+      >>> attr.fields_dict(C)['y'] is attr.fields(C).y
+      True
+
+
+.. autofunction:: attr.has
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     pass
+      >>> attr.has(C)
+      True
+      >>> attr.has(object)
+      False
+
+
+.. autofunction:: attr.resolve_types
+
+    For example:
+
+    .. doctest::
+
+        >>> import typing
+        >>> @attr.s(auto_attribs=True)
+        ... class A:
+        ...     a: typing.List['A']
+        ...     b: 'B'
+        ...
+        >>> @attr.s(auto_attribs=True)
+        ... class B:
+        ...     a: A
+        ...
+        >>> attr.fields(A).a.type
+        typing.List[ForwardRef('A')]
+        >>> attr.fields(A).b.type
+        'B'
+        >>> attr.resolve_types(A, globals(), locals())
+        <class 'A'>
+        >>> attr.fields(A).a.type
+        typing.List[A]
+        >>> attr.fields(A).b.type
+        <class 'B'>
+
+.. autofunction:: attr.asdict
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.asdict(C(1, C(2, 3)))
+      {'x': 1, 'y': {'x': 2, 'y': 3}}
+
+
+.. autofunction:: attr.astuple
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.astuple(C(1,2))
+      (1, 2)
+
+``attrs`` includes some handy helpers for filtering the attributes in `attr.asdict` and `attr.astuple`:
+
+.. autofunction:: attr.filters.include
+
+.. autofunction:: attr.filters.exclude
+
+See :func:`asdict` for examples.
+
+.. autofunction:: attr.evolve
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> i1 = C(1, 2)
+      >>> i1
+      C(x=1, y=2)
+      >>> i2 = attr.evolve(i1, y=3)
+      >>> i2
+      C(x=1, y=3)
+      >>> i1 == i2
+      False
+
+   ``evolve`` creates a new instance using ``__init__``.
+   This fact has several implications:
+
+   * private attributes should be specified without the leading underscore, just like in ``__init__``.
+   * attributes with ``init=False`` can't be set with ``evolve``.
+   * the usual ``__init__`` validators will validate the new values.
+
+.. autofunction:: validate
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.instance_of(int))
+      >>> i = C(1)
+      >>> i.x = "1"
+      >>> attr.validate(i)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <class 'int'> (got '1' that is a <class 'str'>).", ...)
+
+
+Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact:
+
+.. autofunction:: set_run_validators
+
+.. autofunction:: get_run_validators
+
+
+.. _api_validators:
+
+Validators
+----------
+
+``attrs`` comes with some common validators in the ``attrs.validators`` module:
+
+
+.. autofunction:: attr.validators.instance_of
+
+
+   For example:
+
+   .. 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, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42')
+      >>> C(None)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got None that is a <type 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None, kw_only=False), <type 'int'>, None)
+
+.. autofunction:: attr.validators.in_
+
+   For example:
+
+   .. doctest::
+
+       >>> import enum
+       >>> class State(enum.Enum):
+       ...     ON = "on"
+       ...     OFF = "off"
+       >>> @attr.s
+       ... class C(object):
+       ...     state = attr.ib(validator=attr.validators.in_(State))
+       ...     val = attr.ib(validator=attr.validators.in_([1, 2, 3]))
+       >>> C(State.ON, 1)
+       C(state=<State.ON: 'on'>, val=1)
+       >>> C("on", 1)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'state' must be in <enum 'State'> (got 'on')
+       >>> C(State.ON, 4)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'val' must be in [1, 2, 3] (got 4)
+
+.. autofunction:: attr.validators.provides
+
+.. autofunction:: attr.validators.and_
+
+   For convenience, it's also possible to pass a list to `attr.ib`'s validator argument.
+
+   Thus the following two statements are equivalent::
+
+      x = attr.ib(validator=attr.validators.and_(v1, v2, v3))
+      x = attr.ib(validator=[v1, v2, v3])
+
+.. autofunction:: attr.validators.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.optional(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, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42')
+      >>> C(None)
+      C(x=None)
+
+
+.. autofunction:: attr.validators.is_callable
+
+    For example:
+
+    .. doctest::
+
+        >>> @attr.s
+        ... class C(object):
+        ...     x = attr.ib(validator=attr.validators.is_callable())
+        >>> C(isinstance)
+        C(x=<built-in function isinstance>)
+        >>> C("not a callable")
+        Traceback (most recent call last):
+            ...
+        attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a <class 'str'>).
+
+
+.. autofunction:: attr.validators.matches_re
+
+    For example:
+
+    .. doctest::
+
+        >>> @attr.s
+        ... class User(object):
+        ...     email = attr.ib(validator=attr.validators.matches_re(
+        ...         "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"))
+        >>> User(email="user@example.com")
+        User(email='user@example.com')
+        >>> User(email="user@example.com@test.com")
+        Traceback (most recent call last):
+            ...
+        ValueError: ("'email' must match regex '(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\\\.[a-zA-Z0-9-.]+$)' ('user@example.com@test.com' doesn't)", Attribute(name='email', default=NOTHING, validator=<matches_re validator for pattern re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)')>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)'), 'user@example.com@test.com')
+
+
+.. autofunction:: attr.validators.deep_iterable
+
+    For example:
+
+    .. doctest::
+
+        >>> @attr.s
+        ... class C(object):
+        ...     x = attr.ib(validator=attr.validators.deep_iterable(
+        ...     member_validator=attr.validators.instance_of(int),
+        ...     iterable_validator=attr.validators.instance_of(list)
+        ...     ))
+        >>> C(x=[1, 2, 3])
+        C(x=[1, 2, 3])
+        >>> C(x=set([1, 2, 3]))
+        Traceback (most recent call last):
+            ...
+        TypeError: ("'x' must be <class 'list'> (got {1, 2, 3} that is a <class 'set'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'list'>, {1, 2, 3})
+        >>> C(x=[1, 2, "3"])
+        Traceback (most recent call last):
+            ...
+        TypeError: ("'x' must be <class 'int'> (got '3' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, '3')
+
+
+.. autofunction:: attr.validators.deep_mapping
+
+    For example:
+
+    .. doctest::
+
+        >>> @attr.s
+        ... class C(object):
+        ...     x = attr.ib(validator=attr.validators.deep_mapping(
+        ...         key_validator=attr.validators.instance_of(str),
+        ...         value_validator=attr.validators.instance_of(int),
+        ...         mapping_validator=attr.validators.instance_of(dict)
+        ...     ))
+        >>> C(x={"a": 1, "b": 2})
+        C(x={'a': 1, 'b': 2})
+        >>> C(x=None)
+        Traceback (most recent call last):
+            ...
+        TypeError: ("'x' must be <class 'dict'> (got None that is a <class 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'dict'>, None)
+        >>> C(x={"a": 1.0, "b": 2})
+        Traceback (most recent call last):
+            ...
+        TypeError: ("'x' must be <class 'int'> (got 1.0 that is a <class 'float'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, 1.0)
+        >>> C(x={"a": 1, 7: 2})
+        Traceback (most recent call last):
+            ...
+        TypeError: ("'x' must be <class 'str'> (got 7 that is a <class 'int'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'str'>, 7)
+
+
+Converters
+----------
+
+.. autofunction:: attr.converters.pipe
+
+   For convenience, it's also possible to pass a list to `attr.ib`'s converter argument.
+
+   Thus the following two statements are equivalent::
+
+      x = attr.ib(converter=attr.converter.pipe(c1, c2, c3))
+      x = attr.ib(converter=[c1, c2, c3])
+
+.. autofunction:: attr.converters.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(converter=attr.converters.optional(int))
+      >>> C(None)
+      C(x=None)
+      >>> C(42)
+      C(x=42)
+
+
+.. autofunction:: attr.converters.default_if_none
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(
+      ...         converter=attr.converters.default_if_none("")
+      ...     )
+      >>> C(None)
+      C(x='')
+
+
+.. _api_setters:
+
+Setters
+-------
+
+These are helpers that you can use together with `attr.s`'s and `attr.ib`'s ``on_setattr`` arguments.
+
+.. autofunction:: attr.setters.frozen
+.. autofunction:: attr.setters.validate
+.. autofunction:: attr.setters.convert
+.. autofunction:: attr.setters.pipe
+.. autodata:: attr.setters.NO_OP
+
+   For example, only ``x`` is frozen here:
+
+   .. doctest::
+
+     >>> @attr.s(on_setattr=attr.setters.frozen)
+     ... class C(object):
+     ...     x = attr.ib()
+     ...     y = attr.ib(on_setattr=attr.setters.NO_OP)
+     >>> c = C(1, 2)
+     >>> c.y = 3
+     >>> c.y
+     3
+     >>> c.x = 4
+     Traceback (most recent call last):
+         ...
+     attr.exceptions.FrozenAttributeError: ()
+
+   N.B. Please use `attr.s`'s *frozen* argument to freeze whole classes; it is more efficient.
+
+
+.. _prov:
+
+Provisional APIs
+----------------
+
+These are Python 3.6 and later-only, keyword-only, and **provisional** APIs that call `attr.s` with different default values.
+
+The most notable differences are:
+
+- automatically detect whether or not *auto_attribs* should be `True`
+- *slots=True*  (see :term:`slotted classes`)
+- *auto_exc=True*
+- *auto_detect=True*
+- *eq=True*, but *order=False*
+- Validators run when you set an attribute (*on_setattr=attr.setters.validate*).
+- Some options that aren't relevant to Python 3 have been dropped.
+
+Please note that these are *defaults* and you're free to override them, just like before.
+
+----
+
+Their behavior is scheduled to become part of the upcoming ``import attrs`` that will introduce a new namespace  with nicer names and nicer defaults (see  `#408 <https://github.com/python-attrs/attrs/issues/408>`_ and `#487 <https://github.com/python-attrs/attrs/issues/487>`_).
+
+Therefore your constructive feedback in the linked issues above is strongly encouraged!
+
+.. note::
+   Provisional doesn't mean we will remove it (although it will be deprecated once the final form is released), but that it might change if we receive relevant feedback.
+
+   `attr.s` and `attr.ib` (and their serious business cousins) aren't going anywhere.
+   The new APIs build on top of them.
+
+.. autofunction:: attr.define
+.. function:: attr.mutable(same_as_define)
+
+   Alias for `attr.define`.
+
+   .. versionadded:: 20.1.0
+
+.. function:: attr.frozen(same_as_define)
+
+   Behaves the same as `attr.define` but sets *frozen=True* and *on_setattr=None*.
+
+   .. versionadded:: 20.1.0
+
+.. autofunction:: attr.field
+
+
+Deprecated APIs
+---------------
+
+.. _version-info:
+
+To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0.
+It behaves similarly to `sys.version_info` and is an instance of `VersionInfo`:
+
+.. autoclass:: VersionInfo
+
+   With its help you can write code like this:
+
+   >>> if getattr(attr, "__version_info__", (0,)) >= (19, 2):
+   ...     cmp_off = {"eq": False}
+   ... else:
+   ...     cmp_off = {"cmp": False}
+   >>> cmp_off == {"eq":  False}
+   True
+   >>> @attr.s(**cmp_off)
+   ... class C(object):
+   ...     pass
+
+
+----
+
+The serious business aliases used to be called ``attr.attributes`` and ``attr.attr``.
+There are no plans to remove them but they shouldn't be used in new code.
+
+The ``cmp`` argument to both `attr.s` and `attr.ib` has been deprecated in 19.2 and shouldn't be used.
+
+.. autofunction:: assoc
diff --git a/docs/backward-compatibility.rst b/docs/backward-compatibility.rst
new file mode 100644 (file)
index 0000000..63466db
--- /dev/null
@@ -0,0 +1,19 @@
+Backward Compatibility
+======================
+
+.. currentmodule:: attr
+
+``attrs`` has a very strong backward compatibility policy that is inspired by the policy of the `Twisted framework <https://twistedmatrix.com/trac/wiki/CompatibilityPolicy>`_.
+
+Put simply, you shouldn't ever be afraid to upgrade ``attrs`` if you're only using its public APIs.
+If there will ever be a need to break compatibility, it will be announced in the `changelog` and raise a ``DeprecationWarning`` for a year (if possible) before it's finally really broken.
+
+
+.. _exemption:
+
+.. warning::
+
+   The structure of the `attr.Attribute` class is exempt from this rule.
+   It *will* change in the future, but since it should be considered read-only, that shouldn't matter.
+
+   However if you intend to build extensions on top of ``attrs`` you have to anticipate that.
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644 (file)
index 0000000..565b052
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../CHANGELOG.rst
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..033a87b
--- /dev/null
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+import os
+import re
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    here = os.path.abspath(os.path.dirname(__file__))
+    with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+def find_version(*file_paths):
+    """
+    Build a path from *file_paths* and search for a ``__version__``
+    string inside.
+    """
+    version_file = read(*file_paths)
+    version_match = re.search(
+        r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M
+    )
+    if version_match:
+        return version_match.group(1)
+    raise RuntimeError("Unable to find version string.")
+
+
+# -- General configuration ------------------------------------------------
+
+# In nitpick mode (-n), still ignore any of the following "broken" references
+# to non-types.
+nitpick_ignore = [
+    ("py:class", "Any value"),
+    ("py:class", "callable"),
+    ("py:class", "callables"),
+    ("py:class", "tuple of types"),
+]
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx.ext.doctest",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.todo",
+]
+
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix of source filenames.
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+
+# General information about the project.
+project = u"attrs"
+copyright = u"2015, Hynek Schlawack"
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+release = find_version("../src/attr/__init__.py")
+version = release.rsplit(u".", 1)[0]
+# The full version, including alpha/beta/rc tags.
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ["_build"]
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+default_role = "any"
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+
+html_theme = "sphinx_rtd_theme"
+html_theme_options = {
+    "canonical_url": "https://www.attrs.org/",
+    "logo_only": True,
+}
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = "_static/attrs_logo_white.png"
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# If false, no module index is generated.
+html_domain_indices = True
+
+# If false, no index is generated.
+html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = "attrsdoc"
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ("index", "attrs", u"attrs Documentation", [u"Hynek Schlawack"], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (
+        "index",
+        "attrs",
+        u"attrs Documentation",
+        u"Hynek Schlawack",
+        "attrs",
+        "One line description of project.",
+        "Miscellaneous",
+    )
+]
+
+intersphinx_mapping = {
+    "https://docs.python.org/3": None,
+}
+
+# Allow non-local URIs so we can have images in CHANGELOG etc.
+suppress_warnings = ["image.nonlocal_uri"]
diff --git a/docs/contributing.rst b/docs/contributing.rst
new file mode 100644 (file)
index 0000000..acb527b
--- /dev/null
@@ -0,0 +1,5 @@
+.. _contributing:
+
+.. include:: ../.github/CONTRIBUTING.rst
+
+.. include:: ../.github/CODE_OF_CONDUCT.rst
diff --git a/docs/docutils.conf b/docs/docutils.conf
new file mode 100644 (file)
index 0000000..db8ca82
--- /dev/null
@@ -0,0 +1,3 @@
+[parsers]
+[restructuredtext parser]
+smart_quotes=yes
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644 (file)
index 0000000..508aa9f
--- /dev/null
@@ -0,0 +1,704 @@
+``attrs`` by Example
+====================
+
+
+Basics
+------
+
+The simplest possible usage is:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class Empty(object):
+   ...     pass
+   >>> Empty()
+   Empty()
+   >>> Empty() == Empty()
+   True
+   >>> Empty() is Empty()
+   False
+
+So in other words: ``attrs`` is useful even without actual attributes!
+
+But you'll usually want some data on your classes, so let's add some:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+By default, all features are added, so you immediately have a fully functional data class with a nice ``repr`` string and comparison methods.
+
+.. doctest::
+
+   >>> c1 = Coordinates(1, 2)
+   >>> c1
+   Coordinates(x=1, y=2)
+   >>> c2 = Coordinates(x=2, y=1)
+   >>> c2
+   Coordinates(x=2, y=1)
+   >>> c1 == c2
+   False
+
+As shown, the generated ``__init__`` method allows for both positional and keyword arguments.
+
+If playful naming turns you off, ``attrs`` comes with serious-business aliases:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib
+   >>> @attrs
+   ... class SeriousCoordinates(object):
+   ...     x = attrib()
+   ...     y = attrib()
+   >>> SeriousCoordinates(1, 2)
+   SeriousCoordinates(x=1, y=2)
+   >>> attr.fields(Coordinates) == attr.fields(SeriousCoordinates)
+   True
+
+For private attributes, ``attrs`` will strip the leading underscores for keyword arguments:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib()
+   >>> C(x=1)
+   C(_x=1)
+
+If you want to initialize your private attributes yourself, you can do that too:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib(init=False, default=42)
+   >>> C()
+   C(_x=42)
+   >>> C(23)
+   Traceback (most recent call last):
+      ...
+   TypeError: __init__() takes exactly 1 argument (2 given)
+
+An additional way of defining attributes is supported too.
+This is useful in times when you want to enhance classes that are not yours (nice ``__repr__`` for Django models anyone?):
+
+.. doctest::
+
+   >>> class SomethingFromSomeoneElse(object):
+   ...     def __init__(self, x):
+   ...         self.x = x
+   >>> SomethingFromSomeoneElse = attr.s(
+   ...     these={
+   ...         "x": attr.ib()
+   ...     }, init=False)(SomethingFromSomeoneElse)
+   >>> SomethingFromSomeoneElse(1)
+   SomethingFromSomeoneElse(x=1)
+
+
+`Subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, but ``attrs`` will still do what you'd hope for:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class A(object):
+   ...     a = attr.ib()
+   ...     def get_a(self):
+   ...         return self.a
+   >>> @attr.s
+   ... class B(object):
+   ...     b = attr.ib()
+   >>> @attr.s
+   ... class C(A, B):
+   ...     c = attr.ib()
+   >>> i = C(1, 2, 3)
+   >>> i
+   C(a=1, b=2, c=3)
+   >>> i == C(1, 2, 3)
+   True
+   >>> i.get_a()
+   1
+
+The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/>`_.
+
+In Python 3, classes defined within other classes are `detected <https://www.python.org/dev/peps/pep-3155/>`_ and reflected in the ``__repr__``.
+In Python 2 though, it's impossible.
+Therefore ``@attr.s`` comes with the ``repr_ns`` option to set it manually:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     @attr.s(repr_ns="C")
+   ...     class D(object):
+   ...         pass
+   >>> C.D()
+   C.D()
+
+``repr_ns`` works on both Python 2 and 3.
+On Python 3 it overrides the implicit detection.
+
+
+Keyword-only Attributes
+~~~~~~~~~~~~~~~~~~~~~~~
+
+When using ``attrs`` on Python 3, you can also add `keyword-only <https://docs.python.org/3/glossary.html#keyword-only-parameter>`_ attributes:
+
+.. doctest::
+
+    >>> @attr.s
+    ... class A:
+    ...     a = attr.ib(kw_only=True)
+    >>> A()
+    Traceback (most recent call last):
+      ...
+    TypeError: A() missing 1 required keyword-only argument: 'a'
+    >>> A(a=1)
+    A(a=1)
+
+``kw_only`` may also be specified at via ``attr.s``, and will apply to all attributes:
+
+.. doctest::
+
+    >>> @attr.s(kw_only=True)
+    ... class A:
+    ...     a = attr.ib()
+    ...     b = attr.ib()
+    >>> A(1, 2)
+    Traceback (most recent call last):
+      ...
+    TypeError: __init__() takes 1 positional argument but 3 were given
+    >>> A(a=1, b=2)
+    A(a=1, b=2)
+
+
+
+If you create an attribute with ``init=False``, the ``kw_only`` argument is ignored.
+
+Keyword-only attributes allow subclasses to add attributes without default values, even if the base class defines attributes with default values:
+
+.. doctest::
+
+    >>> @attr.s
+    ... class A:
+    ...     a = attr.ib(default=0)
+    >>> @attr.s
+    ... class B(A):
+    ...     b = attr.ib(kw_only=True)
+    >>> B(b=1)
+    B(a=0, b=1)
+    >>> B()
+    Traceback (most recent call last):
+      ...
+    TypeError: B() missing 1 required keyword-only argument: 'b'
+
+If you don't set ``kw_only=True``, then there's is no valid attribute ordering and you'll get an error:
+
+.. doctest::
+
+    >>> @attr.s
+    ... class A:
+    ...     a = attr.ib(default=0)
+    >>> @attr.s
+    ... class B(A):
+    ...     b = attr.ib()
+    Traceback (most recent call last):
+      ...
+    ValueError: No mandatory attributes allowed after an attribute with a default value or factory.  Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, converter=None, metadata=mappingproxy({}), type=None, kw_only=False)
+
+.. _asdict:
+
+Converting to Collections Types
+-------------------------------
+
+When you have a class with data, it often is very convenient to transform that class into a `dict` (for example if you want to serialize it to JSON):
+
+.. doctest::
+
+   >>> attr.asdict(Coordinates(x=1, y=2))
+   {'x': 1, 'y': 2}
+
+Some fields cannot or should not be transformed.
+For that, `attr.asdict` offers a callback that decides whether an attribute should be included:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class UserList(object):
+   ...     users = attr.ib()
+   >>> @attr.s
+   ... class User(object):
+   ...     email = attr.ib()
+   ...     password = attr.ib()
+   >>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
+   ...                       User("joe@doe.invalid", "p4ssw0rd")]),
+   ...             filter=lambda attr, value: attr.name != "password")
+   {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
+
+For the common case where you want to `include <attr.filters.include>` or `exclude <attr.filters.exclude>` certain types or attributes, ``attrs`` ships with a few helpers:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class User(object):
+   ...     login = attr.ib()
+   ...     password = attr.ib()
+   ...     id = attr.ib()
+   >>> attr.asdict(
+   ...     User("jane", "s33kred", 42),
+   ...     filter=attr.filters.exclude(attr.fields(User).password, int))
+   {'login': 'jane'}
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib()
+   >>> attr.asdict(C("foo", "2", 3),
+   ...             filter=attr.filters.include(int, attr.fields(C).x))
+   {'x': 'foo', 'z': 3}
+
+Other times, all you want is a tuple and ``attrs`` won't let you down:
+
+.. doctest::
+
+   >>> import sqlite3
+   >>> import attr
+   >>> @attr.s
+   ... class Foo:
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> foo = Foo(2, 3)
+   >>> with sqlite3.connect(":memory:") as conn:
+   ...    c = conn.cursor()
+   ...    c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS
+   ...    c.execute("INSERT INTO foo VALUES (?, ?)", attr.astuple(foo)) #doctest: +ELLIPSIS
+   ...    foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone())
+   <sqlite3.Cursor object at ...>
+   <sqlite3.Cursor object at ...>
+   >>> foo == foo2
+   True
+
+
+Defaults
+--------
+
+Sometimes you want to have default values for your initializer.
+And sometimes you even want mutable objects as default values (ever used accidentally ``def f(arg=[])``?).
+``attrs`` has you covered in both cases:
+
+.. doctest::
+
+   >>> import collections
+   >>> @attr.s
+   ... class Connection(object):
+   ...     socket = attr.ib()
+   ...     @classmethod
+   ...     def connect(cls, db_string):
+   ...        # ... connect somehow to db_string ...
+   ...        return cls(socket=42)
+   >>> @attr.s
+   ... class ConnectionPool(object):
+   ...     db_string = attr.ib()
+   ...     pool = attr.ib(default=attr.Factory(collections.deque))
+   ...     debug = attr.ib(default=False)
+   ...     def get_connection(self):
+   ...         try:
+   ...             return self.pool.pop()
+   ...         except IndexError:
+   ...             if self.debug:
+   ...                 print("New connection!")
+   ...             return Connection.connect(self.db_string)
+   ...     def free_connection(self, conn):
+   ...         if self.debug:
+   ...             print("Connection returned!")
+   ...         self.pool.appendleft(conn)
+   ...
+   >>> cp = ConnectionPool("postgres://localhost")
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False)
+   >>> conn = cp.get_connection()
+   >>> conn
+   Connection(socket=42)
+   >>> cp.free_connection(conn)
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False)
+
+More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <https://as.ynchrono.us/2014/12/asynchronous-object-initialization.html>`_.
+
+Default factories can also be set using a decorator.
+The method receives the partially initialized instance which enables you to base a default value on other attributes:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(default=1)
+   ...     y = attr.ib()
+   ...     @y.default
+   ...     def _any_name_except_a_name_of_an_attribute(self):
+   ...         return self.x + 1
+   >>> C()
+   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
+----------
+
+Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments.
+
+``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suits your style and project better.
+
+You can use a decorator:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     @x.validator
+   ...     def check(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
+
+...or a callable...
+
+.. 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'!
+
+...or both at once:
+
+.. 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=None, kw_only=False), <class 'int'>, '128')
+   >>> C(256)
+   Traceback (most recent call last):
+      ...
+   ValueError: value out of bounds
+
+Please note that the decorator approach only works if -- and only if! -- the attribute in question has an ``attr.ib`` assigned.
+Therefore if you use ``@attr.s(auto_attribs=True)``, it is *not* enough to decorate said attribute with a type.
+
+``attrs`` ships with a bunch of validators, make sure to `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, kw_only=False), <type 'int'>, '42')
+
+Check out `validators` for more details.
+
+
+Conversion
+----------
+
+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
+
+Check out `converters` for more details.
+
+
+.. _metadata:
+
+Metadata
+--------
+
+All ``attrs`` attributes may include arbitrary metadata in the form of a read-only dictionary.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...    x = attr.ib(metadata={'my_metadata': 1})
+    >>> attr.fields(C).x.metadata
+    mappingproxy({'my_metadata': 1})
+    >>> attr.fields(C).x.metadata['my_metadata']
+    1
+
+Metadata is not used by ``attrs``, and is meant to enable rich functionality in third-party libraries.
+The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable.
+
+If you're the author of a third-party library with ``attrs`` integration, please see `Extending Metadata <extending_metadata>`.
+
+
+Types
+-----
+
+``attrs`` also allows you to associate a type with an attribute using either the *type* argument to `attr.ib` or -- as of Python 3.6 -- using `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_-annotations:
+
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C:
+   ...     x = attr.ib(type=int)
+   ...     y: int = attr.ib()
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'int'>
+
+If you don't mind annotating *all* attributes, you can even drop the `attr.ib` and assign default values instead:
+
+.. doctest::
+
+   >>> import typing
+   >>> @attr.s(auto_attribs=True)
+   ... class AutoC:
+   ...     cls_var: typing.ClassVar[int] = 5  # this one is ignored
+   ...     l: typing.List[int] = attr.Factory(list)
+   ...     x: int = 1
+   ...     foo: str = attr.ib(
+   ...          default="every attrib needs a type if auto_attribs=True"
+   ...     )
+   ...     bar: typing.Any = None
+   >>> attr.fields(AutoC).l.type
+   typing.List[int]
+   >>> attr.fields(AutoC).x.type
+   <class 'int'>
+   >>> attr.fields(AutoC).foo.type
+   <class 'str'>
+   >>> attr.fields(AutoC).bar.type
+   typing.Any
+   >>> AutoC()
+   AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
+   >>> AutoC.cls_var
+   5
+
+The generated ``__init__`` method will have an attribute called ``__annotations__`` that contains this type information.
+
+If your annotations contain strings (e.g. forward references),
+you can resolve these after all references have been defined by using :func:`attr.resolve_types`.
+This will replace the *type* attribute in the respective fields.
+
+.. doctest::
+
+    >>> import typing
+    >>> @attr.s(auto_attribs=True)
+    ... class A:
+    ...     a: typing.List['A']
+    ...     b: 'B'
+    ...
+    >>> @attr.s(auto_attribs=True)
+    ... class B:
+    ...     a: A
+    ...
+    >>> attr.fields(A).a.type
+    typing.List[ForwardRef('A')]
+    >>> attr.fields(A).b.type
+    'B'
+    >>> attr.resolve_types(A, globals(), locals())
+    <class 'A'>
+    >>> attr.fields(A).a.type
+    typing.List[A]
+    >>> attr.fields(A).b.type
+    <class 'B'>
+
+.. warning::
+
+   ``attrs`` itself doesn't have any features that work on top of type metadata *yet*.
+   However it's useful for writing your own validators or serialization frameworks.
+
+
+Slots
+-----
+
+:term:`Slotted classes <slotted classes>` have several advantages on CPython.
+Defining ``__slots__`` by hand is tedious, in ``attrs`` it's just a matter of passing ``slots=True``:
+
+.. doctest::
+
+   >>> @attr.s(slots=True)
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+
+Immutability
+------------
+
+Sometimes you have instances that shouldn't be changed after instantiation.
+Immutability is especially popular in functional programming and is generally a very good thing.
+If you'd like to enforce it, ``attrs`` will try to help:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> i = C(1)
+   >>> i.x = 2
+   Traceback (most recent call last):
+      ...
+   attr.exceptions.FrozenInstanceError: can't set attribute
+   >>> i.x
+   1
+
+Please note that true immutability is impossible in Python but it will `get <how-frozen>` you 99% there.
+By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example.
+
+In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes.
+In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/assoc>`_ and ``attrs`` shamelessly imitates it: `attr.evolve`:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> i1 = C(1, 2)
+   >>> i1
+   C(x=1, y=2)
+   >>> i2 = attr.evolve(i1, y=3)
+   >>> i2
+   C(x=1, y=3)
+   >>> i1 == i2
+   False
+
+
+Other Goodies
+-------------
+
+Sometimes you may want to create a class programmatically.
+``attrs`` won't let you down and gives you `attr.make_class` :
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C1(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> C2 = attr.make_class("C2", ["x", "y"])
+   >>> attr.fields(C1) == attr.fields(C2)
+   True
+
+You can still have power over the attributes if you pass a dictionary of name: ``attr.ib`` mappings and can pass arguments to ``@attr.s``:
+
+.. doctest::
+
+   >>> C = attr.make_class("C", {"x": attr.ib(default=42),
+   ...                           "y": attr.ib(default=attr.Factory(list))},
+   ...                     repr=False)
+   >>> i = C()
+   >>> i  # no repr added!
+   <__main__.C object at ...>
+   >>> i.x
+   42
+   >>> i.y
+   []
+
+If you need to dynamically make a class with `attr.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument:
+
+.. doctest::
+
+  >>> class D(object):
+  ...    def __eq__(self, other):
+  ...        return True  # arbitrary example
+  >>> C = attr.make_class("C", {}, bases=(D,), cmp=False)
+  >>> isinstance(C(), D)
+  True
+
+Sometimes, you want to have your class's ``__init__`` method do more than just
+the initialization, validation, etc. that gets done for you automatically when
+using ``@attr.s``.
+To do this, just define a ``__attrs_post_init__`` method in your class.
+It will get called at the end of the generated ``__init__`` method.
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib(init=False)
+   ...
+   ...     def __attrs_post_init__(self):
+   ...         self.z = self.x + self.y
+   >>> obj = C(x=1, y=2)
+   >>> obj
+   C(x=1, y=2, z=3)
+
+You can exclude single attributes from certain methods:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     user = attr.ib()
+   ...     password = attr.ib(repr=False)
+   >>> C("me", "s3kr3t")
+   C(user='me')
+
+Alternatively, to influence how the generated ``__repr__()`` method formats a specific attribute, specify a custom callable to be used instead of the ``repr()`` built-in function:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     user = attr.ib()
+   ...     password = attr.ib(repr=lambda value: '***')
+   >>> C("me", "s3kr3t")
+   C(user='me', password=***)
diff --git a/docs/extending.rst b/docs/extending.rst
new file mode 100644 (file)
index 0000000..654d250
--- /dev/null
@@ -0,0 +1,161 @@
+Extending
+=========
+
+Each ``attrs``-decorated class has a ``__attrs_attrs__`` class attribute.
+It is a tuple of `attr.Attribute` carrying meta-data about each attribute.
+
+So it is fairly simple to build your own decorators on top of ``attrs``:
+
+.. doctest::
+
+   >>> import attr
+   >>> def print_attrs(cls):
+   ...     print(cls.__attrs_attrs__)
+   ...     return cls
+   >>> @print_attrs
+   ... @attr.s
+   ... class C(object):
+   ...     a = attr.ib()
+   (Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None),)
+
+
+.. warning::
+
+   The `attr.s` decorator **must** be applied first because it puts ``__attrs_attrs__`` in place!
+   That means that is has to come *after* your decorator because::
+
+      @a
+      @b
+      def f():
+         pass
+
+   is just `syntactic sugar <https://en.wikipedia.org/wiki/Syntactic_sugar>`_ for::
+
+      def original_f():
+         pass
+
+      f = a(b(original_f))
+
+
+Wrapping the Decorator
+----------------------
+
+A more elegant way can be to wrap ``attrs`` altogether and build a class `DSL <https://en.wikipedia.org/wiki/Domain-specific_language>`_ on top of it.
+
+An example for that is the package `environ-config <https://github.com/hynek/environ-config>`_ that uses ``attrs`` under the hood to define environment-based configurations declaratively without exposing ``attrs`` APIs at all.
+
+Another common use case is to overwrite ``attrs``'s defaults.
+
+Unfortunately, this currently `confuses <https://github.com/python/mypy/issues/5406>`_ mypy's ``attrs`` plugin.
+At the moment, the best workaround is to hold your nose, write a fake mypy plugin, and mutate a bunch of global variables::
+
+   from mypy.plugin import Plugin
+   from mypy.plugins.attrs import (
+      attr_attrib_makers,
+      attr_class_makers,
+      attr_dataclass_makers,
+   )
+
+   # These work just like `attr.dataclass`.
+   attr_dataclass_makers.add("my_module.method_looks_like_attr_dataclass")
+
+   # This works just like `attr.s`.
+   attr_class_makers.add("my_module.method_looks_like_attr_s")
+
+   # These are our `attr.ib` makers.
+   attr_attrib_makers.add("my_module.method_looks_like_attrib")
+
+   class MyPlugin(Plugin):
+       # Our plugin does nothing but it has to exist so this file gets loaded.
+       pass
+
+
+   def plugin(version):
+       return MyPlugin
+
+
+Then tell mypy about your plugin using your project's ``mypy.ini``:
+
+.. code:: ini
+
+   [mypy]
+   plugins=<path to file>
+
+
+.. warning::
+   Please note that it is currently *impossible* to let mypy know that you've changed defaults like *eq* or *order*.
+   You can only use this trick to tell mypy that a class is actually an ``attrs`` class.
+
+
+Types
+-----
+
+``attrs`` offers two ways of attaching type information to attributes:
+
+- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ annotations on Python 3.6 and later,
+- and the *type* argument to `attr.ib`.
+
+This information is available to you:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class C(object):
+   ...     x: int = attr.ib()
+   ...     y = attr.ib(type=str)
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'str'>
+
+Currently, ``attrs`` doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers!
+
+
+.. _extending_metadata:
+
+Metadata
+--------
+
+If you're the author of a third-party library with ``attrs`` integration, you may want to take advantage of attribute metadata.
+
+Here are some tips for effective use of metadata:
+
+- Try making your metadata keys and values immutable.
+  This keeps the entire ``Attribute`` instances immutable too.
+
+- To avoid metadata key collisions, consider exposing your metadata keys from your modules.::
+
+    from mylib import MY_METADATA_KEY
+
+    @attr.s
+    class C(object):
+      x = attr.ib(metadata={MY_METADATA_KEY: 1})
+
+  Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways.
+
+- Expose ``attr.ib`` wrappers for your specific metadata.
+  This is a more graceful approach if your users don't require metadata from other libraries.
+
+  .. doctest::
+
+    >>> MY_TYPE_METADATA = '__my_type_metadata'
+    >>>
+    >>> def typed(
+    ...     cls, default=attr.NOTHING, validator=None, repr=True,
+    ...     eq=True, order=None, hash=None, init=True, metadata={},
+    ...     type=None, converter=None
+    ... ):
+    ...     metadata = dict() if not metadata else metadata
+    ...     metadata[MY_TYPE_METADATA] = cls
+    ...     return attr.ib(
+    ...         default=default, validator=validator, repr=repr,
+    ...         eq=eq, order=order, hash=hash, init=init,
+    ...         metadata=metadata, type=type, converter=converter
+    ...     )
+    >>>
+    >>> @attr.s
+    ... class C(object):
+    ...     x = typed(int, default=1, init=False)
+    >>> attr.fields(C).x.metadata[MY_TYPE_METADATA]
+    <class 'int'>
diff --git a/docs/glossary.rst b/docs/glossary.rst
new file mode 100644 (file)
index 0000000..0d6b75c
--- /dev/null
@@ -0,0 +1,92 @@
+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 whose instances have no ``__dict__`` attribute and `define <https://docs.python.org/3/reference/datamodel.html#slots>`_ their attributes in a ``__slots__`` attribute instead.
+      In ``attrs``, they are created by passing ``slots=True`` to ``@attr.s`` (and are on by default in `attr.define`/`attr.mutable`/`attr.frozen`).
+
+
+      Their main advantage is that they use less memory on CPython [#pypy]_ and are slightly faster.
+
+      However they also come with several 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 slotted classes.
+
+      - However, `it's not possible <https://docs.python.org/3/reference/datamodel.html#notes-on-using-slots>`_ to inherit from more than one class that has attributes in ``__slots__`` (you will get an ``TypeError: multiple bases have instance lay-out conflict``).
+
+      - It's not possible to monkeypatch methods on slotted classes.
+        This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell.
+
+        If you really need to monkeypatch an instance in your tests, but don't want to give up on the advantages of slotted classes in production code, you can always subclass a slotted class as a dict class with no further changes and all the limitations go away:
+
+        .. doctest::
+
+           >>> import attr, unittest.mock
+           >>> @attr.s(slots=True)
+           ... class Slotted(object):
+           ...     x = attr.ib()
+           ...
+           ...     def method(self):
+           ...         return self.x
+           >>> s = Slotted(42)
+           >>> s.method()
+           42
+           >>> with unittest.mock.patch.object(s, "method", return_value=23):
+           ...     pass
+           Traceback (most recent call last):
+              ...
+           AttributeError: 'Slotted' object attribute 'method' is read-only
+           >>> @attr.s  # implies 'slots=False'
+           ... class Dicted(Slotted):
+           ...     pass
+           >>> d = Dicted(42)
+           >>> d.method()
+           42
+           >>> with unittest.mock.patch.object(d, "method", return_value=23):
+           ...     assert 23 == d.method()
+
+      - Slotted classes must implement :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` to be serializable with `pickle` protocol 0 and 1.
+        Therefore, ``attrs`` creates these methods automatically for ``slots=True`` classes (Python 2 uses protocol 0 by default).
+
+        .. note::
+
+            If the ``@attr.s(slots=True)`` decorated class already implements the :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` methods, they will be *overwritten* by ``attrs`` autogenerated implementation by default.
+
+            This can be avoided by setting ``@attr.s(getstate_setstate=False)`` or by setting ``@attr.s(auto_detect=True)``.
+
+        Also, `think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using `pickle`.
+
+      - Slotted classes are weak-referenceable by default.
+        This can be disabled in CPython by passing ``weakref_slot=False`` to ``@attr.s`` [#pypyweakref]_.
+
+      - Since it's currently impossible to make a class slotted after it's been 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.
+.. [#pypyweakref] On PyPy, slotted classes are naturally weak-referenceable so ``weakref_slot=False`` has no effect.
diff --git a/docs/hashing.rst b/docs/hashing.rst
new file mode 100644 (file)
index 0000000..30888f9
--- /dev/null
@@ -0,0 +1,86 @@
+Hashing
+=======
+
+Hash Method Generation
+----------------------
+
+.. 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 equality by object identity: use ``@attr.s(eq=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 `set` or if you want to use them as keys in a `dict`.
+
+The *hash* of an object is an integer that represents the contents of an object.
+It can be obtained by calling `hash` on an object and is implemented by writing a ``__hash__`` method for your class.
+
+``attrs`` will happily write a ``__hash__`` method for 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 fulfill 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 `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(eq=False)``.
+   Setting ``@attr.s(hash=False)`` (which implies ``eq=True``) is almost certainly a *bug*.
+
+   .. warning::
+
+      Be careful when subclassing!
+      Setting ``eq=False`` on a class whose base class has a non-default ``__hash__`` method will *not* make ``attrs`` remove that ``__hash__`` for you.
+
+      It is part of ``attrs``'s philosophy to only *add* to classes so you have the freedom to customize your classes as you wish.
+      So if you want to *get rid* of methods, you'll have to do it by hand.
+
+      The easiest way to reset ``__hash__`` on a class is adding ``__hash__ = object.__hash__`` in the class body.
+
+#. 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`_.
+
+
+Hashing and Mutability
+----------------------
+
+Changing any field involved in hash code computation after the first call to ``__hash__`` (typically this would be after its insertion into a hash-based collection) can result in silent bugs.
+Therefore, it is strongly recommended that hashable classes be ``frozen``.
+Beware, however, that this is not a complete guarantee of safety:
+if a field points to an object and that object is mutated, the hash code may change, but ``frozen`` will not protect you.
+
+
+Hash Code Caching
+-----------------
+
+Some objects have hash codes which are expensive to compute.
+If such objects are to be stored in hash-based collections, it can be useful to compute the hash codes only once and then store the result on the object to make future hash code requests fast.
+To enable caching of hash codes, pass ``cache_hash=True`` to ``@attrs``.
+This may only be done if ``attrs`` is already generating a hash function for the object.
+
+.. [#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/
diff --git a/docs/how-does-it-work.rst b/docs/how-does-it-work.rst
new file mode 100644 (file)
index 0000000..8519c81
--- /dev/null
@@ -0,0 +1,107 @@
+.. _how:
+
+How Does It Work?
+=================
+
+
+Boilerplate
+-----------
+
+``attrs`` certainly isn't the first library that aims to simplify class definition in Python.
+But its **declarative** approach combined with **no runtime overhead** lets it stand out.
+
+Once you apply the ``@attr.s`` decorator to a class, ``attrs`` searches the class object for instances of ``attr.ib``\ s.
+Internally they're a representation of the data passed into ``attr.ib`` along with a counter to preserve the order of the attributes.
+
+In order to ensure that subclassing works as you'd expect it to work, ``attrs`` also walks the class hierarchy and collects the attributes of all base classes.
+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 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:
+
+.. doctest::
+
+   >>> import attr, inspect
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> print(inspect.getsource(C.__init__))
+   def __init__(self, x):
+       self.x = x
+   <BLANKLINE>
+
+No magic, no meta programming, no expensive introspection at runtime.
+
+****
+
+Everything until this point happens exactly *once* when the class is defined.
+As soon as a class is done, it's done.
+And it's just a regular Python class like any other, except for a single ``__attrs_attrs__`` attribute that ``attrs`` uses internally.
+Much of the information is accessible via `attr.fields` and other functions which can be used for introspection or for writing your own tools and decorators on top of ``attrs`` (like `attr.asdict`).
+
+And once you start instantiating your classes, ``attrs`` is out of your way completely.
+
+This **static** approach was very much a design goal of ``attrs`` and what I strongly believe makes it distinct.
+
+
+.. _how-frozen:
+
+Immutability
+------------
+
+In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises an `attr.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute.
+
+The same is true if you choose to freeze individual attributes using the `attr.setters.frozen` *on_setattr* hook -- except that the exception becomes `attr.exceptions.FrozenAttributeError`.
+
+Both errors subclass `attr.exceptions.FrozenError`.
+
+-----
+
+Depending on whether a class is a dict class or a slotted 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.
+
+
+Slotted Classes
++++++++++++++++
+
+Slotted 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
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 378 ns +- 12 ns
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 676 ns +- 16 ns
+
+So on a laptop computer the difference is about 300 nanoseconds (1 second is 1,000,000,000 nanoseconds).
+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.
+
+
+Summary
++++++++
+
+You should avoid instantiating 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).
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..70ef6c1
--- /dev/null
@@ -0,0 +1,94 @@
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+Release v\ |release| (`What's new? <changelog>`).
+
+.. include:: ../README.rst
+   :start-after: teaser-begin
+   :end-before: teaser-end
+
+
+Getting Started
+===============
+
+``attrs`` is a Python-only package `hosted on PyPI <https://pypi.org/project/attrs/>`_.
+The recommended installation method is `pip <https://pip.pypa.io/en/stable/>`_-installing into a `virtualenv <https://hynek.me/articles/virtualenv-lives/>`_:
+
+.. code-block:: console
+
+   $ python -m pip install attrs
+
+The next three steps should bring you up and running in no time:
+
+- `overview` will show you a simple example of ``attrs`` in action and introduce you to its philosophy.
+  Afterwards, you can start writing your own classes, understand what drives ``attrs``'s design, and know what ``@attr.s`` and ``attr.ib()`` stand for.
+- `examples` will give you a comprehensive tour of ``attrs``'s features.
+  After reading, you will know about our advanced features and how to use them.
+- Finally `why` gives you a rundown of potential alternatives and why we think ``attrs`` is superior.
+  Yes, we've heard about ``namedtuple``\ s and Data Classes!
+- If at any point you get confused by some terminology, please check out our `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!
+
+
+Day-to-Day Usage
+================
+
+- `types` help you to write *correct* and *self-documenting* code.
+  ``attrs`` has first class support for them and even allows you to drop the calls to `attr.ib` on modern Python versions!
+- Instance initialization is one of ``attrs`` key feature areas.
+  Our goal is to relieve you from writing as much code as possible.
+  `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 `hashing` will give you a primer on what to look out for.
+- Once you're comfortable with the concepts, our `api` contains all information you need to use ``attrs`` to its fullest.
+- ``attrs`` is built for extension from the ground up.
+  `extending` will show you the affordances it offers and how to make it a building block of your own projects.
+
+
+.. include:: ../README.rst
+   :start-after: -getting-help-
+   :end-before: -project-information-
+
+
+----
+
+
+Full Table of Contents
+======================
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   why
+   examples
+   types
+   init
+   hashing
+   api
+   extending
+   how-does-it-work
+   glossary
+
+
+.. include:: ../README.rst
+   :start-after: -project-information-
+
+.. toctree::
+   :maxdepth: 1
+
+   license
+   backward-compatibility
+   python-2
+   contributing
+   changelog
+
+
+Indices and tables
+==================
+
+* `genindex`
+* `search`
diff --git a/docs/init.rst b/docs/init.rst
new file mode 100644 (file)
index 0000000..ebacded
--- /dev/null
@@ -0,0 +1,373 @@
+Initialization
+==============
+
+In Python, instance initialization 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 `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.
+You also cannot use type annotations to elide the `attr.ib` call for ``d`` as explained in `types`.
+
+
+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 in ``__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 and that you can't elide the call to `attr.ib`.
+
+
+Callables
+~~~~~~~~~
+
+If you want to re-use your validators, you should have a look at the ``validator`` argument to `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 `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 `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 `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 `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)
+
+Note that you *must not* access the hash code of the object in ``__attrs_post__init__`` if ``cache_hash=True``.
+
+
+Order of Execution
+------------------
+
+If present, the hooks are executed in the following order:
+
+1. For each attribute, in the order it was declared:
+
+   a. default factory
+   b. converter
+
+2. *all* validators
+3. ``__attrs_post_init__``
+
+Notably this means, that you can access all attributes from within your validators, but your converters have to deal with invalid values and have to return a valid value.
+
+
+.. _`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
diff --git a/docs/license.rst b/docs/license.rst
new file mode 100644 (file)
index 0000000..cef5f39
--- /dev/null
@@ -0,0 +1,8 @@
+===================
+License and Credits
+===================
+
+``attrs`` is licensed under the `MIT <https://choosealicense.com/licenses/mit/>`_ license.
+The full license text can be also found in the `source code repository <https://github.com/python-attrs/attrs/blob/master/LICENSE>`_.
+
+.. include:: ../AUTHORS.rst
diff --git a/docs/overview.rst b/docs/overview.rst
new file mode 100644 (file)
index 0000000..146a928
--- /dev/null
@@ -0,0 +1,87 @@
+========
+Overview
+========
+
+In order to fulfill its ambitious goal of bringing back the joy to writing classes, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. include:: ../README.rst
+   :start-after: -code-begin-
+   :end-before: -getting-help-
+
+
+.. _philosophy:
+
+Philosophy
+==========
+
+**It's about regular classes.**
+   ``attrs`` is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class.
+   It can be used for data-only containers like ``namedtuple``\ s or ``types.SimpleNamespace`` but they're just a sub-genre of what ``attrs`` is good for.
+
+**The class belongs to the users.**
+   You define a class and ``attrs`` adds static methods to that class based on the attributes you declare.
+   The end.
+   It doesn't add metaclasses.
+   It doesn't add classes you've never heard of to your inheritance tree.
+   An ``attrs`` class in runtime is indistiguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached.
+
+**Be light on API impact.**
+   As convenient as it seems at first, ``attrs`` will *not* tack on any methods to your classes save the dunder ones.
+   Hence all the useful `tools <helpers>` that come with ``attrs`` live in functions that operate on top of instances.
+   Since they take an ``attrs`` instance as their first argument, you can attach them to your classes with one line of code.
+
+**Performance matters.**
+   ``attrs`` runtime impact is very close to zero because all the work is done when the class is defined.
+   Once you're instantiating it, ``attrs`` is out of the picture completely.
+
+**No surprises.**
+   ``attrs`` creates classes that arguably work the way a Python beginner would reasonably expect them to work.
+   It doesn't try to guess what you mean because explicit is better than implicit.
+   It doesn't try to be clever because software shouldn't be clever.
+
+Check out `how-does-it-work` if you'd like to know how it achieves all of the above.
+
+
+What ``attrs`` Is Not
+=====================
+
+``attrs`` does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies.
+
+All ``attrs`` does is:
+
+1. take your declaration,
+2. write dunder methods based on that information,
+3. and attach them to your class.
+
+It does *nothing* dynamic at runtime, hence zero runtime overhead.
+It's still *your* class.
+Do with it as you please.
+
+
+On the ``attr.s`` and ``attr.ib`` Names
+=======================================
+
+The ``attr.s`` decorator and the ``attr.ib`` function aren't any obscure abbreviations.
+They are a *concise* and highly *readable* way to write ``attrs`` and ``attrib`` with an *explicit namespace*.
+
+At first, some people have a negative gut reaction to that; resembling the reactions to Python's significant whitespace.
+And as with that, once one gets used to it, the readability and explicitness of that API prevails and delights.
+
+For those who can't swallow that API at all, ``attrs`` comes with serious business aliases: ``attr.attrs`` and ``attr.attrib``.
+
+Therefore, the following class definition is identical to the previous one:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib, Factory
+   >>> @attrs
+   ... class SomeClass(object):
+   ...     a_number = attrib(default=42)
+   ...     list_of_numbers = attrib(default=Factory(list))
+   ...
+   ...     def hard_math(self, another_number):
+   ...         return self.a_number + sum(self.list_of_numbers) * another_number
+   >>> SomeClass(1, [1, 2, 3])
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+
+Use whichever variant fits your taste better.
diff --git a/docs/python-2.rst b/docs/python-2.rst
new file mode 100644 (file)
index 0000000..4fdf2c9
--- /dev/null
@@ -0,0 +1,25 @@
+Python 2 Statement
+==================
+
+While ``attrs`` has always been a Python 3-first package, we the maintainers are aware that Python 2 will not magically disappear in 2020.
+We are also aware that ``attrs`` is an important building block in many people's systems and livelihoods.
+
+As such, we do **not** have any immediate plans to drop Python 2 support in ``attrs``.
+We intend to support is as long as it will be technically feasible for us.
+
+Feasibility in this case means:
+
+1. Possibility to run the tests on our development computers,
+2. and **free** CI options.
+
+This can mean that we will have to run our tests on PyPy, whose maintainters have unequivocally declared that they do not intend to stop the development and maintenance of their Python 2-compatible line at all.
+And this can mean that at some point, a sponsor will have to step up and pay for bespoke CI setups.
+
+**However**: there is no promise of new features coming to ``attrs`` running under Python 2.
+It is up to our discretion alone, to decide whether the introduced complexity or awkwardness are worth it, or whether we choose to make a feature available on modern platforms only.
+
+
+Summary
+-------
+
+We will do our best to support existing users, but nobody is entitled to the latest and greatest features on a platform that is officially end of life.
diff --git a/docs/types.rst b/docs/types.rst
new file mode 100644 (file)
index 0000000..7829097
--- /dev/null
@@ -0,0 +1,80 @@
+Type Annotations
+================
+
+``attrs`` comes with first class support for type annotations for both Python 3.6 (:pep:`526`) and legacy syntax.
+
+On Python 3.6 and later, you can even drop the `attr.ib`\ s if you're willing to annotate *all* attributes.
+That means that on modern Python versions, the declaration part of the example from the README can be simplified to:
+
+
+.. doctest::
+
+   >>> import attr
+   >>> import typing
+
+   >>> @attr.s(auto_attribs=True)
+   ... class SomeClass:
+   ...     a_number: int = 42
+   ...     list_of_numbers: typing.List[int] = attr.Factory(list)
+
+   >>> sc = SomeClass(1, [1, 2, 3])
+   >>> sc
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+   >>> attr.fields(SomeClass).a_number.type
+   <class 'int'>
+
+You will still need `attr.ib` for advanced features, but not for the common cases.
+
+One of those features are the decorator-based features like defaults.
+It's important to remember that ``attrs`` doesn't do any magic behind your back.
+All the decorators are implemented using an object that is returned by the call to `attr.ib`.
+
+Attributes that only carry a class annotation do not have that object so trying to call a method on it will inevitably fail.
+
+*****
+
+Please note that types -- however added -- are *only metadata* that can be queried from the class and they aren't used for anything out of the box!
+
+Because Python does not allow references to a class object before the class is defined,
+types may be defined as string literals, so-called *forward references*.
+Also, starting in Python 3.10 (:pep:`526`) **all** annotations will be string literals.
+When this happens, ``attrs`` will simply put these string literals into the ``type`` attributes.
+If you need to resolve these to real types, you can call `attr.resolve_types` which will update the attribute in place.
+
+In practice though, types show their biggest usefulness in combination with tools like mypy_ or pytype_ that both have dedicated support for ``attrs`` classes.
+
+
+mypy
+----
+
+While having a nice syntax for type metadata is great, it's even greater that mypy_ as of 0.570 ships with a dedicated ``attrs`` plugin which allows you to statically check your code.
+
+Imagine you add another line that tries to instantiate the defined class using ``SomeClass("23")``.
+Mypy will catch that error for you:
+
+.. code-block:: console
+
+   $ mypy t.py
+   t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected "int"
+
+This happens *without* running your code!
+
+And it also works with *both* Python 2-style annotation styles.
+To mypy, this code is equivalent to the one above:
+
+.. code-block:: python
+
+  @attr.s
+  class SomeClass(object):
+      a_number = attr.ib(default=42)  # type: int
+      list_of_numbers = attr.ib(factory=list, type=typing.List[int])
+
+*****
+
+The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you writing *correct* and *verified self-documenting* code.
+
+If you don't know where to start, Carl Meyer gave a great talk on `Type-checked Python in the Real World <https://www.youtube.com/watch?v=pMgmKJyWKn8>`_ at PyCon US 2018 that will help you to get started in no time.
+
+
+.. _mypy: http://mypy-lang.org
+.. _pytype: https://google.github.io/pytype/
diff --git a/docs/why.rst b/docs/why.rst
new file mode 100644 (file)
index 0000000..3a3d478
--- /dev/null
@@ -0,0 +1,269 @@
+Why not…
+========
+
+
+If you'd like third party's account why ``attrs`` is great, have a look at Glyph's `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_!
+
+
+…tuples?
+--------
+
+
+Readability
+^^^^^^^^^^^
+
+What makes more sense while debugging::
+
+   Point(x=1, y=2)
+
+or::
+
+   (1, 2)
+
+?
+
+Let's add even more ambiguity::
+
+   Customer(id=42, reseller=23, first_name="Jane", last_name="John")
+
+or::
+
+   (42, 23, "Jane", "John")
+
+?
+
+Why would you want to write ``customer[2]`` instead of ``customer.first_name``?
+
+Don't get me started when you add nesting.
+If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly.
+
+Using proper classes with names and types makes program code much more readable and comprehensible_.
+Especially when trying to grok a new piece of software or returning to old code after several months.
+
+.. _comprehensible: https://arxiv.org/pdf/1304.5257.pdf
+
+
+Extendability
+^^^^^^^^^^^^^
+
+Imagine you have a function that takes or returns a tuple.
+Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*.
+
+Adding an attribute to a class concerns only those who actually care about that attribute.
+
+
+…namedtuples?
+-------------
+
+`collections.namedtuple`\ s are tuples with names, not classes. [#history]_
+Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited.
+However that convenience comes at a price.
+
+The most obvious difference between ``namedtuple``\ s and ``attrs``-based classes is that the latter are type-sensitive:
+
+.. doctest::
+
+   >>> import attr
+   >>> C1 = attr.make_class("C1", ["a"])
+   >>> C2 = attr.make_class("C2", ["a"])
+   >>> i1 = C1(1)
+   >>> i2 = C2(1)
+   >>> i1.a == i2.a
+   True
+   >>> i1 == i2
+   False
+
+…while a ``namedtuple`` is *intentionally* `behaving like a tuple`_ which means the type of a tuple is *ignored*:
+
+.. doctest::
+
+   >>> from collections import namedtuple
+   >>> NT1 = namedtuple("NT1", "a")
+   >>> NT2 = namedtuple("NT2", "b")
+   >>> t1 = NT1(1)
+   >>> t2 = NT2(1)
+   >>> t1 == t2 == (1,)
+   True
+
+Other often surprising behaviors include:
+
+- Since they are a subclass of tuples, ``namedtuple``\ s have a length and are both iterable and indexable.
+  That's not what you'd expect from a class and is likely to shadow subtle typo bugs.
+- Iterability also implies that it's easy to accidentally unpack a ``namedtuple`` which leads to hard-to-find bugs. [#iter]_
+- ``namedtuple``\ s have their methods *on your instances* whether you like it or not. [#pollution]_
+- ``namedtuple``\ s are *always* immutable.
+  Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation?  default values?), you have to implement :meth:`__new__() <object.__new__>` which is a particularly hacky and error-prone requirement for a very common problem. [#immutable]_
+- To attach methods to a ``namedtuple`` you have to subclass it.
+  And if you follow the standard library documentation's recommendation of::
+
+    class Point(namedtuple('Point', ['x', 'y'])):
+        # ...
+
+  you end up with a class that has *two* ``Point``\ s in its :attr:`__mro__ <class.__mro__>`: ``[<class 'point.Point'>, <class 'point.Point'>, <type 'tuple'>, <type 'object'>]``.
+
+  That's not only confusing, it also has very practical consequences:
+  for example if you create documentation that includes class hierarchies like `Sphinx's autodoc <https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_ with ``show-inheritance``.
+  Again: common problem, hacky solution with confusing fallout.
+
+All these things make ``namedtuple``\ s a particularly poor choice for public APIs because all your objects are irrevocably tainted.
+With ``attrs`` your users won't notice a difference because it creates regular, well-behaved classes.
+
+.. admonition:: Summary
+
+  If you want a *tuple with names*, by all means: go for a ``namedtuple``. [#perf]_
+  But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand.
+
+  Other than that, ``attrs`` also adds nifty features like validators, converters, and (mutable!) default values.
+
+
+.. rubric:: Footnotes
+
+.. [#history] The word is that ``namedtuple``\ s were added to the Python standard library as a way to make tuples in return values more readable.
+              And indeed that is something you see throughout the standard library.
+
+              Looking at what the makers of ``namedtuple``\ s use it for themselves is a good guideline for deciding on your own use cases.
+.. [#pollution] ``attrs`` only adds a single attribute: ``__attrs_attrs__`` for introspection.
+                All helpers are functions in the ``attr`` package.
+                Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice.
+.. [#iter] `attr.astuple` can be used to get that behavior in ``attrs`` on *explicit demand*.
+.. [#immutable] ``attrs`` offers *optional* immutability through the ``frozen`` keyword.
+.. [#perf] Although ``attrs`` would serve you just as well!
+           Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases ``attrs`` is even faster if you use ``slots=True`` (which is generally a good idea anyway).
+
+.. _behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+
+
+…Data Classes?
+--------------
+
+:pep:`557` added Data Classes to `Python 3.7 <https://docs.python.org/3.7/whatsnew/3.7.html#dataclasses>`_ 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 mainstream 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 development.
+
+
+
+…dicts?
+-------
+
+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.
+
+In other words: if your dict has a fixed and known set of keys, it is an object, not a hash.
+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 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
+
+.. doctest::
+
+   >>> @attr.s
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> SmartClass(1, 2)
+   SmartClass(a=1, b=2)
+
+is roughly
+
+.. doctest::
+
+   >>> class ArtisanalClass(object):
+   ...     def __init__(self, a, b):
+   ...         self.a = a
+   ...         self.b = b
+   ...
+   ...     def __repr__(self):
+   ...         return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
+   ...
+   ...     def __eq__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) == (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ne__(self, other):
+   ...         result = self.__eq__(other)
+   ...         if result is NotImplemented:
+   ...             return NotImplemented
+   ...         else:
+   ...             return not result
+   ...
+   ...     def __lt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) < (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __le__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) <= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __gt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) > (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ge__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) >= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __hash__(self):
+   ...         return hash((self.__class__, self.a, self.b))
+   >>> ArtisanalClass(a=1, b=2)
+   ArtisanalClass(a=1, b=2)
+
+which is quite a mouthful and it doesn't even use any of ``attrs``'s more advanced features like validators or defaults values.
+Also: no tests whatsoever.
+And who will guarantee you, that you don't accidentally flip the ``<`` in your tenth implementation of ``__gt__``?
+
+It also should be noted that ``attrs`` is not an all-or-nothing solution.
+You can freely choose which features you want and disable those that you want more control over:
+
+.. doctest::
+
+   >>> @attr.s(repr=False)
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   ...
+   ...    def __repr__(self):
+   ...        return "<SmartClass(a=%d)>" % (self.a,)
+   >>> SmartClass(1, 2)
+   <SmartClass(a=1)>
+
+.. admonition:: Summary
+
+   If you don't care and like typing, we're not gonna stop you.
+
+   However it takes a lot of bias and determined rationalization to claim that ``attrs`` raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes.
+
+   In any case, if you ever get sick of the repetitiveness and drowning important code in a sea of boilerplate, ``attrs`` will be waiting for you.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..35d6752
--- /dev/null
@@ -0,0 +1,67 @@
+[build-system]
+requires = ["setuptools>=40.6.0", "wheel"]
+build-backend = "setuptools.build_meta"
+
+
+[tool.coverage.run]
+parallel = true
+branch = true
+source = ["attr"]
+
+[tool.coverage.paths]
+source = ["src", ".tox/*/site-packages"]
+
+[tool.coverage.report]
+show_missing = true
+
+
+[tool.black]
+line-length = 79
+
+
+[tool.interrogate]
+verbose = 2
+fail-under = 100
+whitelist-regex = ["test_.*"]
+
+
+[tool.isort]
+atomic=true
+force_grid_wrap=0
+include_trailing_comma=true
+lines_after_imports=2
+lines_between_types=1
+multi_line_output=3
+use_parentheses=true
+
+known_first_party="attr"
+known_third_party=["attr", "hypothesis", "pytest", "setuptools", "six"]
+
+
+[tool.towncrier]
+    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})"
+    underlines = ["-", "^"]
+
+    [[tool.towncrier.section]]
+        path = ""
+
+    [[tool.towncrier.type]]
+        directory = "breaking"
+        name = "Backward-incompatible Changes"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "deprecation"
+        name = "Deprecations"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "change"
+        name = "Changes"
+        showcontent = true
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..8bfd5a1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..6ecc163
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,126 @@
+import codecs
+import os
+import re
+
+from setuptools import find_packages, setup
+
+
+###############################################################################
+
+NAME = "attrs"
+PACKAGES = find_packages(where="src")
+META_PATH = os.path.join("src", "attr", "__init__.py")
+KEYWORDS = ["class", "attribute", "boilerplate"]
+PROJECT_URLS = {
+    "Documentation": "https://www.attrs.org/",
+    "Bug Tracker": "https://github.com/python-attrs/attrs/issues",
+    "Source Code": "https://github.com/python-attrs/attrs",
+}
+CLASSIFIERS = [
+    "Development Status :: 5 - Production/Stable",
+    "Intended Audience :: Developers",
+    "Natural Language :: English",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 2",
+    "Programming Language :: Python :: 2.7",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.5",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+INSTALL_REQUIRES = []
+EXTRAS_REQUIRE = {
+    "docs": ["sphinx", "sphinx-rtd-theme", "zope.interface"],
+    "tests_no_zope": [
+        # 5.0 introduced toml; parallel was broken until 5.0.2
+        "coverage[toml]>=5.0.2",
+        "hypothesis",
+        "pympler",
+        "pytest>=4.3.0",  # 4.3.0 dropped last use of `convert`
+        "six",
+    ],
+}
+EXTRAS_REQUIRE["tests"] = EXTRAS_REQUIRE["tests_no_zope"] + ["zope.interface"]
+EXTRAS_REQUIRE["dev"] = (
+    EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["docs"] + ["pre-commit"]
+)
+
+###############################################################################
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+META_FILE = read(META_PATH)
+
+
+def find_meta(meta):
+    """
+    Extract __*meta*__ from META_FILE.
+    """
+    meta_match = re.search(
+        r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M
+    )
+    if meta_match:
+        return meta_match.group(1)
+    raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta))
+
+
+VERSION = find_meta("version")
+URL = find_meta("url")
+LONG = (
+    read("README.rst")
+    + "\n\n"
+    + "Release Information\n"
+    + "===================\n\n"
+    + re.search(
+        r"(\d+.\d.\d \(.*?\)\r?\n.*?)\r?\n\r?\n\r?\n----\r?\n\r?\n\r?\n",
+        read("CHANGELOG.rst"),
+        re.S,
+    ).group(1)
+    + "\n\n`Full changelog "
+    + "<{url}en/stable/changelog.html>`_.\n\n".format(url=URL)
+    + read("AUTHORS.rst")
+)
+
+
+if __name__ == "__main__":
+    setup(
+        name=NAME,
+        description=find_meta("description"),
+        license=find_meta("license"),
+        url=URL,
+        project_urls=PROJECT_URLS,
+        version=VERSION,
+        author=find_meta("author"),
+        author_email=find_meta("email"),
+        maintainer=find_meta("author"),
+        maintainer_email=find_meta("email"),
+        keywords=KEYWORDS,
+        long_description=LONG,
+        long_description_content_type="text/x-rst",
+        packages=PACKAGES,
+        package_dir={"": "src"},
+        python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+        zip_safe=False,
+        classifiers=CLASSIFIERS,
+        install_requires=INSTALL_REQUIRES,
+        extras_require=EXTRAS_REQUIRE,
+        include_package_data=True,
+        options={"bdist_wheel": {"universal": "1"}},
+    )
diff --git a/src/attr/__init__.py b/src/attr/__init__.py
new file mode 100644 (file)
index 0000000..7a79e57
--- /dev/null
@@ -0,0 +1,76 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+
+from functools import partial
+
+from . import converters, exceptions, filters, setters, validators
+from ._config import get_run_validators, set_run_validators
+from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
+from ._make import (
+    NOTHING,
+    Attribute,
+    Factory,
+    attrib,
+    attrs,
+    fields,
+    fields_dict,
+    make_class,
+    validate,
+)
+from ._version_info import VersionInfo
+
+
+__version__ = "20.2.0"
+__version_info__ = VersionInfo._from_version_string(__version__)
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__url__ = "https://www.attrs.org/"
+__uri__ = __url__
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = partial(attrs, auto_attribs=True)  # happy Easter ;)
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "fields_dict",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "resolve_types",
+    "s",
+    "set_run_validators",
+    "setters",
+    "validate",
+    "validators",
+]
+
+if sys.version_info[:2] >= (3, 6):
+    from ._next_gen import define, field, frozen, mutable
+
+    __all__.extend((define, field, frozen, mutable))
diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi
new file mode 100644 (file)
index 0000000..0869914
--- /dev/null
@@ -0,0 +1,423 @@
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    Generic,
+    List,
+    Optional,
+    Sequence,
+    Mapping,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+    overload,
+)
+
+# `import X as X` is required to make these public
+from . import exceptions as exceptions
+from . import filters as filters
+from . import converters as converters
+from . import setters as setters
+from . import validators as validators
+
+from ._version_info import VersionInfo
+
+__version__: str
+__version_info__: VersionInfo
+__title__: str
+__description__: str
+__url__: str
+__uri__: str
+__author__: str
+__email__: str
+__license__: str
+__copyright__: str
+
+_T = TypeVar("_T")
+_C = TypeVar("_C", bound=type)
+
+_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
+_ConverterType = Callable[[Any], Any]
+_FilterType = Callable[[Attribute[_T], _T], bool]
+_ReprType = Callable[[Any], str]
+_ReprArgType = Union[bool, _ReprType]
+_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any]
+_OnSetAttrArgType = Union[
+    _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType
+]
+# FIXME: in reality, if multiple validators are passed they must be in a list
+# or tuple, but those are invariant and so would prevent subtypes of
+# _ValidatorType from working when passed in a list or tuple.
+_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
+
+# _make --
+
+NOTHING: object
+
+# NOTE: Factory lies about its return type to make this possible:
+# `x: List[int] # = Factory(list)`
+# Work around mypy issue #4554 in the common case by using an overload.
+@overload
+def Factory(factory: Callable[[], _T]) -> _T: ...
+@overload
+def Factory(
+    factory: Union[Callable[[Any], _T], Callable[[], _T]],
+    takes_self: bool = ...,
+) -> _T: ...
+
+class Attribute(Generic[_T]):
+    name: str
+    default: Optional[_T]
+    validator: Optional[_ValidatorType[_T]]
+    repr: _ReprArgType
+    cmp: bool
+    eq: bool
+    order: bool
+    hash: Optional[bool]
+    init: bool
+    converter: Optional[_ConverterType]
+    metadata: Dict[Any, Any]
+    type: Optional[Type[_T]]
+    kw_only: bool
+    on_setattr: _OnSetAttrType
+
+# NOTE: We had several choices for the annotation to use for type arg:
+# 1) Type[_T]
+#   - Pros: Handles simple cases correctly
+#   - Cons: Might produce less informative errors in the case of conflicting
+#     TypeVars e.g. `attr.ib(default='bad', type=int)`
+# 2) Callable[..., _T]
+#   - Pros: Better error messages than #1 for conflicting TypeVars
+#   - Cons: Terrible error messages for validator checks.
+#   e.g. attr.ib(type=int, validator=validate_str)
+#        -> error: Cannot infer function type argument
+# 3) type (and do all of the work in the mypy plugin)
+#   - Pros: Simple here, and we could customize the plugin with our own errors.
+#   - Cons: Would need to write mypy plugin code to handle all the cases.
+# We chose option #1.
+
+# `attr` lies about its return type to make the following possible:
+#     attr()    -> Any
+#     attr(8)   -> int
+#     attr(validator=<some callable>)  -> Whatever the callable expects.
+# This makes this type of assignments possible:
+#     x: int = attr(8)
+#
+# This form catches explicit None or no default but with no other arguments
+# returns Any.
+@overload
+def attrib(
+    default: None = ...,
+    validator: None = ...,
+    repr: _ReprArgType = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    type: None = ...,
+    converter: None = ...,
+    factory: None = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Any: ...
+
+# This form catches an explicit None or no default and infers the type from the
+# other arguments.
+@overload
+def attrib(
+    default: None = ...,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    type: Optional[Type[_T]] = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _T: ...
+
+# This form catches an explicit default argument.
+@overload
+def attrib(
+    default: _T,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    type: Optional[Type[_T]] = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _T: ...
+
+# This form covers type=non-Type: e.g. forward references (str), Any
+@overload
+def attrib(
+    default: Optional[_T] = ...,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    type: object = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Any: ...
+@overload
+def field(
+    *,
+    default: None = ...,
+    validator: None = ...,
+    repr: _ReprArgType = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    converter: None = ...,
+    factory: None = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Any: ...
+
+# This form catches an explicit None or no default and infers the type from the
+# other arguments.
+@overload
+def field(
+    *,
+    default: None = ...,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _T: ...
+
+# This form catches an explicit default argument.
+@overload
+def field(
+    *,
+    default: _T,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _T: ...
+
+# This form covers type=non-Type: e.g. forward references (str), Any
+@overload
+def field(
+    *,
+    default: Optional[_T] = ...,
+    validator: Optional[_ValidatorArgType[_T]] = ...,
+    repr: _ReprArgType = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    metadata: Optional[Mapping[Any, Any]] = ...,
+    converter: Optional[_ConverterType] = ...,
+    factory: Optional[Callable[[], _T]] = ...,
+    kw_only: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Any: ...
+@overload
+def attrs(
+    maybe_cls: _C,
+    these: Optional[Dict[str, Any]] = ...,
+    repr_ns: Optional[str] = ...,
+    repr: bool = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    slots: bool = ...,
+    frozen: bool = ...,
+    weakref_slot: bool = ...,
+    str: bool = ...,
+    auto_attribs: bool = ...,
+    kw_only: bool = ...,
+    cache_hash: bool = ...,
+    auto_exc: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    auto_detect: bool = ...,
+    getstate_setstate: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _C: ...
+@overload
+def attrs(
+    maybe_cls: None = ...,
+    these: Optional[Dict[str, Any]] = ...,
+    repr_ns: Optional[str] = ...,
+    repr: bool = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    slots: bool = ...,
+    frozen: bool = ...,
+    weakref_slot: bool = ...,
+    str: bool = ...,
+    auto_attribs: bool = ...,
+    kw_only: bool = ...,
+    cache_hash: bool = ...,
+    auto_exc: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    auto_detect: bool = ...,
+    getstate_setstate: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Callable[[_C], _C]: ...
+@overload
+def define(
+    maybe_cls: _C,
+    *,
+    these: Optional[Dict[str, Any]] = ...,
+    repr: bool = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    slots: bool = ...,
+    frozen: bool = ...,
+    weakref_slot: bool = ...,
+    str: bool = ...,
+    auto_attribs: bool = ...,
+    kw_only: bool = ...,
+    cache_hash: bool = ...,
+    auto_exc: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    auto_detect: bool = ...,
+    getstate_setstate: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> _C: ...
+@overload
+def define(
+    maybe_cls: None = ...,
+    *,
+    these: Optional[Dict[str, Any]] = ...,
+    repr: bool = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    slots: bool = ...,
+    frozen: bool = ...,
+    weakref_slot: bool = ...,
+    str: bool = ...,
+    auto_attribs: bool = ...,
+    kw_only: bool = ...,
+    cache_hash: bool = ...,
+    auto_exc: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    auto_detect: bool = ...,
+    getstate_setstate: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> Callable[[_C], _C]: ...
+
+mutable = define
+frozen = define  # they differ only in their defaults
+
+# TODO: add support for returning NamedTuple from the mypy plugin
+class _Fields(Tuple[Attribute[Any], ...]):
+    def __getattr__(self, name: str) -> Attribute[Any]: ...
+
+def fields(cls: type) -> _Fields: ...
+def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
+def validate(inst: Any) -> None: ...
+def resolve_types(
+    cls: _C,
+    globalns: Optional[Dict[str, Any]] = ...,
+    localns: Optional[Dict[str, Any]] = ...,
+) -> _C: ...
+
+# TODO: add support for returning a proper attrs class from the mypy plugin
+# we use Any instead of _CountingAttr so that e.g. `make_class('Foo',
+# [attr.ib()])` is valid
+def make_class(
+    name: str,
+    attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]],
+    bases: Tuple[type, ...] = ...,
+    repr_ns: Optional[str] = ...,
+    repr: bool = ...,
+    cmp: Optional[bool] = ...,
+    hash: Optional[bool] = ...,
+    init: bool = ...,
+    slots: bool = ...,
+    frozen: bool = ...,
+    weakref_slot: bool = ...,
+    str: bool = ...,
+    auto_attribs: bool = ...,
+    kw_only: bool = ...,
+    cache_hash: bool = ...,
+    auto_exc: bool = ...,
+    eq: Optional[bool] = ...,
+    order: Optional[bool] = ...,
+    on_setattr: Optional[_OnSetAttrArgType] = ...,
+) -> type: ...
+
+# _funcs --
+
+# TODO: add support for returning TypedDict from the mypy plugin
+# FIXME: asdict/astuple do not honor their factory args. Waiting on one of
+# these:
+# https://github.com/python/mypy/issues/4236
+# https://github.com/python/typing/issues/253
+def asdict(
+    inst: Any,
+    recurse: bool = ...,
+    filter: Optional[_FilterType[Any]] = ...,
+    dict_factory: Type[Mapping[Any, Any]] = ...,
+    retain_collection_types: bool = ...,
+) -> Dict[str, Any]: ...
+
+# TODO: add support for returning NamedTuple from the mypy plugin
+def astuple(
+    inst: Any,
+    recurse: bool = ...,
+    filter: Optional[_FilterType[Any]] = ...,
+    tuple_factory: Type[Sequence[Any]] = ...,
+    retain_collection_types: bool = ...,
+) -> Tuple[Any, ...]: ...
+def has(cls: type) -> bool: ...
+def assoc(inst: _T, **changes: Any) -> _T: ...
+def evolve(inst: _T, **changes: Any) -> _T: ...
+
+# _config --
+
+def set_run_validators(run: bool) -> None: ...
+def get_run_validators() -> bool: ...
+
+# aliases --
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = attrs  # Technically, partial(attrs, auto_attribs=True) ;)
diff --git a/src/attr/_compat.py b/src/attr/_compat.py
new file mode 100644 (file)
index 0000000..bed5b13
--- /dev/null
@@ -0,0 +1,231 @@
+from __future__ import absolute_import, division, print_function
+
+import platform
+import sys
+import types
+import warnings
+
+
+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 collections import Mapping, Sequence
+
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError(
+                "'mappingproxy' object does not support item assignment"
+            )
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError(
+                "'mappingproxy' object has no attribute 'update'"
+            )
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError(
+                "'mappingproxy' object does not support item deletion"
+            )
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError(
+                "'mappingproxy' object has no attribute 'clear'"
+            )
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError(
+                "'mappingproxy' object has no attribute 'pop'"
+            )
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError(
+                "'mappingproxy' object has no attribute 'popitem'"
+            )
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError(
+                "'mappingproxy' object has no attribute 'setdefault'"
+            )
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+    def just_warn(*args, **kw):  # pragma: nocover
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+
+
+else:  # Python 3 and later.
+    from collections.abc import Mapping, Sequence  # noqa
+
+    def just_warn(*args, **kw):
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+        warnings.warn(
+            "Running interpreter doesn't sufficiently support code object "
+            "introspection.  Some features like bare super() or accessing "
+            "__class__ will not work with slotted classes.",
+            RuntimeWarning,
+            stacklevel=2,
+        )
+
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
+
+
+def make_set_closure_cell():
+    """Return a function of two arguments (cell, value) which sets
+    the value stored in the closure cell `cell` to `value`.
+    """
+    # pypy makes this easy. (It also supports the logic below, but
+    # why not do the easy/fast thing?)
+    if PYPY:  # pragma: no cover
+
+        def set_closure_cell(cell, value):
+            cell.__setstate__((value,))
+
+        return set_closure_cell
+
+    # Otherwise gotta do it the hard way.
+
+    # Create a function that will set its first cellvar to `value`.
+    def set_first_cellvar_to(value):
+        x = value
+        return
+
+        # This function will be eliminated as dead code, but
+        # not before its reference to `x` forces `x` to be
+        # represented as a closure cell rather than a local.
+        def force_x_to_be_a_cell():  # pragma: no cover
+            return x
+
+    try:
+        # Extract the code object and make sure our assumptions about
+        # the closure behavior are correct.
+        if PY2:
+            co = set_first_cellvar_to.func_code
+        else:
+            co = set_first_cellvar_to.__code__
+        if co.co_cellvars != ("x",) or co.co_freevars != ():
+            raise AssertionError  # pragma: no cover
+
+        # Convert this code object to a code object that sets the
+        # function's first _freevar_ (not cellvar) to the argument.
+        if sys.version_info >= (3, 8):
+            # CPython 3.8+ has an incompatible CodeType signature
+            # (added a posonlyargcount argument) but also added
+            # CodeType.replace() to do this without counting parameters.
+            set_first_freevar_code = co.replace(
+                co_cellvars=co.co_freevars, co_freevars=co.co_cellvars
+            )
+        else:
+            args = [co.co_argcount]
+            if not PY2:
+                args.append(co.co_kwonlyargcount)
+            args.extend(
+                [
+                    co.co_nlocals,
+                    co.co_stacksize,
+                    co.co_flags,
+                    co.co_code,
+                    co.co_consts,
+                    co.co_names,
+                    co.co_varnames,
+                    co.co_filename,
+                    co.co_name,
+                    co.co_firstlineno,
+                    co.co_lnotab,
+                    # These two arguments are reversed:
+                    co.co_cellvars,
+                    co.co_freevars,
+                ]
+            )
+            set_first_freevar_code = types.CodeType(*args)
+
+        def set_closure_cell(cell, value):
+            # Create a function using the set_first_freevar_code,
+            # whose first closure cell is `cell`. Calling it will
+            # change the value of that cell.
+            setter = types.FunctionType(
+                set_first_freevar_code, {}, "setter", (), (cell,)
+            )
+            # And call it to set the cell.
+            setter(value)
+
+        # Make sure it works on this interpreter:
+        def make_func_with_cell():
+            x = None
+
+            def func():
+                return x  # pragma: no cover
+
+            return func
+
+        if PY2:
+            cell = make_func_with_cell().func_closure[0]
+        else:
+            cell = make_func_with_cell().__closure__[0]
+        set_closure_cell(cell, 100)
+        if cell.cell_contents != 100:
+            raise AssertionError  # pragma: no cover
+
+    except Exception:
+        return just_warn
+    else:
+        return set_closure_cell
+
+
+set_closure_cell = make_set_closure_cell()
diff --git a/src/attr/_config.py b/src/attr/_config.py
new file mode 100644 (file)
index 0000000..8ec9209
--- /dev/null
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py
new file mode 100644 (file)
index 0000000..ca92f9f
--- /dev/null
@@ -0,0 +1,338 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, _obj_setattr, fields
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(
+    inst,
+    recurse=True,
+    filter=None,
+    dict_factory=dict,
+    retain_collection_types=False,
+):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the `attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(
+                    v, True, filter, dict_factory, retain_collection_types
+                )
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf(
+                    [
+                        _asdict_anything(
+                            i, filter, dict_factory, retain_collection_types
+                        )
+                        for i in v
+                    ]
+                )
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df(
+                    (
+                        _asdict_anything(
+                            kk, filter, df, retain_collection_types
+                        ),
+                        _asdict_anything(
+                            vv, filter, df, retain_collection_types
+                        ),
+                    )
+                    for kk, vv in iteritems(v)
+                )
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def _asdict_anything(val, filter, dict_factory, retain_collection_types):
+    """
+    ``asdict`` only works on attrs instances, this works on anything.
+    """
+    if getattr(val.__class__, "__attrs_attrs__", None) is not None:
+        # Attrs class.
+        rv = asdict(val, True, filter, dict_factory, retain_collection_types)
+    elif isinstance(val, (tuple, list, set)):
+        cf = val.__class__ if retain_collection_types is True else list
+        rv = cf(
+            [
+                _asdict_anything(
+                    i, filter, dict_factory, retain_collection_types
+                )
+                for i in val
+            ]
+        )
+    elif isinstance(val, dict):
+        df = dict_factory
+        rv = df(
+            (
+                _asdict_anything(kk, filter, df, retain_collection_types),
+                _asdict_anything(vv, filter, df, retain_collection_types),
+            )
+            for kk, vv in iteritems(val)
+        )
+    else:
+        rv = val
+    return rv
+
+
+def astuple(
+    inst,
+    recurse=True,
+    filter=None,
+    tuple_factory=tuple,
+    retain_collection_types=False,
+):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the `attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(
+                    astuple(
+                        v,
+                        recurse=True,
+                        filter=filter,
+                        tuple_factory=tuple_factory,
+                        retain_collection_types=retain,
+                    )
+                )
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(
+                    cf(
+                        [
+                            astuple(
+                                j,
+                                recurse=True,
+                                filter=filter,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain,
+                            )
+                            if has(j.__class__)
+                            else j
+                            for j in v
+                        ]
+                    )
+                )
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(
+                    df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain,
+                            )
+                            if has(kk.__class__)
+                            else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain,
+                            )
+                            if has(vv.__class__)
+                            else vv,
+                        )
+                        for kk, vv in iteritems(v)
+                    )
+                )
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: bool
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use `evolve` instead.
+    """
+    import warnings
+
+    warnings.warn(
+        "assoc is deprecated and will be removed after 2018/01.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}.".format(
+                    k=k, cl=new.__class__
+                )
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+
+    return cls(**changes)
+
+
+def resolve_types(cls, globalns=None, localns=None):
+    """
+    Resolve any strings and forward annotations in type annotations.
+
+    This is only required if you need concrete types in `Attribute`'s *type*
+    field. In other words, you don't need to resolve your types if you only
+    use them for static type checking.
+
+    With no arguments, names will be looked up in the module in which the class
+    was created. If this is not what you want, e.g. if the name only exists
+    inside a method, you may pass *globalns* or *localns* to specify other
+    dictionaries in which to look up these names. See the docs of
+    `typing.get_type_hints` for more details.
+
+    :param type cls: Class to resolve.
+    :param Optional[dict] globalns: Dictionary containing global variables.
+    :param Optional[dict] localns: Dictionary containing local variables.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+    :raise NameError: If types cannot be resolved because of missing variables.
+
+    :returns: *cls* so you can use this function also as a class decorator.
+        Please note that you have to apply it **after** `attr.s`. That means
+        the decorator has to come in the line **before** `attr.s`.
+
+    ..  versionadded:: 20.1.0
+    """
+    try:
+        # Since calling get_type_hints is expensive we cache whether we've
+        # done it already.
+        cls.__attrs_types_resolved__
+    except AttributeError:
+        import typing
+
+        hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
+        for field in fields(cls):
+            if field.name in hints:
+                # Since fields have been frozen we must work around it.
+                _obj_setattr(field, "type", hints[field.name])
+        cls.__attrs_types_resolved__ = True
+
+    # Return the class so you can use it as a decorator too.
+    return cls
diff --git a/src/attr/_make.py b/src/attr/_make.py
new file mode 100644 (file)
index 0000000..0fbbd7c
--- /dev/null
@@ -0,0 +1,2653 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+import linecache
+import sys
+import threading
+import uuid
+import warnings
+
+from operator import itemgetter
+
+from . import _config, setters
+from ._compat import (
+    PY2,
+    isclass,
+    iteritems,
+    metadata_proxy,
+    ordered_dict,
+    set_closure_cell,
+)
+from .exceptions import (
+    DefaultAlreadySetError,
+    FrozenInstanceError,
+    NotAnAttrsClassError,
+    PythonTooOldError,
+    UnannotatedAttributeError,
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_converter_pat = "__attr_converter_%s"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = (
+    "    {attr_name} = _attrs_property(_attrs_itemgetter({index}))"
+)
+_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
+# we don't use a double-underscore prefix because that triggers
+# name mangling when trying to create a slot for the field
+# (when slots=True)
+_hash_cache_field = "_attrs_cached_hash"
+
+_empty_metadata_singleton = metadata_proxy({})
+
+# Unique object for unequivocal getattr() defaults.
+_sentinel = object()
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    ``_Nothing`` is a singleton. There is only ever one of it.
+    """
+
+    _singleton = None
+
+    def __new__(cls):
+        if _Nothing._singleton is None:
+            _Nothing._singleton = super(_Nothing, cls).__new__(cls)
+        return _Nothing._singleton
+
+    def __repr__(self):
+        return "NOTHING"
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+class _CacheHashWrapper(int):
+    """
+    An integer subclass that pickles / copies as None
+
+    This is used for non-slots classes with ``cache_hash=True``, to avoid
+    serializing a potentially (even likely) invalid hash value. Since ``None``
+    is the default value for uncalculated hashes, whenever this is copied,
+    the copy's value for the hash should automatically reset.
+
+    See GH #613 for more details.
+    """
+
+    if PY2:
+        # For some reason `type(None)` isn't callable in Python 2, but we don't
+        # actually need a constructor for None objects, we just need any
+        # available function that returns None.
+        def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)):
+            return _none_constructor, _args
+
+    else:
+
+        def __reduce__(self, _none_constructor=type(None), _args=()):
+            return _none_constructor, _args
+
+
+def attrib(
+    default=NOTHING,
+    validator=None,
+    repr=True,
+    cmp=None,
+    hash=None,
+    init=True,
+    metadata=None,
+    type=None,
+    converter=None,
+    factory=None,
+    kw_only=False,
+    eq=None,
+    order=None,
+    on_setattr=None,
+):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        `attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of `Factory`, its callable will be
+        used to construct a new value (useful for mutable data types like lists
+        or dicts).
+
+        If a default is not set (or set manually to `attr.NOTHING`), a value
+        *must* be supplied when instantiating; otherwise a `TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value
+
+    :param callable factory: Syntactic sugar for
+        ``default=attr.Factory(factory)``.
+
+    :param validator: `callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the `Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a `list` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        `get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: `callable` or a `list` of `callable`\\ s.
+
+    :param repr: Include this attribute in the generated ``__repr__``
+        method. If ``True``, include the attribute; if ``False``, omit it. By
+        default, the built-in ``repr()`` function is used. To override how the
+        attribute value is formatted, pass a ``callable`` that takes a single
+        value and returns a string. Note that the resulting string is used
+        as-is, i.e. it will be used directly *instead* of calling ``repr()``
+        (the default).
+    :type repr: a `bool` or a `callable` to use a custom function.
+    :param bool eq: If ``True`` (default), include this attribute in the
+        generated ``__eq__`` and ``__ne__`` methods that check two instances
+        for equality.
+    :param bool order: If ``True`` (default), include this attributes in the
+        generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods.
+    :param bool cmp: Setting to ``True`` is equivalent to setting ``eq=True,
+        order=True``. Deprecated in favor of *eq* and *order*.
+    :param Optional[bool] hash: Include this attribute in the generated
+        ``__hash__`` method.  If ``None`` (default), mirror *eq*'s value.  This
+        is the correct behavior according the Python spec.  Setting this value
+        to anything else than ``None`` is *discouraged*.
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable converter: `callable` that is called by
+        ``attrs``-generated ``__init__`` methods to convert attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See `extending_metadata`.
+    :param type: The type of the attribute.  In Python 3.6 or greater, the
+        preferred method to specify the type is using a variable annotation
+        (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_).
+        This argument is provided for backward compatibility.
+        Regardless of the approach used, the type will be stored on
+        ``Attribute.type``.
+
+        Please note that ``attrs`` doesn't do anything with this metadata by
+        itself. You can use it as part of your own code or for
+        `static type checking <types>`.
+    :param kw_only: Make this attribute keyword-only (Python 3+)
+        in the generated ``__init__`` (if ``init`` is ``False``, this
+        parameter is ignored).
+    :param on_setattr: Allows to overwrite the *on_setattr* setting from
+        `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used.
+        Set to `attr.setters.NO_OP` to run **no** `setattr` hooks for this
+        attribute -- regardless of the setting in `attr.s`.
+    :type on_setattr: `callable`, or a list of callables, or `None`, or
+        `attr.setters.NO_OP`
+
+    .. versionadded:: 15.2.0 *convert*
+    .. versionadded:: 16.3.0 *metadata*
+    .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    .. versionchanged:: 17.1.0
+       *hash* is ``None`` and therefore mirrors *eq* by default.
+    .. versionadded:: 17.3.0 *type*
+    .. deprecated:: 17.4.0 *convert*
+    .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
+       *convert* to achieve consistency with other noun-based arguments.
+    .. versionadded:: 18.1.0
+       ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
+    .. versionadded:: 18.2.0 *kw_only*
+    .. versionchanged:: 19.2.0 *convert* keyword argument removed
+    .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
+    .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
+    .. versionadded:: 19.2.0 *eq* and *order*
+    .. versionadded:: 20.1.0 *on_setattr*
+    """
+    eq, order = _determine_eq_order(cmp, eq, order, True)
+
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+
+    if factory is not None:
+        if default is not NOTHING:
+            raise ValueError(
+                "The `default` and `factory` arguments are mutually "
+                "exclusive."
+            )
+        if not callable(factory):
+            raise ValueError("The `factory` argument must be a callable.")
+        default = Factory(factory)
+
+    if metadata is None:
+        metadata = {}
+
+    # Apply syntactic sugar by auto-wrapping.
+    if isinstance(on_setattr, (list, tuple)):
+        on_setattr = setters.pipe(*on_setattr)
+
+    if validator and isinstance(validator, (list, tuple)):
+        validator = and_(*validator)
+
+    if converter and isinstance(converter, (list, tuple)):
+        converter = pipe(*converter)
+
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=None,
+        hash=hash,
+        init=init,
+        converter=converter,
+        metadata=metadata,
+        type=type,
+        kw_only=kw_only,
+        eq=eq,
+        order=order,
+        on_setattr=on_setattr,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(
+                _tuple_property_pat.format(index=i, attr_name=attr_name)
+            )
+    else:
+        attr_class_template.append("    pass")
+    globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+
+    return globs[attr_class_name]
+
+
+# Tuple class for extracted attributes from a class definition.
+# `base_attrs` is a subset of `attrs`.
+_Attributes = _make_attr_tuple_class(
+    "_Attributes",
+    [
+        # all attributes to build dunder methods for
+        "attrs",
+        # attributes that have been inherited
+        "base_attrs",
+        # map inherited attributes to their originating classes
+        "base_attrs_map",
+    ],
+)
+
+
+def _is_class_var(annot):
+    """
+    Check whether *annot* is a typing.ClassVar.
+
+    The string comparison hack is used to avoid evaluating all string
+    annotations which would put attrs-based classes at a performance
+    disadvantage compared to plain old classes.
+    """
+    return str(annot).startswith(_classvar_prefixes)
+
+
+def _has_own_attribute(cls, attrib_name):
+    """
+    Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
+
+    Requires Python 3.
+    """
+    attr = getattr(cls, attrib_name, _sentinel)
+    if attr is _sentinel:
+        return False
+
+    for base_cls in cls.__mro__[1:]:
+        a = getattr(base_cls, attrib_name, None)
+        if attr is a:
+            return False
+
+    return True
+
+
+def _get_annotations(cls):
+    """
+    Get annotations for *cls*.
+    """
+    if _has_own_attribute(cls, "__annotations__"):
+        return cls.__annotations__
+
+    return {}
+
+
+def _counter_getter(e):
+    """
+    Key function for sorting to avoid re-creating a lambda for every class.
+    """
+    return e[1].counter
+
+
+def _collect_base_attrs(cls, taken_attr_names):
+    """
+    Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
+    """
+    base_attrs = []
+    base_attr_map = {}  # A dictionary of base attrs to their classes.
+
+    # Traverse the MRO and collect attributes.
+    for base_cls in reversed(cls.__mro__[1:-1]):
+        for a in getattr(base_cls, "__attrs_attrs__", []):
+            if a.inherited or a.name in taken_attr_names:
+                continue
+
+            a = a._assoc(inherited=True)
+            base_attrs.append(a)
+            base_attr_map[a.name] = base_cls
+
+    # For each name, only keep the freshest definition i.e. the furthest at the
+    # back.  base_attr_map is fine because it gets overwritten with every new
+    # instance.
+    filtered = []
+    seen = set()
+    for a in reversed(base_attrs):
+        if a.name in seen:
+            continue
+        filtered.insert(0, a)
+        seen.add(a.name)
+
+    return filtered, base_attr_map
+
+
+def _collect_base_attrs_broken(cls, taken_attr_names):
+    """
+    Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
+
+    N.B. *taken_attr_names* will be mutated.
+
+    Adhere to the old incorrect behavior.
+
+    Notably it collects from the front and considers inherited attributes which
+    leads to the buggy behavior reported in #428.
+    """
+    base_attrs = []
+    base_attr_map = {}  # A dictionary of base attrs to their classes.
+
+    # Traverse the MRO and collect attributes.
+    for base_cls in cls.__mro__[1:-1]:
+        for a in getattr(base_cls, "__attrs_attrs__", []):
+            if a.name in taken_attr_names:
+                continue
+
+            a = a._assoc(inherited=True)
+            taken_attr_names.add(a.name)
+            base_attrs.append(a)
+            base_attr_map[a.name] = base_cls
+
+    return base_attrs, base_attr_map
+
+
+def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
+    """
+    Transform all `_CountingAttr`s on a class into `Attribute`s.
+
+    If *these* is passed, use that and don't look for them on the class.
+
+    *collect_by_mro* is True, collect them in the correct MRO order, otherwise
+    use the old -- incorrect -- order.  See #428.
+
+    Return an `_Attributes`.
+    """
+    cd = cls.__dict__
+    anns = _get_annotations(cls)
+
+    if these is not None:
+        ca_list = [(name, ca) for name, ca in iteritems(these)]
+
+        if not isinstance(these, ordered_dict):
+            ca_list.sort(key=_counter_getter)
+    elif auto_attribs is True:
+        ca_names = {
+            name
+            for name, attr in cd.items()
+            if isinstance(attr, _CountingAttr)
+        }
+        ca_list = []
+        annot_names = set()
+        for attr_name, type in anns.items():
+            if _is_class_var(type):
+                continue
+            annot_names.add(attr_name)
+            a = cd.get(attr_name, NOTHING)
+            if not isinstance(a, _CountingAttr):
+                if a is NOTHING:
+                    a = attrib()
+                else:
+                    a = attrib(default=a)
+            ca_list.append((attr_name, a))
+
+        unannotated = ca_names - annot_names
+        if len(unannotated) > 0:
+            raise UnannotatedAttributeError(
+                "The following `attr.ib`s lack a type annotation: "
+                + ", ".join(
+                    sorted(unannotated, key=lambda n: cd.get(n).counter)
+                )
+                + "."
+            )
+    else:
+        ca_list = sorted(
+            (
+                (name, attr)
+                for name, attr in cd.items()
+                if isinstance(attr, _CountingAttr)
+            ),
+            key=lambda e: e[1].counter,
+        )
+
+    own_attrs = [
+        Attribute.from_counting_attr(
+            name=attr_name, ca=ca, type=anns.get(attr_name)
+        )
+        for attr_name, ca in ca_list
+    ]
+
+    if collect_by_mro:
+        base_attrs, base_attr_map = _collect_base_attrs(
+            cls, {a.name for a in own_attrs}
+        )
+    else:
+        base_attrs, base_attr_map = _collect_base_attrs_broken(
+            cls, {a.name for a in own_attrs}
+        )
+
+    attr_names = [a.name for a in base_attrs + own_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    if kw_only:
+        own_attrs = [a._assoc(kw_only=True) for a in own_attrs]
+        base_attrs = [a._assoc(kw_only=True) for a in base_attrs]
+
+    attrs = AttrsClass(base_attrs + own_attrs)
+
+    # Mandatory vs non-mandatory attr order only matters when they are part of
+    # the __init__ signature and when they aren't kw_only (which are moved to
+    # the end and can be mandatory or non-mandatory in any order, as they will
+    # be specified as keyword args anyway). Check the order of those attrs:
+    had_default = False
+    for a in (a for a in attrs if a.init is not False and a.kw_only is False):
+        if had_default is True and a.default is NOTHING:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: %r" % (a,)
+            )
+
+        if had_default is False and a.default is not NOTHING:
+            had_default = True
+
+    return _Attributes((attrs, base_attrs, base_attr_map))
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+class _ClassBuilder(object):
+    """
+    Iteratively build *one* class.
+    """
+
+    __slots__ = (
+        "_attr_names",
+        "_attrs",
+        "_base_attr_map",
+        "_base_names",
+        "_cache_hash",
+        "_cls",
+        "_cls_dict",
+        "_delete_attribs",
+        "_frozen",
+        "_has_post_init",
+        "_is_exc",
+        "_on_setattr",
+        "_slots",
+        "_weakref_slot",
+        "_has_own_setattr",
+        "_has_custom_setattr",
+    )
+
+    def __init__(
+        self,
+        cls,
+        these,
+        slots,
+        frozen,
+        weakref_slot,
+        getstate_setstate,
+        auto_attribs,
+        kw_only,
+        cache_hash,
+        is_exc,
+        collect_by_mro,
+        on_setattr,
+        has_custom_setattr,
+    ):
+        attrs, base_attrs, base_map = _transform_attrs(
+            cls, these, auto_attribs, kw_only, collect_by_mro
+        )
+
+        self._cls = cls
+        self._cls_dict = dict(cls.__dict__) if slots else {}
+        self._attrs = attrs
+        self._base_names = set(a.name for a in base_attrs)
+        self._base_attr_map = base_map
+        self._attr_names = tuple(a.name for a in attrs)
+        self._slots = slots
+        self._frozen = frozen
+        self._weakref_slot = weakref_slot
+        self._cache_hash = cache_hash
+        self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
+        self._delete_attribs = not bool(these)
+        self._is_exc = is_exc
+        self._on_setattr = on_setattr
+
+        self._has_custom_setattr = has_custom_setattr
+        self._has_own_setattr = False
+
+        self._cls_dict["__attrs_attrs__"] = self._attrs
+
+        if frozen:
+            self._cls_dict["__setattr__"] = _frozen_setattrs
+            self._cls_dict["__delattr__"] = _frozen_delattrs
+
+            self._has_own_setattr = True
+
+        if getstate_setstate:
+            (
+                self._cls_dict["__getstate__"],
+                self._cls_dict["__setstate__"],
+            ) = self._make_getstate_setstate()
+
+    def __repr__(self):
+        return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__)
+
+    def build_class(self):
+        """
+        Finalize class based on the accumulated configuration.
+
+        Builder cannot be used after calling this method.
+        """
+        if self._slots is True:
+            return self._create_slots_class()
+        else:
+            return self._patch_original_class()
+
+    def _patch_original_class(self):
+        """
+        Apply accumulated methods and return the class.
+        """
+        cls = self._cls
+        base_names = self._base_names
+
+        # Clean class of attribute definitions (`attr.ib()`s).
+        if self._delete_attribs:
+            for name in self._attr_names:
+                if (
+                    name not in base_names
+                    and getattr(cls, name, _sentinel) is not _sentinel
+                ):
+                    try:
+                        delattr(cls, name)
+                    except AttributeError:
+                        # This can happen if a base class defines a class
+                        # variable and we want to set an attribute with the
+                        # same name by using only a type annotation.
+                        pass
+
+        # Attach our dunder methods.
+        for name, value in self._cls_dict.items():
+            setattr(cls, name, value)
+
+        # If we've inherited an attrs __setattr__ and don't write our own,
+        # reset it to object's.
+        if not self._has_own_setattr and getattr(
+            cls, "__attrs_own_setattr__", False
+        ):
+            cls.__attrs_own_setattr__ = False
+
+            if not self._has_custom_setattr:
+                cls.__setattr__ = object.__setattr__
+
+        return cls
+
+    def _create_slots_class(self):
+        """
+        Build and return a new class with a `__slots__` attribute.
+        """
+        base_names = self._base_names
+        cd = {
+            k: v
+            for k, v in iteritems(self._cls_dict)
+            if k not in tuple(self._attr_names) + ("__dict__", "__weakref__")
+        }
+
+        # If our class doesn't have its own implementation of __setattr__
+        # (either from the user or by us), check the bases, if one of them has
+        # an attrs-made __setattr__, that needs to be reset. We don't walk the
+        # MRO because we only care about our immediate base classes.
+        # XXX: This can be confused by subclassing a slotted attrs class with
+        # XXX: a non-attrs class and subclass the resulting class with an attrs
+        # XXX: class.  See `test_slotted_confused` for details.  For now that's
+        # XXX: OK with us.
+        if not self._has_own_setattr:
+            cd["__attrs_own_setattr__"] = False
+
+            if not self._has_custom_setattr:
+                for base_cls in self._cls.__bases__:
+                    if base_cls.__dict__.get("__attrs_own_setattr__", False):
+                        cd["__setattr__"] = object.__setattr__
+                        break
+
+        # Traverse the MRO to check for an existing __weakref__.
+        weakref_inherited = False
+        for base_cls in self._cls.__mro__[1:-1]:
+            if base_cls.__dict__.get("__weakref__", None) is not None:
+                weakref_inherited = True
+                break
+
+        names = self._attr_names
+        if (
+            self._weakref_slot
+            and "__weakref__" not in getattr(self._cls, "__slots__", ())
+            and "__weakref__" not in names
+            and not weakref_inherited
+        ):
+            names += ("__weakref__",)
+
+        # We only add the names of attributes that aren't inherited.
+        # Setting __slots__ to inherited attributes wastes memory.
+        slot_names = [name for name in names if name not in base_names]
+        if self._cache_hash:
+            slot_names.append(_hash_cache_field)
+        cd["__slots__"] = tuple(slot_names)
+
+        qualname = getattr(self._cls, "__qualname__", None)
+        if qualname is not None:
+            cd["__qualname__"] = qualname
+
+        # Create new class based on old class and our methods.
+        cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
+
+        # The following is a fix for
+        # https://github.com/python-attrs/attrs/issues/102.  On Python 3,
+        # if a method mentions `__class__` or uses the no-arg super(), the
+        # compiler will bake a reference to the class in the method itself
+        # as `method.__closure__`.  Since we replace the class with a
+        # clone, we rewrite these references so it keeps working.
+        for item in cls.__dict__.values():
+            if isinstance(item, (classmethod, staticmethod)):
+                # Class- and staticmethods hide their functions inside.
+                # These might need to be rewritten as well.
+                closure_cells = getattr(item.__func__, "__closure__", None)
+            else:
+                closure_cells = getattr(item, "__closure__", None)
+
+            if not closure_cells:  # Catch None or the empty list.
+                continue
+            for cell in closure_cells:
+                try:
+                    match = cell.cell_contents is self._cls
+                except ValueError:  # ValueError: Cell is empty
+                    pass
+                else:
+                    if match:
+                        set_closure_cell(cell, cls)
+
+        return cls
+
+    def add_repr(self, ns):
+        self._cls_dict["__repr__"] = self._add_method_dunders(
+            _make_repr(self._attrs, ns=ns)
+        )
+        return self
+
+    def add_str(self):
+        repr = self._cls_dict.get("__repr__")
+        if repr is None:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        def __str__(self):
+            return self.__repr__()
+
+        self._cls_dict["__str__"] = self._add_method_dunders(__str__)
+        return self
+
+    def _make_getstate_setstate(self):
+        """
+        Create custom __setstate__ and __getstate__ methods.
+        """
+        # __weakref__ is not writable.
+        state_attr_names = tuple(
+            an for an in self._attr_names if an != "__weakref__"
+        )
+
+        def slots_getstate(self):
+            """
+            Automatically created by attrs.
+            """
+            return tuple(getattr(self, name) for name in state_attr_names)
+
+        hash_caching_enabled = self._cache_hash
+
+        def slots_setstate(self, state):
+            """
+            Automatically created by attrs.
+            """
+            __bound_setattr = _obj_setattr.__get__(self, Attribute)
+            for name, value in zip(state_attr_names, state):
+                __bound_setattr(name, value)
+
+            # The hash code cache is not included when the object is
+            # serialized, but it still needs to be initialized to None to
+            # indicate that the first call to __hash__ should be a cache
+            # miss.
+            if hash_caching_enabled:
+                __bound_setattr(_hash_cache_field, None)
+
+        return slots_getstate, slots_setstate
+
+    def make_unhashable(self):
+        self._cls_dict["__hash__"] = None
+        return self
+
+    def add_hash(self):
+        self._cls_dict["__hash__"] = self._add_method_dunders(
+            _make_hash(
+                self._cls,
+                self._attrs,
+                frozen=self._frozen,
+                cache_hash=self._cache_hash,
+            )
+        )
+
+        return self
+
+    def add_init(self):
+        self._cls_dict["__init__"] = self._add_method_dunders(
+            _make_init(
+                self._cls,
+                self._attrs,
+                self._has_post_init,
+                self._frozen,
+                self._slots,
+                self._cache_hash,
+                self._base_attr_map,
+                self._is_exc,
+                self._on_setattr is not None
+                and self._on_setattr is not setters.NO_OP,
+            )
+        )
+
+        return self
+
+    def add_eq(self):
+        cd = self._cls_dict
+
+        cd["__eq__"] = self._add_method_dunders(
+            _make_eq(self._cls, self._attrs)
+        )
+        cd["__ne__"] = self._add_method_dunders(_make_ne())
+
+        return self
+
+    def add_order(self):
+        cd = self._cls_dict
+
+        cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
+            self._add_method_dunders(meth)
+            for meth in _make_order(self._cls, self._attrs)
+        )
+
+        return self
+
+    def add_setattr(self):
+        if self._frozen:
+            return self
+
+        sa_attrs = {}
+        for a in self._attrs:
+            on_setattr = a.on_setattr or self._on_setattr
+            if on_setattr and on_setattr is not setters.NO_OP:
+                sa_attrs[a.name] = a, on_setattr
+
+        if not sa_attrs:
+            return self
+
+        if self._has_custom_setattr:
+            # We need to write a __setattr__ but there already is one!
+            raise ValueError(
+                "Can't combine custom __setattr__ with on_setattr hooks."
+            )
+
+        # docstring comes from _add_method_dunders
+        def __setattr__(self, name, val):
+            try:
+                a, hook = sa_attrs[name]
+            except KeyError:
+                nval = val
+            else:
+                nval = hook(self, a, val)
+
+            _obj_setattr(self, name, nval)
+
+        self._cls_dict["__attrs_own_setattr__"] = True
+        self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
+        self._has_own_setattr = True
+
+        return self
+
+    def _add_method_dunders(self, method):
+        """
+        Add __module__ and __qualname__ to a *method* if possible.
+        """
+        try:
+            method.__module__ = self._cls.__module__
+        except AttributeError:
+            pass
+
+        try:
+            method.__qualname__ = ".".join(
+                (self._cls.__qualname__, method.__name__)
+            )
+        except AttributeError:
+            pass
+
+        try:
+            method.__doc__ = "Method generated by attrs for class %s." % (
+                self._cls.__qualname__,
+            )
+        except AttributeError:
+            pass
+
+        return method
+
+
+_CMP_DEPRECATION = (
+    "The usage of `cmp` is deprecated and will be removed on or after "
+    "2021-06-01.  Please use `eq` and `order` instead."
+)
+
+
+def _determine_eq_order(cmp, eq, order, default_eq):
+    """
+    Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
+    values of eq and order.  If *eq* is None, set it to *default_eq*.
+    """
+    if cmp is not None and any((eq is not None, order is not None)):
+        raise ValueError("Don't mix `cmp` with `eq' and `order`.")
+
+    # cmp takes precedence due to bw-compatibility.
+    if cmp is not None:
+        warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=3)
+
+        return cmp, cmp
+
+    # If left None, equality is set to the specified default and ordering
+    # mirrors equality.
+    if eq is None:
+        eq = default_eq
+
+    if order is None:
+        order = eq
+
+    if eq is False and order is True:
+        raise ValueError("`order` can only be True if `eq` is True too.")
+
+    return eq, order
+
+
+def _determine_whether_to_implement(
+    cls, flag, auto_detect, dunders, default=True
+):
+    """
+    Check whether we should implement a set of methods for *cls*.
+
+    *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
+    same as passed into @attr.s and *dunders* is a tuple of attribute names
+    whose presence signal that the user has implemented it themselves.
+
+    Return *default* if no reason for either for or against is found.
+
+    auto_detect must be False on Python 2.
+    """
+    if flag is True or flag is False:
+        return flag
+
+    if flag is None and auto_detect is False:
+        return default
+
+    # Logically, flag is None and auto_detect is True here.
+    for dunder in dunders:
+        if _has_own_attribute(cls, dunder):
+            return False
+
+    return default
+
+
+def attrs(
+    maybe_cls=None,
+    these=None,
+    repr_ns=None,
+    repr=None,
+    cmp=None,
+    hash=None,
+    init=None,
+    slots=False,
+    frozen=False,
+    weakref_slot=True,
+    str=False,
+    auto_attribs=False,
+    kw_only=False,
+    cache_hash=False,
+    auto_exc=False,
+    eq=None,
+    order=None,
+    auto_detect=False,
+    collect_by_mro=False,
+    getstate_setstate=None,
+    on_setattr=None,
+):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using `attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to `attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes and will *not* remove any attributes from it.
+
+        If *these* is an ordered dict (`dict` on Python 3.6+,
+        `collections.OrderedDict` otherwise), the order is deduced from
+        the order of the attributes inside *these*.  Otherwise the order
+        of the definition of the attributes is used.
+
+    :type these: `dict` of `str` to `attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*,
+        *order*, and *hash* arguments explicitly, assume they are set to
+        ``True`` **unless any** of the involved methods for one of the
+        arguments is implemented in the *current* class (i.e. it is *not*
+        inherited from some base class).
+
+        So for example by implementing ``__eq__`` on a class yourself,
+        ``attrs`` will deduce ``eq=False`` and won't create *neither*
+        ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible
+        ``__ne__`` by default, so it *should* be enough to only implement
+        ``__eq__`` in most cases).
+
+        .. warning::
+
+           If you prevent ``attrs`` from creating the ordering methods for you
+           (``order=False``, e.g. by implementing ``__le__``), it becomes
+           *your* responsibility to make sure its ordering is sound. The best
+           way is to use the `functools.total_ordering` decorator.
+
+
+        Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*,
+        *cmp*, or *hash* overrides whatever *auto_detect* would determine.
+
+        *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises
+        a `PythonTooOldError`.
+
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        representation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        `Exception`\ s.
+    :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__``
+        and ``__ne__`` methods that check two instances for equality.
+
+        They compare the instances as if they were tuples of their ``attrs``
+        attributes if and only if the types of both classes are *identical*!
+    :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that behave like *eq* above and
+        allow instances to be ordered. If ``None`` (default) mirror value of
+        *eq*.
+    :param Optional[bool] cmp: Setting to ``True`` is equivalent to setting
+        ``eq=True, order=True``. Deprecated in favor of *eq* and *order*, has
+        precedence over them for backward-compatibility though. Must not be
+        mixed with *eq* or *order*.
+    :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method
+        is generated according how *eq* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *eq* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the base class will be used (if base class is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See our documentation on `hashing`, Python's documentation on
+        `object.__hash__`, and the `GitHub issue that led to the default \
+        behavior <https://github.com/python-attrs/attrs/issues/136>`_ for more
+        details.
+    :param bool init: Create a ``__init__`` method that initializes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a `slotted class <slotted classes>` that's more
+        memory-efficient.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        `attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class, so you can't implement your own.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance `impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+            5. Subclasses of a frozen class are frozen too.
+
+    :param bool weakref_slot: Make instances weak-referenceable.  This has no
+        effect unless ``slots`` is also enabled.
+    :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated
+        attributes (Python 3.6 and later only) from the class body.
+
+        In this case, you **must** annotate every field.  If ``attrs``
+        encounters a field that is set to an `attr.ib` but lacks a type
+        annotation, an `attr.exceptions.UnannotatedAttributeError` is
+        raised.  Use ``field_name: typing.Any = attr.ib(...)`` if you don't
+        want to set a type.
+
+        If you assign a value to those attributes (e.g. ``x: int = 42``), that
+        value becomes the default value like if it were passed using
+        ``attr.ib(default=42)``.  Passing an instance of `Factory` also
+        works as expected.
+
+        Attributes annotated as `typing.ClassVar`, and attributes that are
+        neither annotated nor set to an `attr.ib` are **ignored**.
+
+        .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
+    :param bool kw_only: Make all attributes keyword-only (Python 3+)
+        in the generated ``__init__`` (if ``init`` is ``False``, this
+        parameter is ignored).
+    :param bool cache_hash: Ensure that the object's hash code is computed
+        only once and stored on the object.  If this is set to ``True``,
+        hashing must be either explicitly or implicitly enabled for this
+        class.  If the hash code is cached, avoid any reassignments of
+        fields involved in hash code computation or mutations of the objects
+        those fields point to after object creation.  If such changes occur,
+        the behavior of the object's hash code is undefined.
+    :param bool auto_exc: If the class subclasses `BaseException`
+        (which implicitly includes any subclass of any exception), the
+        following happens to behave like a well-behaved Python exceptions
+        class:
+
+        - the values for *eq*, *order*, and *hash* are ignored and the
+          instances compare and hash by the instance's ids (N.B. ``attrs`` will
+          *not* remove existing implementations of ``__hash__`` or the equality
+          methods. It just won't add own ones.),
+        - all attributes that are either passed into ``__init__`` or have a
+          default value are additionally available as a tuple in the ``args``
+          attribute,
+        - the value of *str* is ignored leaving ``__str__`` to base classes.
+    :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs``
+       collects attributes from base classes.  The default behavior is
+       incorrect in certain cases of multiple inheritance.  It should be on by
+       default but is kept off for backward-compatability.
+
+       See issue `#428 <https://github.com/python-attrs/attrs/issues/428>`_ for
+       more details.
+
+    :param Optional[bool] getstate_setstate:
+       .. note::
+          This is usually only interesting for slotted classes and you should
+          probably just set *auto_detect* to `True`.
+
+       If `True`, ``__getstate__`` and
+       ``__setstate__`` are generated and attached to the class. This is
+       necessary for slotted classes to be pickleable. If left `None`, it's
+       `True` by default for slotted classes and ``False`` for dict classes.
+
+       If *auto_detect* is `True`, and *getstate_setstate* is left `None`,
+       and **either** ``__getstate__`` or ``__setstate__`` is detected directly
+       on the class (i.e. not inherited), it is set to `False` (this is usually
+       what you want).
+
+    :param on_setattr: A callable that is run whenever the user attempts to set
+        an attribute (either by assignment like ``i.x = 42`` or by using
+        `setattr` like ``setattr(i, "x", 42)``). It receives the same argument
+        as validators: the instance, the attribute that is being modified, and
+        the new value.
+
+        If no exception is raised, the attribute is set to the return value of
+        the callable.
+
+        If a list of callables is passed, they're automatically wrapped in an
+        `attr.setters.pipe`.
+
+
+    .. versionadded:: 16.0.0 *slots*
+    .. versionadded:: 16.1.0 *frozen*
+    .. versionadded:: 16.3.0 *str*
+    .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
+    .. versionchanged:: 17.1.0
+       *hash* supports ``None`` as value which is also the default now.
+    .. versionadded:: 17.3.0 *auto_attribs*
+    .. versionchanged:: 18.1.0
+       If *these* is passed, no attributes are deleted from the class body.
+    .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
+    .. versionadded:: 18.2.0 *weakref_slot*
+    .. deprecated:: 18.2.0
+       ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
+       `DeprecationWarning` if the classes compared are subclasses of
+       each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
+       to each other.
+    .. versionchanged:: 19.2.0
+       ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
+       subclasses comparable anymore.
+    .. versionadded:: 18.2.0 *kw_only*
+    .. versionadded:: 18.2.0 *cache_hash*
+    .. versionadded:: 19.1.0 *auto_exc*
+    .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
+    .. versionadded:: 19.2.0 *eq* and *order*
+    .. versionadded:: 20.1.0 *auto_detect*
+    .. versionadded:: 20.1.0 *collect_by_mro*
+    .. versionadded:: 20.1.0 *getstate_setstate*
+    .. versionadded:: 20.1.0 *on_setattr*
+    """
+    if auto_detect and PY2:
+        raise PythonTooOldError(
+            "auto_detect only works on Python 3 and later."
+        )
+
+    eq_, order_ = _determine_eq_order(cmp, eq, order, None)
+    hash_ = hash  # work around the lack of nonlocal
+
+    if isinstance(on_setattr, (list, tuple)):
+        on_setattr = setters.pipe(*on_setattr)
+
+    def wrap(cls):
+
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        is_frozen = frozen or _has_frozen_base_class(cls)
+        is_exc = auto_exc is True and issubclass(cls, BaseException)
+        has_own_setattr = auto_detect and _has_own_attribute(
+            cls, "__setattr__"
+        )
+
+        if has_own_setattr and is_frozen:
+            raise ValueError("Can't freeze a class with a custom __setattr__.")
+
+        builder = _ClassBuilder(
+            cls,
+            these,
+            slots,
+            is_frozen,
+            weakref_slot,
+            _determine_whether_to_implement(
+                cls,
+                getstate_setstate,
+                auto_detect,
+                ("__getstate__", "__setstate__"),
+                default=slots,
+            ),
+            auto_attribs,
+            kw_only,
+            cache_hash,
+            is_exc,
+            collect_by_mro,
+            on_setattr,
+            has_own_setattr,
+        )
+        if _determine_whether_to_implement(
+            cls, repr, auto_detect, ("__repr__",)
+        ):
+            builder.add_repr(repr_ns)
+        if str is True:
+            builder.add_str()
+
+        eq = _determine_whether_to_implement(
+            cls, eq_, auto_detect, ("__eq__", "__ne__")
+        )
+        if not is_exc and eq is True:
+            builder.add_eq()
+        if not is_exc and _determine_whether_to_implement(
+            cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__")
+        ):
+            builder.add_order()
+
+        builder.add_setattr()
+
+        if (
+            hash_ is None
+            and auto_detect is True
+            and _has_own_attribute(cls, "__hash__")
+        ):
+            hash = False
+        else:
+            hash = hash_
+        if hash is not True and hash is not False and hash is not None:
+            # Can't use `hash in` because 1 == True for example.
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and eq is False) or is_exc:
+            # Don't do anything. Should fall back to __object__'s __hash__
+            # which is by id.
+            if cache_hash:
+                raise TypeError(
+                    "Invalid value for cache_hash.  To use hash caching,"
+                    " hashing must be either explicitly or implicitly "
+                    "enabled."
+                )
+        elif hash is True or (
+            hash is None and eq is True and is_frozen is True
+        ):
+            # Build a __hash__ if told so, or if it's safe.
+            builder.add_hash()
+        else:
+            # Raise TypeError on attempts to hash.
+            if cache_hash:
+                raise TypeError(
+                    "Invalid value for cache_hash.  To use hash caching,"
+                    " hashing must be either explicitly or implicitly "
+                    "enabled."
+                )
+            builder.make_unhashable()
+
+        if _determine_whether_to_implement(
+            cls, init, auto_detect, ("__init__",)
+        ):
+            builder.add_init()
+        else:
+            if cache_hash:
+                raise TypeError(
+                    "Invalid value for cache_hash.  To use hash caching,"
+                    " init must be True."
+                )
+
+        return builder.build_class()
+
+    # maybe_cls's type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attrs` but ``None`` if used as `@attrs()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+_attrs = attrs
+"""
+Internal alias so we can use it in functions that take an argument called
+*attrs*.
+"""
+
+
+if PY2:
+
+    def _has_frozen_base_class(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(cls.__setattr__, "__module__", None)
+            == _frozen_setattrs.__module__
+            and cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+
+
+else:
+
+    def _has_frozen_base_class(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _generate_unique_filename(cls, func_name):
+    """
+    Create a "filename" suitable for a function being generated.
+    """
+    unique_id = uuid.uuid4()
+    extra = ""
+    count = 1
+
+    while True:
+        unique_filename = "<attrs generated {0} {1}.{2}{3}>".format(
+            func_name,
+            cls.__module__,
+            getattr(cls, "__qualname__", cls.__name__),
+            extra,
+        )
+        # To handle concurrency we essentially "reserve" our spot in
+        # the linecache with a dummy line.  The caller can then
+        # set this value correctly.
+        cache_line = (1, None, (str(unique_id),), unique_filename)
+        if (
+            linecache.cache.setdefault(unique_filename, cache_line)
+            == cache_line
+        ):
+            return unique_filename
+
+        # Looks like this spot is taken. Try again.
+        count += 1
+        extra = "-{0}".format(count)
+
+
+def _make_hash(cls, attrs, frozen, cache_hash):
+    attrs = tuple(
+        a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
+    )
+
+    tab = "        "
+
+    unique_filename = _generate_unique_filename(cls, "hash")
+    type_hash = hash(unique_filename)
+
+    hash_def = "def __hash__(self"
+    hash_func = "hash(("
+    closing_braces = "))"
+    if not cache_hash:
+        hash_def += "):"
+    else:
+        if not PY2:
+            hash_def += ", *"
+
+        hash_def += (
+            ", _cache_wrapper="
+            + "__import__('attr._make')._make._CacheHashWrapper):"
+        )
+        hash_func = "_cache_wrapper(" + hash_func
+        closing_braces += ")"
+
+    method_lines = [hash_def]
+
+    def append_hash_computation_lines(prefix, indent):
+        """
+        Generate the code for actually computing the hash code.
+        Below this will either be returned directly or used to compute
+        a value which is then cached, depending on the value of cache_hash
+        """
+
+        method_lines.extend(
+            [
+                indent + prefix + hash_func,
+                indent + "        %d," % (type_hash,),
+            ]
+        )
+
+        for a in attrs:
+            method_lines.append(indent + "        self.%s," % a.name)
+
+        method_lines.append(indent + "    " + closing_braces)
+
+    if cache_hash:
+        method_lines.append(tab + "if self.%s is None:" % _hash_cache_field)
+        if frozen:
+            append_hash_computation_lines(
+                "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2
+            )
+            method_lines.append(tab * 2 + ")")  # close __setattr__
+        else:
+            append_hash_computation_lines(
+                "self.%s = " % _hash_cache_field, tab * 2
+            )
+        method_lines.append(tab + "return self.%s" % _hash_cache_field)
+    else:
+        append_hash_computation_lines("return ", tab)
+
+    script = "\n".join(method_lines)
+    globs = {}
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    return locs["__hash__"]
+
+
+def _add_hash(cls, attrs):
+    """
+    Add a hash method to *cls*.
+    """
+    cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False)
+    return cls
+
+
+def _make_ne():
+    """
+    Create __ne__ method.
+    """
+
+    def __ne__(self, other):
+        """
+        Check equality and either forward a NotImplemented or
+        return the result negated.
+        """
+        result = self.__eq__(other)
+        if result is NotImplemented:
+            return NotImplemented
+
+        return not result
+
+    return __ne__
+
+
+def _make_eq(cls, attrs):
+    """
+    Create __eq__ method for *cls* with *attrs*.
+    """
+    attrs = [a for a in attrs if a.eq]
+
+    unique_filename = _generate_unique_filename(cls, "eq")
+    lines = [
+        "def __eq__(self, other):",
+        "    if other.__class__ is not self.__class__:",
+        "        return NotImplemented",
+    ]
+    # We can't just do a big self.x = other.x and... clause due to
+    # irregularities like nan == nan is false but (nan,) == (nan,) is true.
+    if attrs:
+        lines.append("    return  (")
+        others = ["    ) == ("]
+        for a in attrs:
+            lines.append("        self.%s," % (a.name,))
+            others.append("        other.%s," % (a.name,))
+
+        lines += others + ["    )"]
+    else:
+        lines.append("    return True")
+
+    script = "\n".join(lines)
+    globs = {}
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+    return locs["__eq__"]
+
+
+def _make_order(cls, attrs):
+    """
+    Create ordering methods for *cls* with *attrs*.
+    """
+    attrs = [a for a in attrs if a.order]
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def __lt__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+
+        return NotImplemented
+
+    def __le__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+
+        return NotImplemented
+
+    def __gt__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+
+        return NotImplemented
+
+    def __ge__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+
+        return NotImplemented
+
+    return __lt__, __le__, __gt__, __ge__
+
+
+def _add_eq(cls, attrs=None):
+    """
+    Add equality methods to *cls* with *attrs*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    cls.__eq__ = _make_eq(cls, attrs)
+    cls.__ne__ = _make_ne()
+
+    return cls
+
+
+_already_repring = threading.local()
+
+
+def _make_repr(attrs, ns):
+    """
+    Make a repr method that includes relevant *attrs*, adding *ns* to the full
+    name.
+    """
+
+    # Figure out which attributes to include, and which function to use to
+    # format them. The a.repr value can be either bool or a custom callable.
+    attr_names_with_reprs = tuple(
+        (a.name, repr if a.repr is True else a.repr)
+        for a in attrs
+        if a.repr is not False
+    )
+
+    def __repr__(self):
+        """
+        Automatically created by attrs.
+        """
+        try:
+            working_set = _already_repring.working_set
+        except AttributeError:
+            working_set = set()
+            _already_repring.working_set = working_set
+
+        if id(self) in working_set:
+            return "..."
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        # Since 'self' remains on the stack (i.e.: strongly referenced) for the
+        # duration of this call, it's safe to depend on id(...) stability, and
+        # not need to track the instance and therefore worry about properties
+        # like weakref- or hash-ability.
+        working_set.add(id(self))
+        try:
+            result = [class_name, "("]
+            first = True
+            for name, attr_repr in attr_names_with_reprs:
+                if first:
+                    first = False
+                else:
+                    result.append(", ")
+                result.extend(
+                    (name, "=", attr_repr(getattr(self, name, NOTHING)))
+                )
+            return "".join(result) + ")"
+        finally:
+            working_set.remove(id(self))
+
+    return __repr__
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    cls.__repr__ = _make_repr(attrs, ns)
+    return cls
+
+
+def fields(cls):
+    """
+    Return the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accessors) of `attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def fields_dict(cls):
+    """
+    Return an ordered dictionary of ``attrs`` attributes for a class, whose
+    keys are the attribute names.
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: an ordered dict where keys are attribute names and values are
+        `attr.Attribute`\\ s. This will be a `dict` if it's
+        naturally ordered like on Python 3.6+ or an
+        :class:`~collections.OrderedDict` otherwise.
+
+    .. versionadded:: 18.1.0
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return ordered_dict(((a.name, a) for a in attrs))
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _is_slot_cls(cls):
+    return "__slots__" in cls.__dict__
+
+
+def _is_slot_attr(a_name, base_attr_map):
+    """
+    Check if the attribute name comes from a slot class.
+    """
+    return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name])
+
+
+def _make_init(
+    cls,
+    attrs,
+    post_init,
+    frozen,
+    slots,
+    cache_hash,
+    base_attr_map,
+    is_exc,
+    has_global_on_setattr,
+):
+    if frozen and has_global_on_setattr:
+        raise ValueError("Frozen classes can't use on_setattr.")
+
+    needs_cached_setattr = cache_hash or frozen
+    filtered_attrs = []
+    attr_dict = {}
+    for a in attrs:
+        if not a.init and a.default is NOTHING:
+            continue
+
+        filtered_attrs.append(a)
+        attr_dict[a.name] = a
+
+        if a.on_setattr is not None:
+            if frozen is True:
+                raise ValueError("Frozen classes can't use on_setattr.")
+
+            needs_cached_setattr = True
+        elif (
+            has_global_on_setattr and a.on_setattr is not setters.NO_OP
+        ) or _is_slot_attr(a.name, base_attr_map):
+            needs_cached_setattr = True
+
+    unique_filename = _generate_unique_filename(cls, "init")
+
+    script, globs, annotations = _attrs_to_init_script(
+        filtered_attrs,
+        frozen,
+        slots,
+        post_init,
+        cache_hash,
+        base_attr_map,
+        is_exc,
+        needs_cached_setattr,
+        has_global_on_setattr,
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
+
+    if needs_cached_setattr:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # setattr hooks.
+        globs["_cached_setattr"] = _obj_setattr
+
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    __init__ = locs["__init__"]
+    __init__.__annotations__ = annotations
+
+    return __init__
+
+
+def _setattr(attr_name, value_var, has_on_setattr):
+    """
+    Use the cached object.setattr to set *attr_name* to *value_var*.
+    """
+    return "_setattr('%s', %s)" % (attr_name, value_var)
+
+
+def _setattr_with_converter(attr_name, value_var, has_on_setattr):
+    """
+    Use the cached object.setattr to set *attr_name* to *value_var*, but run
+    its converter first.
+    """
+    return "_setattr('%s', %s(%s))" % (
+        attr_name,
+        _init_converter_pat % (attr_name,),
+        value_var,
+    )
+
+
+def _assign(attr_name, value, has_on_setattr):
+    """
+    Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
+    relegate to _setattr.
+    """
+    if has_on_setattr:
+        return _setattr(attr_name, value, True)
+
+    return "self.%s = %s" % (attr_name, value)
+
+
+def _assign_with_converter(attr_name, value_var, has_on_setattr):
+    """
+    Unless *attr_name* has an on_setattr hook, use normal assignment after
+    conversion. Otherwise relegate to _setattr_with_converter.
+    """
+    if has_on_setattr:
+        return _setattr_with_converter(attr_name, value_var, True)
+
+    return "self.%s = %s(%s)" % (
+        attr_name,
+        _init_converter_pat % (attr_name,),
+        value_var,
+    )
+
+
+def _attrs_to_init_script(
+    attrs,
+    frozen,
+    slots,
+    post_init,
+    cache_hash,
+    base_attr_map,
+    is_exc,
+    needs_cached_setattr,
+    has_global_on_setattr,
+):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+    If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    if needs_cached_setattr:
+        lines.append(
+            # Circumvent the __setattr__ descriptor to save one lookup per
+            # assignment.
+            # Note _setattr will be used again below if cache_hash is True
+            "_setattr = _cached_setattr.__get__(self, self.__class__)"
+        )
+
+    if frozen is True:
+        if slots is True:
+            fmt_setter = _setattr
+            fmt_setter_with_converter = _setattr_with_converter
+        else:
+            # Dict frozen classes assign directly to __dict__.
+            # But only if the attribute doesn't come from an ancestor slot
+            # class.
+            # Note _inst_dict will be used again below if cache_hash is True
+            lines.append("_inst_dict = self.__dict__")
+
+            def fmt_setter(attr_name, value_var, has_on_setattr):
+                if _is_slot_attr(attr_name, base_attr_map):
+                    return _setattr(attr_name, value_var, has_on_setattr)
+
+                return "_inst_dict['%s'] = %s" % (attr_name, value_var)
+
+            def fmt_setter_with_converter(
+                attr_name, value_var, has_on_setattr
+            ):
+                if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
+                    return _setattr_with_converter(
+                        attr_name, value_var, has_on_setattr
+                    )
+
+                return "_inst_dict['%s'] = %s(%s)" % (
+                    attr_name,
+                    _init_converter_pat % (attr_name,),
+                    value_var,
+                )
+
+    else:
+        # Not frozen.
+        fmt_setter = _assign
+        fmt_setter_with_converter = _assign_with_converter
+
+    args = []
+    kw_only_args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+    annotations = {"return": None}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+
+        attr_name = a.name
+        has_on_setattr = a.on_setattr is not None or (
+            a.on_setattr is not setters.NO_OP and has_global_on_setattr
+        )
+        arg_name = a.name.lstrip("_")
+
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.converter is not None:
+                    lines.append(
+                        fmt_setter_with_converter(
+                            attr_name,
+                            init_factory_name + "(%s)" % (maybe_self,),
+                            has_on_setattr,
+                        )
+                    )
+                    conv_name = _init_converter_pat % (a.name,)
+                    names_for_globals[conv_name] = a.converter
+                else:
+                    lines.append(
+                        fmt_setter(
+                            attr_name,
+                            init_factory_name + "(%s)" % (maybe_self,),
+                            has_on_setattr,
+                        )
+                    )
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.converter is not None:
+                    lines.append(
+                        fmt_setter_with_converter(
+                            attr_name,
+                            "attr_dict['%s'].default" % (attr_name,),
+                            has_on_setattr,
+                        )
+                    )
+                    conv_name = _init_converter_pat % (a.name,)
+                    names_for_globals[conv_name] = a.converter
+                else:
+                    lines.append(
+                        fmt_setter(
+                            attr_name,
+                            "attr_dict['%s'].default" % (attr_name,),
+                            has_on_setattr,
+                        )
+                    )
+        elif a.default is not NOTHING and not has_factory:
+            arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name)
+            if a.kw_only:
+                kw_only_args.append(arg)
+            else:
+                args.append(arg)
+
+            if a.converter is not None:
+                lines.append(
+                    fmt_setter_with_converter(
+                        attr_name, arg_name, has_on_setattr
+                    )
+                )
+                names_for_globals[
+                    _init_converter_pat % (a.name,)
+                ] = a.converter
+            else:
+                lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
+
+        elif has_factory:
+            arg = "%s=NOTHING" % (arg_name,)
+            if a.kw_only:
+                kw_only_args.append(arg)
+            else:
+                args.append(arg)
+            lines.append("if %s is not NOTHING:" % (arg_name,))
+
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.converter is not None:
+                lines.append(
+                    "    "
+                    + fmt_setter_with_converter(
+                        attr_name, arg_name, has_on_setattr
+                    )
+                )
+                lines.append("else:")
+                lines.append(
+                    "    "
+                    + fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "(" + maybe_self + ")",
+                        has_on_setattr,
+                    )
+                )
+                names_for_globals[
+                    _init_converter_pat % (a.name,)
+                ] = a.converter
+            else:
+                lines.append(
+                    "    " + fmt_setter(attr_name, arg_name, has_on_setattr)
+                )
+                lines.append("else:")
+                lines.append(
+                    "    "
+                    + fmt_setter(
+                        attr_name,
+                        init_factory_name + "(" + maybe_self + ")",
+                        has_on_setattr,
+                    )
+                )
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            if a.kw_only:
+                kw_only_args.append(arg_name)
+            else:
+                args.append(arg_name)
+
+            if a.converter is not None:
+                lines.append(
+                    fmt_setter_with_converter(
+                        attr_name, arg_name, has_on_setattr
+                    )
+                )
+                names_for_globals[
+                    _init_converter_pat % (a.name,)
+                ] = a.converter
+            else:
+                lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
+
+        if a.init is True and a.converter is None and a.type is not None:
+            annotations[arg_name] = a.type
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_" + a.name
+            attr_name = "__attr_" + a.name
+            lines.append(
+                "    %s(self, %s, self.%s)" % (val_name, attr_name, a.name)
+            )
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    # because this is set only after __attrs_post_init is called, a crash
+    # will result if post-init tries to access the hash code.  This seemed
+    # preferable to setting this beforehand, in which case alteration to
+    # field values during post-init combined with post-init accessing the
+    # hash code would result in silent bugs.
+    if cache_hash:
+        if frozen:
+            if slots:
+                # if frozen and slots, then _setattr defined above
+                init_hash_cache = "_setattr('%s', %s)"
+            else:
+                # if frozen and not slots, then _inst_dict defined above
+                init_hash_cache = "_inst_dict['%s'] = %s"
+        else:
+            init_hash_cache = "self.%s = %s"
+        lines.append(init_hash_cache % (_hash_cache_field, "None"))
+
+    # For exceptions we rely on BaseException.__init__ for proper
+    # initialization.
+    if is_exc:
+        vals = ",".join("self." + a.name for a in attrs if a.init)
+
+        lines.append("BaseException.__init__(self, %s)" % (vals,))
+
+    args = ", ".join(args)
+    if kw_only_args:
+        if PY2:
+            raise PythonTooOldError(
+                "Keyword-only arguments only work on Python 3 and later."
+            )
+
+        args += "{leading_comma}*, {kw_only_args}".format(
+            leading_comma=", " if args else "",
+            kw_only_args=", ".join(kw_only_args),
+        )
+    return (
+        """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+            args=args, lines="\n    ".join(lines) if lines else "pass"
+        ),
+        names_for_globals,
+        annotations,
+    )
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+    :attribute inherited: Whether or not that attribute has been inherited from
+        a base class.
+
+    Plus *all* arguments of `attr.ib` (except for ``factory``
+    which is only syntactic sugar for ``default=Factory(...)``.
+
+    .. versionadded:: 20.1.0 *inherited*
+    .. versionadded:: 20.1.0 *on_setattr*
+    .. versionchanged:: 20.2.0 *inherited* is not taken into account for
+        equality checks and hashing anymore.
+
+    For the full version history of the fields, see `attr.ib`.
+    """
+
+    __slots__ = (
+        "name",
+        "default",
+        "validator",
+        "repr",
+        "eq",
+        "order",
+        "hash",
+        "init",
+        "metadata",
+        "type",
+        "converter",
+        "kw_only",
+        "inherited",
+        "on_setattr",
+    )
+
+    def __init__(
+        self,
+        name,
+        default,
+        validator,
+        repr,
+        cmp,  # XXX: unused, remove along with other cmp code.
+        hash,
+        init,
+        inherited,
+        metadata=None,
+        type=None,
+        converter=None,
+        kw_only=False,
+        eq=None,
+        order=None,
+        on_setattr=None,
+    ):
+        eq, order = _determine_eq_order(cmp, eq, order, True)
+
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        # Despite the big red warning, people *do* instantiate `Attribute`
+        # themselves.
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("eq", eq)
+        bound_setattr("order", order)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("converter", converter)
+        bound_setattr(
+            "metadata",
+            (
+                metadata_proxy(metadata)
+                if metadata
+                else _empty_metadata_singleton
+            ),
+        )
+        bound_setattr("type", type)
+        bound_setattr("kw_only", kw_only)
+        bound_setattr("inherited", inherited)
+        bound_setattr("on_setattr", on_setattr)
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @classmethod
+    def from_counting_attr(cls, name, ca, type=None):
+        # type holds the annotated value. deal with conflicts:
+        if type is None:
+            type = ca.type
+        elif ca.type is not None:
+            raise ValueError(
+                "Type annotation and type argument cannot both be present"
+            )
+        inst_dict = {
+            k: getattr(ca, k)
+            for k in Attribute.__slots__
+            if k
+            not in (
+                "name",
+                "validator",
+                "default",
+                "type",
+                "inherited",
+            )  # exclude methods and deprecated alias
+        }
+        return cls(
+            name=name,
+            validator=ca._validator,
+            default=ca._default,
+            type=type,
+            cmp=None,
+            inherited=False,
+            **inst_dict
+        )
+
+    @property
+    def cmp(self):
+        """
+        Simulate the presence of a cmp attribute and warn.
+        """
+        warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2)
+
+        return self.eq and self.order
+
+    # Don't use attr.assoc since fields(Attribute) doesn't work
+    def _assoc(self, **changes):
+        """
+        Copy *self* and apply *changes*.
+        """
+        new = copy.copy(self)
+
+        new._setattrs(changes.items())
+
+        return new
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(
+            getattr(self, name) if name != "metadata" else dict(self.metadata)
+            for name in self.__slots__
+        )
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        self._setattrs(zip(self.__slots__, state))
+
+    def _setattrs(self, name_values_pairs):
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in name_values_pairs:
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(
+                    name,
+                    metadata_proxy(value)
+                    if value
+                    else _empty_metadata_singleton,
+                )
+
+
+_a = [
+    Attribute(
+        name=name,
+        default=NOTHING,
+        validator=None,
+        repr=True,
+        cmp=None,
+        eq=True,
+        order=False,
+        hash=(name != "metadata"),
+        init=True,
+        inherited=False,
+    )
+    for name in Attribute.__slots__
+]
+
+Attribute = _add_hash(
+    _add_eq(
+        _add_repr(Attribute, attrs=_a),
+        attrs=[a for a in _a if a.name != "inherited"],
+    ),
+    attrs=[a for a in _a if a.hash and a.name != "inherited"],
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+
+    __slots__ = (
+        "counter",
+        "_default",
+        "repr",
+        "eq",
+        "order",
+        "hash",
+        "init",
+        "metadata",
+        "_validator",
+        "converter",
+        "type",
+        "kw_only",
+        "on_setattr",
+    )
+    __attrs_attrs__ = tuple(
+        Attribute(
+            name=name,
+            default=NOTHING,
+            validator=None,
+            repr=True,
+            cmp=None,
+            hash=True,
+            init=True,
+            kw_only=False,
+            eq=True,
+            order=False,
+            inherited=False,
+            on_setattr=None,
+        )
+        for name in (
+            "counter",
+            "_default",
+            "repr",
+            "eq",
+            "order",
+            "hash",
+            "init",
+            "on_setattr",
+        )
+    ) + (
+        Attribute(
+            name="metadata",
+            default=None,
+            validator=None,
+            repr=True,
+            cmp=None,
+            hash=False,
+            init=True,
+            kw_only=False,
+            eq=True,
+            order=False,
+            inherited=False,
+            on_setattr=None,
+        ),
+    )
+    cls_counter = 0
+
+    def __init__(
+        self,
+        default,
+        validator,
+        repr,
+        cmp,  # XXX: unused, remove along with cmp
+        hash,
+        init,
+        converter,
+        metadata,
+        type,
+        kw_only,
+        eq,
+        order,
+        on_setattr,
+    ):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        self._validator = validator
+        self.converter = converter
+        self.repr = repr
+        self.eq = eq
+        self.order = order
+        self.hash = hash
+        self.init = init
+        self.metadata = metadata
+        self.type = type
+        self.kw_only = kw_only
+        self.on_setattr = on_setattr
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_eq(_add_repr(_CountingAttr))
+
+
+@attrs(slots=True, init=False, hash=True)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to `attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+
+    factory = attrib()
+    takes_self = attrib()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param str name: The name for the new class.
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+
+        If *attrs* is a list or an ordered dict (`dict` on Python 3.6+,
+        `collections.OrderedDict` otherwise), the order is deduced from
+        the order of the names or attributes inside *attrs*.  Otherwise the
+        order of the definition of the attributes is used.
+    :type attrs: `list` or `dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to `attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    .. versionadded:: 17.1.0 *bases*
+    .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attrib()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    post_init = cls_dict.pop("__attrs_post_init__", None)
+    type_ = type(
+        name,
+        bases,
+        {} if post_init is None else {"__attrs_post_init__": post_init},
+    )
+    # For pickling to work, the __module__ variable needs to be set to the
+    # frame where the class is created.  Bypass this step in environments where
+    # sys._getframe is not defined (Jython for example) or sys._getframe is not
+    # defined for arguments greater than 0 (IronPython).
+    try:
+        type_.__module__ = sys._getframe(1).f_globals.get(
+            "__name__", "__main__"
+        )
+    except (AttributeError, ValueError):
+        pass
+
+    # We do it here for proper warnings with meaningful stacklevel.
+    cmp = attributes_arguments.pop("cmp", None)
+    (
+        attributes_arguments["eq"],
+        attributes_arguments["order"],
+    ) = _determine_eq_order(
+        cmp,
+        attributes_arguments.get("eq"),
+        attributes_arguments.get("order"),
+        True,
+    )
+
+    return _attrs(these=cls_dict, **attributes_arguments)(type_)
+
+
+# These are required by within this module so we define them here and merely
+# import into .validators / .converters.
+
+
+@attrs(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+
+    _validators = attrib()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param callables validators: Arbitrary number of validators.
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators
+            if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
+
+
+def pipe(*converters):
+    """
+    A converter that composes multiple converters into one.
+
+    When called on a value, it runs all wrapped converters, returning the
+    *last* value.
+
+    :param callables converters: Arbitrary number of converters.
+
+    .. versionadded:: 20.1.0
+    """
+
+    def pipe_converter(val):
+        for converter in converters:
+            val = converter(val)
+
+        return val
+
+    return pipe_converter
diff --git a/src/attr/_next_gen.py b/src/attr/_next_gen.py
new file mode 100644 (file)
index 0000000..b5ff60e
--- /dev/null
@@ -0,0 +1,158 @@
+"""
+This is a Python 3.6 and later-only, keyword-only, and **provisional** API that
+calls `attr.s` with different default values.
+
+Provisional APIs that shall become "import attrs" one glorious day.
+"""
+
+from functools import partial
+
+from attr.exceptions import UnannotatedAttributeError
+
+from . import setters
+from ._make import NOTHING, _frozen_setattrs, attrib, attrs
+
+
+def define(
+    maybe_cls=None,
+    *,
+    these=None,
+    repr=None,
+    hash=None,
+    init=None,
+    slots=True,
+    frozen=False,
+    weakref_slot=True,
+    str=False,
+    auto_attribs=None,
+    kw_only=False,
+    cache_hash=False,
+    auto_exc=True,
+    eq=None,
+    order=False,
+    auto_detect=True,
+    getstate_setstate=None,
+    on_setattr=None,
+):
+    r"""
+    The only behavioral differences are the handling of the *auto_attribs*
+    option:
+
+    :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves
+       exactly like `attr.s`. If left `None`, `attr.s` will try to guess:
+
+       1. If all attributes are annotated and no `attr.ib` is found, it assumes
+          *auto_attribs=True*.
+       2. Otherwise it assumes *auto_attribs=False* and tries to collect
+          `attr.ib`\ s.
+
+    and that mutable classes (``frozen=False``) validate on ``__setattr__``.
+
+    .. versionadded:: 20.1.0
+    """
+
+    def do_it(cls, auto_attribs):
+        return attrs(
+            maybe_cls=cls,
+            these=these,
+            repr=repr,
+            hash=hash,
+            init=init,
+            slots=slots,
+            frozen=frozen,
+            weakref_slot=weakref_slot,
+            str=str,
+            auto_attribs=auto_attribs,
+            kw_only=kw_only,
+            cache_hash=cache_hash,
+            auto_exc=auto_exc,
+            eq=eq,
+            order=order,
+            auto_detect=auto_detect,
+            collect_by_mro=True,
+            getstate_setstate=getstate_setstate,
+            on_setattr=on_setattr,
+        )
+
+    def wrap(cls):
+        """
+        Making this a wrapper ensures this code runs during class creation.
+
+        We also ensure that frozen-ness of classes is inherited.
+        """
+        nonlocal frozen, on_setattr
+
+        had_on_setattr = on_setattr not in (None, setters.NO_OP)
+
+        # By default, mutable classes validate on setattr.
+        if frozen is False and on_setattr is None:
+            on_setattr = setters.validate
+
+        # However, if we subclass a frozen class, we inherit the immutability
+        # and disable on_setattr.
+        for base_cls in cls.__bases__:
+            if base_cls.__setattr__ is _frozen_setattrs:
+                if had_on_setattr:
+                    raise ValueError(
+                        "Frozen classes can't use on_setattr "
+                        "(frozen-ness was inherited)."
+                    )
+
+                on_setattr = setters.NO_OP
+                break
+
+        if auto_attribs is not None:
+            return do_it(cls, auto_attribs)
+
+        try:
+            return do_it(cls, True)
+        except UnannotatedAttributeError:
+            return do_it(cls, False)
+
+    # maybe_cls's type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attrs` but ``None`` if used as `@attrs()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+mutable = define
+frozen = partial(define, frozen=True, on_setattr=None)
+
+
+def field(
+    *,
+    default=NOTHING,
+    validator=None,
+    repr=True,
+    hash=None,
+    init=True,
+    metadata=None,
+    converter=None,
+    factory=None,
+    kw_only=False,
+    eq=None,
+    order=None,
+    on_setattr=None,
+):
+    """
+    Identical to `attr.ib`, except keyword-only and with some arguments
+    removed.
+
+    .. versionadded:: 20.1.0
+    """
+    return attrib(
+        default=default,
+        validator=validator,
+        repr=repr,
+        hash=hash,
+        init=init,
+        metadata=metadata,
+        converter=converter,
+        factory=factory,
+        kw_only=kw_only,
+        eq=eq,
+        order=order,
+        on_setattr=on_setattr,
+    )
diff --git a/src/attr/_version_info.py b/src/attr/_version_info.py
new file mode 100644 (file)
index 0000000..014e78a
--- /dev/null
@@ -0,0 +1,85 @@
+from __future__ import absolute_import, division, print_function
+
+from functools import total_ordering
+
+from ._funcs import astuple
+from ._make import attrib, attrs
+
+
+@total_ordering
+@attrs(eq=False, order=False, slots=True, frozen=True)
+class VersionInfo(object):
+    """
+    A version object that can be compared to tuple of length 1--4:
+
+    >>> attr.VersionInfo(19, 1, 0, "final")  <= (19, 2)
+    True
+    >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1)
+    True
+    >>> vi = attr.VersionInfo(19, 2, 0, "final")
+    >>> vi < (19, 1, 1)
+    False
+    >>> vi < (19,)
+    False
+    >>> vi == (19, 2,)
+    True
+    >>> vi == (19, 2, 1)
+    False
+
+    .. versionadded:: 19.2
+    """
+
+    year = attrib(type=int)
+    minor = attrib(type=int)
+    micro = attrib(type=int)
+    releaselevel = attrib(type=str)
+
+    @classmethod
+    def _from_version_string(cls, s):
+        """
+        Parse *s* and return a _VersionInfo.
+        """
+        v = s.split(".")
+        if len(v) == 3:
+            v.append("final")
+
+        return cls(
+            year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3]
+        )
+
+    def _ensure_tuple(self, other):
+        """
+        Ensure *other* is a tuple of a valid length.
+
+        Returns a possibly transformed *other* and ourselves as a tuple of
+        the same length as *other*.
+        """
+
+        if self.__class__ is other.__class__:
+            other = astuple(other)
+
+        if not isinstance(other, tuple):
+            raise NotImplementedError
+
+        if not (1 <= len(other) <= 4):
+            raise NotImplementedError
+
+        return astuple(self)[: len(other)], other
+
+    def __eq__(self, other):
+        try:
+            us, them = self._ensure_tuple(other)
+        except NotImplementedError:
+            return NotImplemented
+
+        return us == them
+
+    def __lt__(self, other):
+        try:
+            us, them = self._ensure_tuple(other)
+        except NotImplementedError:
+            return NotImplemented
+
+        # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
+        # have to do anything special with releaselevel for now.
+        return us < them
diff --git a/src/attr/_version_info.pyi b/src/attr/_version_info.pyi
new file mode 100644 (file)
index 0000000..45ced08
--- /dev/null
@@ -0,0 +1,9 @@
+class VersionInfo:
+    @property
+    def year(self) -> int: ...
+    @property
+    def minor(self) -> int: ...
+    @property
+    def micro(self) -> int: ...
+    @property
+    def releaselevel(self) -> str: ...
diff --git a/src/attr/converters.py b/src/attr/converters.py
new file mode 100644 (file)
index 0000000..715ce17
--- /dev/null
@@ -0,0 +1,85 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import NOTHING, Factory, pipe
+
+
+__all__ = [
+    "pipe",
+    "optional",
+    "default_if_none",
+]
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    .. versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
+
+
+def default_if_none(default=NOTHING, factory=None):
+    """
+    A converter that allows to replace ``None`` values by *default* or the
+    result of *factory*.
+
+    :param default: Value to be used if ``None`` is passed. Passing an instance
+       of `attr.Factory` is supported, however the ``takes_self`` option
+       is *not*.
+    :param callable factory: A callable that takes not parameters whose result
+       is used if ``None`` is passed.
+
+    :raises TypeError: If **neither** *default* or *factory* is passed.
+    :raises TypeError: If **both** *default* and *factory* are passed.
+    :raises ValueError: If an instance of `attr.Factory` is passed with
+       ``takes_self=True``.
+
+    .. versionadded:: 18.2.0
+    """
+    if default is NOTHING and factory is None:
+        raise TypeError("Must pass either `default` or `factory`.")
+
+    if default is not NOTHING and factory is not None:
+        raise TypeError(
+            "Must pass either `default` or `factory` but not both."
+        )
+
+    if factory is not None:
+        default = Factory(factory)
+
+    if isinstance(default, Factory):
+        if default.takes_self:
+            raise ValueError(
+                "`takes_self` is not supported by default_if_none."
+            )
+
+        def default_if_none_converter(val):
+            if val is not None:
+                return val
+
+            return default.factory()
+
+    else:
+
+        def default_if_none_converter(val):
+            if val is not None:
+                return val
+
+            return default
+
+    return default_if_none_converter
diff --git a/src/attr/converters.pyi b/src/attr/converters.pyi
new file mode 100644 (file)
index 0000000..7b0caa1
--- /dev/null
@@ -0,0 +1,11 @@
+from typing import TypeVar, Optional, Callable, overload
+from . import _ConverterType
+
+_T = TypeVar("_T")
+
+def pipe(*validators: _ConverterType) -> _ConverterType: ...
+def optional(converter: _ConverterType) -> _ConverterType: ...
+@overload
+def default_if_none(default: _T) -> _ConverterType: ...
+@overload
+def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ...
diff --git a/src/attr/exceptions.py b/src/attr/exceptions.py
new file mode 100644 (file)
index 0000000..fcd8910
--- /dev/null
@@ -0,0 +1,92 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenError(AttributeError):
+    """
+    A frozen/immutable instance or attribute haave been attempted to be
+    modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing `AttributeError`.
+
+    .. versionadded:: 20.1.0
+    """
+
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class FrozenInstanceError(FrozenError):
+    """
+    A frozen instance has been attempted to be modified.
+
+    .. versionadded:: 16.1.0
+    """
+
+
+class FrozenAttributeError(FrozenError):
+    """
+    A frozen attribute has been attempted to be modified.
+
+    .. versionadded:: 20.1.0
+    """
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
+
+
+class UnannotatedAttributeError(RuntimeError):
+    """
+    A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
+    annotation.
+
+    .. versionadded:: 17.3.0
+    """
+
+
+class PythonTooOldError(RuntimeError):
+    """
+    It was attempted to use an ``attrs`` feature that requires a newer Python
+    version.
+
+    .. versionadded:: 18.2.0
+    """
+
+
+class NotCallableError(TypeError):
+    """
+    A ``attr.ib()`` requiring a callable has been set with a value
+    that is not callable.
+
+    .. versionadded:: 19.2.0
+    """
+
+    def __init__(self, msg, value):
+        super(TypeError, self).__init__(msg, value)
+        self.msg = msg
+        self.value = value
+
+    def __str__(self):
+        return str(self.msg)
diff --git a/src/attr/exceptions.pyi b/src/attr/exceptions.pyi
new file mode 100644 (file)
index 0000000..f268011
--- /dev/null
@@ -0,0 +1,17 @@
+from typing import Any
+
+class FrozenError(AttributeError):
+    msg: str = ...
+
+class FrozenInstanceError(FrozenError): ...
+class FrozenAttributeError(FrozenError): ...
+class AttrsAttributeNotFoundError(ValueError): ...
+class NotAnAttrsClassError(ValueError): ...
+class DefaultAlreadySetError(RuntimeError): ...
+class UnannotatedAttributeError(RuntimeError): ...
+class PythonTooOldError(RuntimeError): ...
+
+class NotCallableError(TypeError):
+    msg: str = ...
+    value: Any = ...
+    def __init__(self, msg: str, value: Any) -> None: ...
diff --git a/src/attr/filters.py b/src/attr/filters.py
new file mode 100644 (file)
index 0000000..dc47e8f
--- /dev/null
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for `attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: `list` of `type` or `attr.Attribute`\\ s
+
+    :rtype: `callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: `list` of classes or `attr.Attribute`\\ s.
+
+    :rtype: `callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
diff --git a/src/attr/filters.pyi b/src/attr/filters.pyi
new file mode 100644 (file)
index 0000000..68368fe
--- /dev/null
@@ -0,0 +1,5 @@
+from typing import Union, Any
+from . import Attribute, _FilterType
+
+def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
+def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
diff --git a/src/attr/py.typed b/src/attr/py.typed
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/attr/setters.py b/src/attr/setters.py
new file mode 100644 (file)
index 0000000..240014b
--- /dev/null
@@ -0,0 +1,77 @@
+"""
+Commonly used hooks for on_setattr.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from . import _config
+from .exceptions import FrozenAttributeError
+
+
+def pipe(*setters):
+    """
+    Run all *setters* and return the return value of the last one.
+
+    .. versionadded:: 20.1.0
+    """
+
+    def wrapped_pipe(instance, attrib, new_value):
+        rv = new_value
+
+        for setter in setters:
+            rv = setter(instance, attrib, rv)
+
+        return rv
+
+    return wrapped_pipe
+
+
+def frozen(_, __, ___):
+    """
+    Prevent an attribute to be modified.
+
+    .. versionadded:: 20.1.0
+    """
+    raise FrozenAttributeError()
+
+
+def validate(instance, attrib, new_value):
+    """
+    Run *attrib*'s validator on *new_value* if it has one.
+
+    .. versionadded:: 20.1.0
+    """
+    if _config._run_validators is False:
+        return new_value
+
+    v = attrib.validator
+    if not v:
+        return new_value
+
+    v(instance, attrib, new_value)
+
+    return new_value
+
+
+def convert(instance, attrib, new_value):
+    """
+    Run *attrib*'s converter -- if it has one --  on *new_value* and return the
+    result.
+
+    .. versionadded:: 20.1.0
+    """
+    c = attrib.converter
+    if c:
+        return c(new_value)
+
+    return new_value
+
+
+NO_OP = object()
+"""
+Sentinel for disabling class-wide *on_setattr* hooks for certain attributes.
+
+Does not work in `pipe` or within lists.
+
+.. versionadded:: 20.1.0
+"""
diff --git a/src/attr/setters.pyi b/src/attr/setters.pyi
new file mode 100644 (file)
index 0000000..19bc33f
--- /dev/null
@@ -0,0 +1,18 @@
+from . import _OnSetAttrType, Attribute
+from typing import TypeVar, Any, NewType, NoReturn, cast
+
+_T = TypeVar("_T")
+
+def frozen(
+    instance: Any, attribute: Attribute, new_value: Any
+) -> NoReturn: ...
+def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ...
+def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ...
+
+# convert is allowed to return Any, because they can be chained using pipe.
+def convert(
+    instance: Any, attribute: Attribute[Any], new_value: Any
+) -> Any: ...
+
+_NoOpType = NewType("_NoOpType", object)
+NO_OP: _NoOpType
diff --git a/src/attr/validators.py b/src/attr/validators.py
new file mode 100644 (file)
index 0000000..b9a7305
--- /dev/null
@@ -0,0 +1,379 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import re
+
+from ._make import _AndValidator, and_, attrib, attrs
+from .exceptions import NotCallableError
+
+
+__all__ = [
+    "and_",
+    "deep_iterable",
+    "deep_mapping",
+    "in_",
+    "instance_of",
+    "is_callable",
+    "matches_re",
+    "optional",
+    "provides",
+]
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r}).".format(
+                    name=attr.name,
+                    type=self.type,
+                    actual=value.__class__,
+                    value=value,
+                ),
+                attr,
+                self.type,
+                value,
+            )
+
+    def __repr__(self):
+        return "<instance_of validator for type {type!r}>".format(
+            type=self.type
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a `TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are performed using
+    `isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type `attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attrs(repr=False, frozen=True, slots=True)
+class _MatchesReValidator(object):
+    regex = attrib()
+    flags = attrib()
+    match_func = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.match_func(value):
+            raise ValueError(
+                "'{name}' must match regex {regex!r}"
+                " ({value!r} doesn't)".format(
+                    name=attr.name, regex=self.regex.pattern, value=value
+                ),
+                attr,
+                self.regex,
+                value,
+            )
+
+    def __repr__(self):
+        return "<matches_re validator for pattern {regex!r}>".format(
+            regex=self.regex
+        )
+
+
+def matches_re(regex, flags=0, func=None):
+    r"""
+    A validator that raises `ValueError` if the initializer is called
+    with a string that doesn't match *regex*.
+
+    :param str regex: a regex string to match against
+    :param int flags: flags that will be passed to the underlying re function
+        (default 0)
+    :param callable func: which underlying `re` function to call (options
+        are `re.fullmatch`, `re.search`, `re.match`, default
+        is ``None`` which means either `re.fullmatch` or an emulation of
+        it on Python 2). For performance reasons, they won't be used directly
+        but on a pre-`re.compile`\ ed pattern.
+
+    .. versionadded:: 19.2.0
+    """
+    fullmatch = getattr(re, "fullmatch", None)
+    valid_funcs = (fullmatch, None, re.search, re.match)
+    if func not in valid_funcs:
+        raise ValueError(
+            "'func' must be one of %s."
+            % (
+                ", ".join(
+                    sorted(
+                        e and e.__name__ or "None" for e in set(valid_funcs)
+                    )
+                ),
+            )
+        )
+
+    pattern = re.compile(regex, flags)
+    if func is re.match:
+        match_func = pattern.match
+    elif func is re.search:
+        match_func = pattern.search
+    else:
+        if fullmatch:
+            match_func = pattern.fullmatch
+        else:
+            pattern = re.compile(r"(?:{})\Z".format(regex), flags)
+            match_func = pattern.match
+
+    return _MatchesReValidator(pattern, flags, match_func)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't.".format(
+                    name=attr.name, interface=self.interface, value=value
+                ),
+                attr,
+                self.interface,
+                value,
+            )
+
+    def __repr__(self):
+        return "<provides validator for interface {interface!r}>".format(
+            interface=self.interface
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a `TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param interface: The interface to check for.
+    :type interface: ``zope.interface.Interface``
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type `attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attrib()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return "<optional validator for {what} or None>".format(
+            what=repr(self.validator)
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or `list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attrib()
+
+    def __call__(self, inst, attr, value):
+        try:
+            in_options = value in self.options
+        except TypeError:  # e.g. `1 in "abc"`
+            in_options = False
+
+        if not in_options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})".format(
+                    name=attr.name, options=self.options, value=value
+                )
+            )
+
+    def __repr__(self):
+        return "<in_ validator with options {options!r}>".format(
+            options=self.options
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a `ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, `enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type `attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
+
+
+@attrs(repr=False, slots=False, hash=True)
+class _IsCallableValidator(object):
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not callable(value):
+            message = (
+                "'{name}' must be callable "
+                "(got {value!r} that is a {actual!r})."
+            )
+            raise NotCallableError(
+                msg=message.format(
+                    name=attr.name, value=value, actual=value.__class__
+                ),
+                value=value,
+            )
+
+    def __repr__(self):
+        return "<is_callable validator>"
+
+
+def is_callable():
+    """
+    A validator that raises a `attr.exceptions.NotCallableError` if the
+    initializer is called with a value for this particular attribute
+    that is not callable.
+
+    .. versionadded:: 19.1.0
+
+    :raises `attr.exceptions.NotCallableError`: With a human readable error
+        message containing the attribute (`attr.Attribute`) name,
+        and the value it got.
+    """
+    return _IsCallableValidator()
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _DeepIterable(object):
+    member_validator = attrib(validator=is_callable())
+    iterable_validator = attrib(
+        default=None, validator=optional(is_callable())
+    )
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if self.iterable_validator is not None:
+            self.iterable_validator(inst, attr, value)
+
+        for member in value:
+            self.member_validator(inst, attr, member)
+
+    def __repr__(self):
+        iterable_identifier = (
+            ""
+            if self.iterable_validator is None
+            else " {iterable!r}".format(iterable=self.iterable_validator)
+        )
+        return (
+            "<deep_iterable validator for{iterable_identifier}"
+            " iterables of {member!r}>"
+        ).format(
+            iterable_identifier=iterable_identifier,
+            member=self.member_validator,
+        )
+
+
+def deep_iterable(member_validator, iterable_validator=None):
+    """
+    A validator that performs deep validation of an iterable.
+
+    :param member_validator: Validator to apply to iterable members
+    :param iterable_validator: Validator to apply to iterable itself
+        (optional)
+
+    .. versionadded:: 19.1.0
+
+    :raises TypeError: if any sub-validators fail
+    """
+    return _DeepIterable(member_validator, iterable_validator)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _DeepMapping(object):
+    key_validator = attrib(validator=is_callable())
+    value_validator = attrib(validator=is_callable())
+    mapping_validator = attrib(default=None, validator=optional(is_callable()))
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if self.mapping_validator is not None:
+            self.mapping_validator(inst, attr, value)
+
+        for key in value:
+            self.key_validator(inst, attr, key)
+            self.value_validator(inst, attr, value[key])
+
+    def __repr__(self):
+        return (
+            "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
+        ).format(key=self.key_validator, value=self.value_validator)
+
+
+def deep_mapping(key_validator, value_validator, mapping_validator=None):
+    """
+    A validator that performs deep validation of a dictionary.
+
+    :param key_validator: Validator to apply to dictionary keys
+    :param value_validator: Validator to apply to dictionary values
+    :param mapping_validator: Validator to apply to top-level mapping
+        attribute (optional)
+
+    .. versionadded:: 19.1.0
+
+    :raises TypeError: if any sub-validators fail
+    """
+    return _DeepMapping(key_validator, value_validator, mapping_validator)
diff --git a/src/attr/validators.pyi b/src/attr/validators.pyi
new file mode 100644 (file)
index 0000000..9a22abb
--- /dev/null
@@ -0,0 +1,66 @@
+from typing import (
+    Container,
+    List,
+    Union,
+    TypeVar,
+    Type,
+    Any,
+    Optional,
+    Tuple,
+    Iterable,
+    Mapping,
+    Callable,
+    Match,
+    AnyStr,
+    overload,
+)
+from . import _ValidatorType
+
+_T = TypeVar("_T")
+_T1 = TypeVar("_T1")
+_T2 = TypeVar("_T2")
+_T3 = TypeVar("_T3")
+_I = TypeVar("_I", bound=Iterable)
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+_M = TypeVar("_M", bound=Mapping)
+
+# To be more precise on instance_of use some overloads.
+# If there are more than 3 items in the tuple then we fall back to Any
+@overload
+def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ...
+@overload
+def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ...
+@overload
+def instance_of(
+    type: Tuple[Type[_T1], Type[_T2]]
+) -> _ValidatorType[Union[_T1, _T2]]: ...
+@overload
+def instance_of(
+    type: Tuple[Type[_T1], Type[_T2], Type[_T3]]
+) -> _ValidatorType[Union[_T1, _T2, _T3]]: ...
+@overload
+def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ...
+def provides(interface: Any) -> _ValidatorType[Any]: ...
+def optional(
+    validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]]
+) -> _ValidatorType[Optional[_T]]: ...
+def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
+def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
+def matches_re(
+    regex: AnyStr,
+    flags: int = ...,
+    func: Optional[
+        Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]]
+    ] = ...,
+) -> _ValidatorType[AnyStr]: ...
+def deep_iterable(
+    member_validator: _ValidatorType[_T],
+    iterable_validator: Optional[_ValidatorType[_I]] = ...,
+) -> _ValidatorType[_I]: ...
+def deep_mapping(
+    key_validator: _ValidatorType[_K],
+    value_validator: _ValidatorType[_V],
+    mapping_validator: Optional[_ValidatorType[_M]] = ...,
+) -> _ValidatorType[_M]: ...
+def is_callable() -> _ValidatorType[_T]: ...
diff --git a/src/attrs.egg-info/PKG-INFO b/src/attrs.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..04ad542
--- /dev/null
@@ -0,0 +1,218 @@
+Metadata-Version: 2.1
+Name: attrs
+Version: 20.2.0
+Summary: Classes Without Boilerplate
+Home-page: https://www.attrs.org/
+Author: Hynek Schlawack
+Author-email: hs@ox.cx
+Maintainer: Hynek Schlawack
+Maintainer-email: hs@ox.cx
+License: MIT
+Project-URL: Documentation, https://www.attrs.org/
+Project-URL: Bug Tracker, https://github.com/python-attrs/attrs/issues
+Project-URL: Source Code, https://github.com/python-attrs/attrs
+Description: .. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png
+           :alt: attrs Logo
+        
+        ======================================
+        ``attrs``: Classes Without Boilerplate
+        ======================================
+        
+        .. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+           :target: https://www.attrs.org/en/stable/?badge=stable
+           :alt: Documentation Status
+        
+        .. image:: https://github.com/python-attrs/attrs/workflows/CI/badge.svg?branch=master
+           :target: https://github.com/python-attrs/attrs/actions?workflow=CI
+           :alt: CI Status
+        
+        .. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+           :target: https://codecov.io/github/python-attrs/attrs
+           :alt: Test Coverage
+        
+        .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+           :target: https://github.com/psf/black
+           :alt: Code style: black
+        
+        .. teaser-begin
+        
+        ``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+        
+        Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+        
+        .. teaser-end
+        
+        For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+        
+        .. -code-begin-
+        
+        .. code-block:: pycon
+        
+           >>> import attr
+        
+           >>> @attr.s
+           ... class SomeClass(object):
+           ...     a_number = attr.ib(default=42)
+           ...     list_of_numbers = attr.ib(factory=list)
+           ...
+           ...     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')
+        
+        
+        After *declaring* your attributes ``attrs`` gives you:
+        
+        - a concise and explicit overview of the class's attributes,
+        - a nice human-readable ``__repr__``,
+        - a complete set of comparison methods (equality and ordering),
+        - an initializer,
+        - and much more,
+        
+        *without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+        
+        On Python 3.6 and later, you can often even drop the calls to ``attr.ib()`` by using `type annotations <https://www.attrs.org/en/latest/types.html>`_.
+        
+        This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <https://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+        Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+        Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+        
+        
+        .. -getting-help-
+        
+        Getting Help
+        ============
+        
+        Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+        
+        Answering questions of your fellow developers is also great way to help the project!
+        
+        
+        .. -project-information-
+        
+        Project Information
+        ===================
+        
+        ``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+        its documentation lives at `Read the Docs <https://www.attrs.org/>`_,
+        the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+        and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+        It’s rigorously tested on Python 2.7, 3.5+, and PyPy.
+        
+        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 <https://www.attrs.org/en/latest/contributing.html>`_ to get you started!
+        
+        
+        ``attrs`` for Enterprise
+        ------------------------
+        
+        Available as part of the Tidelift Subscription.
+        
+        The maintainers of ``attrs`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
+        Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
+        `Learn more. <https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_
+        
+        
+        Release Information
+        ===================
+        
+        20.2.0 (2020-09-05)
+        -------------------
+        
+        Backward-incompatible Changes
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        - ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**.
+        
+          This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged.
+        
+          If you wish to use them together with mypy, you can simply drop `this plugin <https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py>`_ into your project.
+        
+          Feel free to provide feedback to them in the linked issue #668.
+        
+          We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled.
+          `#668 <https://github.com/python-attrs/attrs/issues/668>`_
+        
+        
+        Changes
+        ^^^^^^^
+        
+        - ``attr.define()`` et al now correct detect ``__eq__`` and ``__ne__``.
+          `#671 <https://github.com/python-attrs/attrs/issues/671>`_
+        - ``attr.define()`` et al's hybrid behavior now also works correctly when arguments are passed.
+          `#675 <https://github.com/python-attrs/attrs/issues/675>`_
+        - It's possible to define custom ``__setattr__`` methods on slotted classes again.
+          `#681 <https://github.com/python-attrs/attrs/issues/681>`_
+        - In 20.1.0 we introduced the ``inherited`` attribute on the ``attr.Attribute`` class to differentiate attributes that have been inherited and those that have been defined directly on the class.
+        
+          It has shown to be problematic to involve that attribute when comparing instances of ``attr.Attribute`` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class.
+        
+          Therefore the ``inherited`` attribute will now be ignored when hashing and comparing instances of ``attr.Attribute``.
+          `#684 <https://github.com/python-attrs/attrs/issues/684>`_
+        - ``zope.interface`` is now a "soft dependency" when running the test suite; if ``zope.interface`` is not installed when running the test suite, the interface-related tests will be automatically skipped.
+          `#685 <https://github.com/python-attrs/attrs/issues/685>`_
+        - The ergonomics of creating frozen classes using ``@define(frozen=True)`` and sub-classing frozen classes has been improved:
+          you don't have to set ``on_setattr=None`` anymore.
+          `#687 <https://github.com/python-attrs/attrs/issues/687>`_
+        
+        `Full changelog <https://www.attrs.org/en/stable/changelog.html>`_.
+        
+        Credits
+        =======
+        
+        ``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+        
+        The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+        
+        A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+        
+        It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+        Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
+        
+Keywords: class,attribute,boilerplate
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Description-Content-Type: text/x-rst
+Provides-Extra: docs
+Provides-Extra: tests_no_zope
+Provides-Extra: tests
+Provides-Extra: dev
diff --git a/src/attrs.egg-info/SOURCES.txt b/src/attrs.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..f0ce451
--- /dev/null
@@ -0,0 +1,85 @@
+.pre-commit-config.yaml
+.readthedocs.yml
+AUTHORS.rst
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+codecov.yml
+conftest.py
+pyproject.toml
+setup.py
+tox.ini
+.github/CODE_OF_CONDUCT.rst
+.github/CONTRIBUTING.rst
+.github/FUNDING.yml
+.github/PULL_REQUEST_TEMPLATE.md
+.github/SECURITY.yml
+.github/workflows/main.yml
+changelog.d/towncrier_template.rst
+docs/Makefile
+docs/api.rst
+docs/backward-compatibility.rst
+docs/changelog.rst
+docs/conf.py
+docs/contributing.rst
+docs/docutils.conf
+docs/examples.rst
+docs/extending.rst
+docs/glossary.rst
+docs/hashing.rst
+docs/how-does-it-work.rst
+docs/index.rst
+docs/init.rst
+docs/license.rst
+docs/overview.rst
+docs/python-2.rst
+docs/types.rst
+docs/why.rst
+docs/_static/attrs_logo.png
+docs/_static/attrs_logo.svg
+docs/_static/attrs_logo_white.png
+src/attr/__init__.py
+src/attr/__init__.pyi
+src/attr/_compat.py
+src/attr/_config.py
+src/attr/_funcs.py
+src/attr/_make.py
+src/attr/_next_gen.py
+src/attr/_version_info.py
+src/attr/_version_info.pyi
+src/attr/converters.py
+src/attr/converters.pyi
+src/attr/exceptions.py
+src/attr/exceptions.pyi
+src/attr/filters.py
+src/attr/filters.pyi
+src/attr/py.typed
+src/attr/setters.py
+src/attr/setters.pyi
+src/attr/validators.py
+src/attr/validators.pyi
+src/attrs.egg-info/PKG-INFO
+src/attrs.egg-info/SOURCES.txt
+src/attrs.egg-info/dependency_links.txt
+src/attrs.egg-info/not-zip-safe
+src/attrs.egg-info/requires.txt
+src/attrs.egg-info/top_level.txt
+tests/__init__.py
+tests/strategies.py
+tests/test_annotations.py
+tests/test_config.py
+tests/test_converters.py
+tests/test_dunders.py
+tests/test_filters.py
+tests/test_funcs.py
+tests/test_functional.py
+tests/test_init_subclass.py
+tests/test_make.py
+tests/test_next_gen.py
+tests/test_setattr.py
+tests/test_slots.py
+tests/test_validators.py
+tests/test_version_info.py
+tests/typing_example.py
+tests/utils.py
\ No newline at end of file
diff --git a/src/attrs.egg-info/dependency_links.txt b/src/attrs.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/attrs.egg-info/not-zip-safe b/src/attrs.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/attrs.egg-info/requires.txt b/src/attrs.egg-info/requires.txt
new file mode 100644 (file)
index 0000000..0076f49
--- /dev/null
@@ -0,0 +1,32 @@
+
+[dev]
+coverage[toml]>=5.0.2
+hypothesis
+pympler
+pytest>=4.3.0
+six
+zope.interface
+sphinx
+sphinx-rtd-theme
+zope.interface
+pre-commit
+
+[docs]
+sphinx
+sphinx-rtd-theme
+zope.interface
+
+[tests]
+coverage[toml]>=5.0.2
+hypothesis
+pympler
+pytest>=4.3.0
+six
+zope.interface
+
+[tests_no_zope]
+coverage[toml]>=5.0.2
+hypothesis
+pympler
+pytest>=4.3.0
+six
diff --git a/src/attrs.egg-info/top_level.txt b/src/attrs.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..66a062d
--- /dev/null
@@ -0,0 +1 @@
+attr
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/strategies.py b/tests/strategies.py
new file mode 100644 (file)
index 0000000..fc4ad50
--- /dev/null
@@ -0,0 +1,216 @@
+"""
+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
+
+
+optional_bool = st.one_of(st.none(), st.booleans())
+
+
+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=3)
+    )
+
+    return attr.ib(
+        default=c_attr._default,
+        validator=c_attr._validator,
+        repr=c_attr.repr,
+        eq=c_attr.eq,
+        order=c_attr.order,
+        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=3)
+
+
+@st.composite
+def simple_classes(
+    draw, slots=None, frozen=None, weakref_slot=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, weakref_slot=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, frozen, and weakref_slot classes
+    will be generated.  If `slots=True` is passed in, only slotted classes will
+    be generated, and if `slots=False` is passed in, no slotted classes will be
+    generated. The same applies to `frozen` and `weakref_slot`.
+
+    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
+    weakref_slot_flag = (
+        draw(st.booleans()) if weakref_slot is None else weakref_slot
+    )
+
+    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,
+        weakref_slot=weakref_slot_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=3
+)
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
new file mode 100644 (file)
index 0000000..f85b5d9
--- /dev/null
@@ -0,0 +1,403 @@
+"""
+Tests for PEP-526 type annotations.
+
+Python 3.6+ only.
+"""
+
+import types
+import typing
+
+import pytest
+
+import attr
+
+from attr._make import _classvar_prefixes
+from attr.exceptions import UnannotatedAttributeError
+
+
+class TestAnnotations:
+    """
+    Tests for types derived from variable annotations (PEP-526).
+    """
+
+    def test_basic_annotations(self):
+        """
+        Sets the `Attribute.type` attr from basic type annotations.
+        """
+
+        @attr.s
+        class C:
+            x: int = attr.ib()
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        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):
+        """
+        Raises ValueError if type is specified both ways.
+        """
+        with pytest.raises(ValueError) as e:
+
+            @attr.s
+            class C:
+                x: int = attr.ib(type=int)
+
+        assert (
+            "Type annotation and type argument cannot both be present",
+        ) == e.value.args
+
+    def test_typing_annotations(self):
+        """
+        Sets the `Attribute.type` attr from typing annotations.
+        """
+
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            y = attr.ib(type=typing.Optional[str])
+
+        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):
+        """
+        Annotations that aren't set to an attr.ib are ignored.
+        """
+
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            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):
+        """
+        If *auto_attribs* is True, bare annotations are collected too.
+        Defaults work and class variables are ignored.
+        """
+
+        @attr.s(auto_attribs=True, slots=slots)
+        class C:
+            cls_var: typing.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
+
+        i = C(42)
+        assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i)
+
+        attr_names = set(a.name for a in C.__attrs_attrs__)
+        assert "a" in attr_names  # just double check that the set works
+        assert "cls_var" not in attr_names
+
+        assert int == attr.fields(C).a.type
+
+        assert attr.Factory(list) == attr.fields(C).x.default
+        assert typing.List[int] == attr.fields(C).x.type
+
+        assert int == attr.fields(C).y.type
+        assert 2 == attr.fields(C).y.default
+
+        assert int == attr.fields(C).z.type
+
+        assert typing.Any == attr.fields(C).foo.type
+
+        # Class body is clean.
+        if slots is False:
+            with pytest.raises(AttributeError):
+                C.y
+
+            assert 2 == i.y
+        else:
+            assert isinstance(C.y, types.MemberDescriptorType)
+
+            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):
+        """
+        Unannotated `attr.ib`s raise an error.
+        """
+        with pytest.raises(UnannotatedAttributeError) as e:
+
+            @attr.s(slots=slots, auto_attribs=True)
+            class C:
+                v = attr.ib()
+                x: int
+                y = attr.ib()
+                z: str
+
+        assert (
+            "The following `attr.ib`s lack a type annotation: v, y.",
+        ) == e.value.args
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_subclassing(self, slots):
+        """
+        Attributes from base classes are inherited, it doesn't matter if the
+        subclass has annotations or not.
+
+        Ref #291
+        """
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: int = 1
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class B(A):
+            b: int = 2
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class C(A):
+            pass
+
+        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,
+        }
+
+    def test_keyword_only_auto_attribs(self):
+        """
+        `kw_only` propagates to attributes defined via `auto_attribs`.
+        """
+
+        @attr.s(auto_attribs=True, kw_only=True)
+        class C:
+            x: int
+            y: int
+
+        with pytest.raises(TypeError):
+            C(0, 1)
+
+        with pytest.raises(TypeError):
+            C(x=0)
+
+        c = C(x=0, y=1)
+
+        assert c.x == 0
+        assert c.y == 1
+
+    def test_base_class_variable(self):
+        """
+        Base class' class variables can be overridden with an attribute
+        without resorting to using an explicit `attr.ib()`.
+        """
+
+        class Base:
+            x: int = 42
+
+        @attr.s(auto_attribs=True)
+        class C(Base):
+            x: int
+
+        assert 1 == C(1).x
+
+    def test_removes_none_too(self):
+        """
+        Regression test for #523: make sure defaults that are set to None are
+        removed too.
+        """
+
+        @attr.s(auto_attribs=True)
+        class C:
+            x: int = 42
+            y: typing.Any = None
+
+        with pytest.raises(AttributeError):
+            C.x
+
+        with pytest.raises(AttributeError):
+            C.y
+
+    def test_non_comparable_defaults(self):
+        """
+        Regression test for #585: objects that are not directly comparable
+        (for example numpy arrays) would cause a crash when used as
+        default values of an attrs auto-attrib class.
+        """
+
+        class NonComparable:
+            def __eq__(self, other):
+                raise ValueError
+
+        @attr.s(auto_attribs=True)
+        class C:
+            x: typing.Any = NonComparable()
+
+    def test_basic_resolve(self):
+        """
+        Resolve the `Attribute.type` attr from basic type annotations.
+        Unannotated types are ignored.
+        """
+
+        @attr.s
+        class C:
+            x: "int" = attr.ib()
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        assert "int" == attr.fields(C).x.type
+        assert str is attr.fields(C).y.type
+        assert None is attr.fields(C).z.type
+
+        attr.resolve_types(C)
+
+        assert int is attr.fields(C).x.type
+        assert str is attr.fields(C).y.type
+        assert None is attr.fields(C).z.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_resolve_types_auto_attrib(self, slots):
+        """
+        Types can be resolved even when strings are involved.
+        """
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: typing.List[int]
+            b: typing.List["int"]
+            c: "typing.List[int]"
+
+        assert typing.List[int] == attr.fields(A).a.type
+        assert typing.List["int"] == attr.fields(A).b.type
+        assert "typing.List[int]" == attr.fields(A).c.type
+
+        # Note: I don't have to pass globals and locals here because
+        # int is a builtin and will be available in any scope.
+        attr.resolve_types(A)
+
+        assert typing.List[int] == attr.fields(A).a.type
+        assert typing.List[int] == attr.fields(A).b.type
+        assert typing.List[int] == attr.fields(A).c.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_resolve_types_decorator(self, slots):
+        """
+        Types can be resolved using it as a decorator.
+        """
+
+        @attr.resolve_types
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: typing.List[int]
+            b: typing.List["int"]
+            c: "typing.List[int]"
+
+        assert typing.List[int] == attr.fields(A).a.type
+        assert typing.List[int] == attr.fields(A).b.type
+        assert typing.List[int] == attr.fields(A).c.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_self_reference(self, slots):
+        """
+        References to self class using quotes can be resolved.
+        """
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: "A"
+            b: typing.Optional["A"]  # noqa: will resolve below
+
+        assert "A" == attr.fields(A).a.type
+        assert typing.Optional["A"] == attr.fields(A).b.type
+
+        attr.resolve_types(A, globals(), locals())
+
+        assert A == attr.fields(A).a.type
+        assert typing.Optional[A] == attr.fields(A).b.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_forward_reference(self, slots):
+        """
+        Forward references can be resolved.
+        """
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: typing.List["B"]  # noqa: will resolve below
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class B:
+            a: A
+
+        assert typing.List["B"] == attr.fields(A).a.type
+        assert A == attr.fields(B).a.type
+
+        attr.resolve_types(A, globals(), locals())
+
+        assert typing.List[B] == attr.fields(A).a.type
+        assert A == attr.fields(B).a.type
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644 (file)
index 0000000..287be03
--- /dev/null
@@ -0,0 +1,43 @@
+"""
+Tests for `attr._config`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from attr import _config
+
+
+class TestConfig(object):
+    def test_default(self):
+        """
+        Run validators by default.
+        """
+        assert True is _config._run_validators
+
+    def test_set_run_validators(self):
+        """
+        Sets `_run_validators`.
+        """
+        _config.set_run_validators(False)
+        assert False is _config._run_validators
+        _config.set_run_validators(True)
+        assert True is _config._run_validators
+
+    def test_get_run_validators(self):
+        """
+        Returns `_run_validators`.
+        """
+        _config._run_validators = False
+        assert _config._run_validators is _config.get_run_validators()
+        _config._run_validators = True
+        assert _config._run_validators is _config.get_run_validators()
+
+    def test_wrong_type(self):
+        """
+        Passing anything else than a boolean raises TypeError.
+        """
+        with pytest.raises(TypeError) as e:
+            _config.set_run_validators("False")
+        assert "'run' must be bool." == e.value.args[0]
diff --git a/tests/test_converters.py b/tests/test_converters.py
new file mode 100644 (file)
index 0000000..f86e07e
--- /dev/null
@@ -0,0 +1,138 @@
+"""
+Tests for `attr.converters`.
+"""
+
+from __future__ import absolute_import
+
+from distutils.util import strtobool
+
+import pytest
+
+import attr
+
+from attr import Factory, attrib
+from attr.converters import default_if_none, optional, pipe
+
+
+class TestOptional(object):
+    """
+    Tests for `optional`.
+    """
+
+    def test_success_with_type(self):
+        """
+        Wrapped converter is used as usual if value is not None.
+        """
+        c = optional(int)
+
+        assert c("42") == 42
+
+    def test_success_with_none(self):
+        """
+        Nothing happens if None.
+        """
+        c = optional(int)
+
+        assert c(None) is None
+
+    def test_fail(self):
+        """
+        Propagates the underlying conversion error when conversion fails.
+        """
+        c = optional(int)
+
+        with pytest.raises(ValueError):
+            c("not_an_int")
+
+
+class TestDefaultIfNone(object):
+    def test_missing_default(self):
+        """
+        Raises TypeError if neither default nor factory have been passed.
+        """
+        with pytest.raises(TypeError, match="Must pass either"):
+            default_if_none()
+
+    def test_too_many_defaults(self):
+        """
+        Raises TypeError if both default and factory are passed.
+        """
+        with pytest.raises(TypeError, match="but not both"):
+            default_if_none(True, lambda: 42)
+
+    def test_factory_takes_self(self):
+        """
+        Raises ValueError if passed Factory has takes_self=True.
+        """
+        with pytest.raises(ValueError, match="takes_self"):
+            default_if_none(Factory(list, takes_self=True))
+
+    @pytest.mark.parametrize("val", [1, 0, True, False, "foo", "", object()])
+    def test_not_none(self, val):
+        """
+        If a non-None value is passed, it's handed down.
+        """
+        c = default_if_none("nope")
+
+        assert val == c(val)
+
+        c = default_if_none(factory=list)
+
+        assert val == c(val)
+
+    def test_none_value(self):
+        """
+        Default values are returned when a None is passed.
+        """
+        c = default_if_none(42)
+
+        assert 42 == c(None)
+
+    def test_none_factory(self):
+        """
+        Factories are used if None is passed.
+        """
+        c = default_if_none(factory=list)
+
+        assert [] == c(None)
+
+        c = default_if_none(default=Factory(list))
+
+        assert [] == c(None)
+
+
+class TestPipe(object):
+    def test_success(self):
+        """
+        Succeeds if all wrapped converters succeed.
+        """
+        c = pipe(str, strtobool, bool)
+
+        assert True is c("True") is c(True)
+
+    def test_fail(self):
+        """
+        Fails if any wrapped converter fails.
+        """
+        c = pipe(str, strtobool)
+
+        # First wrapped converter fails:
+        with pytest.raises(ValueError):
+            c(33)
+
+        # Last wrapped converter fails:
+        with pytest.raises(ValueError):
+            c("33")
+
+    def test_sugar(self):
+        """
+        `pipe(c1, c2, c3)` and `[c1, c2, c3]` are equivalent.
+        """
+
+        @attr.s
+        class C(object):
+            a1 = attrib(default="True", converter=pipe(str, strtobool, bool))
+            a2 = attrib(default=True, converter=[str, strtobool, bool])
+
+        c = C()
+        assert True is c.a1 is c.a2
diff --git a/tests/test_dunders.py b/tests/test_dunders.py
new file mode 100644 (file)
index 0000000..2f1ebab
--- /dev/null
@@ -0,0 +1,854 @@
+"""
+Tests for dunder methods from `attrib._make`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import copy
+import pickle
+
+import pytest
+
+from hypothesis import given
+from hypothesis.strategies import booleans
+
+import attr
+
+from attr._make import (
+    NOTHING,
+    Factory,
+    _add_repr,
+    _is_slot_cls,
+    _make_init,
+    _Nothing,
+    fields,
+    make_class,
+)
+from attr.validators import instance_of
+
+from .utils import simple_attr, simple_class
+
+
+EqC = simple_class(eq=True)
+EqCSlots = simple_class(eq=True, slots=True)
+OrderC = simple_class(order=True)
+OrderCSlots = simple_class(order=True, slots=True)
+ReprC = simple_class(repr=True)
+ReprCSlots = simple_class(repr=True, slots=True)
+
+# HashC is hashable by explicit definition while HashCSlots is hashable
+# implicitly.  The "Cached" versions are the same, except with hash code
+# caching enabled
+HashC = simple_class(hash=True)
+HashCSlots = simple_class(hash=None, eq=True, frozen=True, slots=True)
+HashCCached = simple_class(hash=True, cache_hash=True)
+HashCSlotsCached = simple_class(
+    hash=None, eq=True, frozen=True, slots=True, cache_hash=True
+)
+# the cached hash code is stored slightly differently in this case
+# so it needs to be tested separately
+HashCFrozenNotSlotsCached = simple_class(
+    frozen=True, slots=False, hash=True, cache_hash=True
+)
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+
+    This function used to be part of _make.  It wasn't used anymore however
+    the tests for it are still useful to test the behavior of _make_init.
+    """
+    cls.__init__ = _make_init(
+        cls,
+        cls.__attrs_attrs__,
+        getattr(cls, "__attrs_post_init__", False),
+        frozen,
+        _is_slot_cls(cls),
+        cache_hash=False,
+        base_attr_map={},
+        is_exc=False,
+        has_global_on_setattr=False,
+    )
+    return cls
+
+
+class InitC(object):
+    __attrs_attrs__ = [simple_attr("a"), simple_attr("b")]
+
+
+InitC = _add_init(InitC, False)
+
+
+class TestEqOrder(object):
+    """
+    Tests for eq and order related methods.
+    """
+
+    @given(booleans())
+    def test_eq_ignore_attrib(self, slots):
+        """
+        If `eq` is False for an attribute, ignore that attribute.
+        """
+        C = make_class(
+            "C", {"a": attr.ib(eq=False), "b": attr.ib()}, slots=slots
+        )
+
+        assert C(1, 2) == C(2, 2)
+
+    @pytest.mark.parametrize("cls", [EqC, EqCSlots])
+    def test_equal(self, cls):
+        """
+        Equal objects are detected as equal.
+        """
+        assert cls(1, 2) == cls(1, 2)
+        assert not (cls(1, 2) != cls(1, 2))
+
+    @pytest.mark.parametrize("cls", [EqC, EqCSlots])
+    def test_unequal_same_class(self, cls):
+        """
+        Unequal objects of correct type are detected as unequal.
+        """
+        assert cls(1, 2) != cls(2, 1)
+        assert not (cls(1, 2) == cls(2, 1))
+
+    @pytest.mark.parametrize("cls", [EqC, EqCSlots])
+    def test_unequal_different_class(self, cls):
+        """
+        Unequal objects of different type are detected even if their attributes
+        match.
+        """
+
+        class NotEqC(object):
+            a = 1
+            b = 2
+
+        assert cls(1, 2) != NotEqC()
+        assert not (cls(1, 2) == NotEqC())
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_lt(self, cls):
+        """
+        __lt__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((1, 2), (2, 1)),
+            ((1, 2), (1, 3)),
+            (("a", "b"), ("b", "a")),
+        ]:
+            assert cls(*a) < cls(*b)
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_lt_unordable(self, cls):
+        """
+        __lt__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__lt__(42))
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_le(self, cls):
+        """
+        __le__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((1, 2), (2, 1)),
+            ((1, 2), (1, 3)),
+            ((1, 1), (1, 1)),
+            (("a", "b"), ("b", "a")),
+            (("a", "b"), ("a", "b")),
+        ]:
+            assert cls(*a) <= cls(*b)
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_le_unordable(self, cls):
+        """
+        __le__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__le__(42))
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_gt(self, cls):
+        """
+        __gt__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((2, 1), (1, 2)),
+            ((1, 3), (1, 2)),
+            (("b", "a"), ("a", "b")),
+        ]:
+            assert cls(*a) > cls(*b)
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_gt_unordable(self, cls):
+        """
+        __gt__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__gt__(42))
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_ge(self, cls):
+        """
+        __ge__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((2, 1), (1, 2)),
+            ((1, 3), (1, 2)),
+            ((1, 1), (1, 1)),
+            (("b", "a"), ("a", "b")),
+            (("a", "b"), ("a", "b")),
+        ]:
+            assert cls(*a) >= cls(*b)
+
+    @pytest.mark.parametrize("cls", [OrderC, OrderCSlots])
+    def test_ge_unordable(self, cls):
+        """
+        __ge__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__ge__(42))
+
+
+class TestAddRepr(object):
+    """
+    Tests for `_add_repr`.
+    """
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_repr(self, slots):
+        """
+        If `repr` is False, ignore that attribute.
+        """
+        C = make_class(
+            "C", {"a": attr.ib(repr=False), "b": attr.ib()}, slots=slots
+        )
+
+        assert "C(b=2)" == repr(C(1, 2))
+
+    @pytest.mark.parametrize("cls", [ReprC, ReprCSlots])
+    def test_repr_works(self, cls):
+        """
+        repr returns a sensible value.
+        """
+        assert "C(a=1, b=2)" == repr(cls(1, 2))
+
+    def test_custom_repr_works(self):
+        """
+        repr returns a sensible value for attributes with a custom repr
+        callable.
+        """
+
+        def custom_repr(value):
+            return "foo:" + str(value)
+
+        @attr.s
+        class C(object):
+            a = attr.ib(repr=custom_repr)
+
+        assert "C(a=foo:1)" == repr(C(1))
+
+    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.
+        """
+
+        class C(object):
+            __attrs_attrs__ = [simple_attr("_x")]
+
+        C = _add_repr(C)
+        i = C()
+        i._x = 42
+
+        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):
+        """
+        If str is True, it returns the same as repr.
+
+        This only makes sense when subclassing a class with an poor __str__
+        (like Exceptions).
+        """
+
+        @attr.s(str=add_str, slots=slots)
+        class Error(Exception):
+            x = attr.ib()
+
+        e = Error(42)
+
+        assert (str(e) == repr(e)) is add_str
+
+    def test_str_no_repr(self):
+        """
+        Raises a ValueError if repr=False and str=True.
+        """
+        with pytest.raises(ValueError) as e:
+            simple_class(repr=False, str=True)
+
+        assert (
+            "__str__ can only be generated if a __repr__ exists."
+        ) == e.value.args[0]
+
+
+# these are for use in TestAddHash.test_cache_hash_serialization
+# they need to be out here so they can be un-pickled
+@attr.attrs(hash=True, cache_hash=False)
+class HashCacheSerializationTestUncached(object):
+    foo_value = attr.ib()
+
+
+@attr.attrs(hash=True, cache_hash=True)
+class HashCacheSerializationTestCached(object):
+    foo_value = attr.ib()
+
+
+@attr.attrs(slots=True, hash=True, cache_hash=True)
+class HashCacheSerializationTestCachedSlots(object):
+    foo_value = attr.ib()
+
+
+class IncrementingHasher(object):
+    def __init__(self):
+        self.hash_value = 100
+
+    def __hash__(self):
+        rv = self.hash_value
+        self.hash_value += 1
+        return rv
+
+
+class TestAddHash(object):
+    """
+    Tests for `_add_hash`.
+    """
+
+    def test_enforces_type(self):
+        """
+        The `hash` argument to both attrs and attrib must be None, True, or
+        False.
+        """
+        exc_args = ("Invalid value for hash.  Must be True, False, or None.",)
+
+        with pytest.raises(TypeError) as e:
+            make_class("C", {}, hash=1),
+
+        assert exc_args == e.value.args
+
+        with pytest.raises(TypeError) as e:
+            make_class("C", {"a": attr.ib(hash=1)}),
+
+        assert exc_args == e.value.args
+
+    def test_enforce_no_cache_hash_without_hash(self):
+        """
+        Ensure exception is thrown if caching the hash code is requested
+        but attrs is not requested to generate `__hash__`.
+        """
+        exc_args = (
+            "Invalid value for cache_hash.  To use hash caching,"
+            " hashing must be either explicitly or implicitly "
+            "enabled.",
+        )
+        with pytest.raises(TypeError) as e:
+            make_class("C", {}, hash=False, cache_hash=True)
+        assert exc_args == e.value.args
+
+        # unhashable case
+        with pytest.raises(TypeError) as e:
+            make_class(
+                "C", {}, hash=None, eq=True, frozen=False, cache_hash=True
+            )
+        assert exc_args == e.value.args
+
+    def test_enforce_no_cached_hash_without_init(self):
+        """
+        Ensure exception is thrown if caching the hash code is requested
+        but attrs is not requested to generate `__init__`.
+        """
+        exc_args = (
+            "Invalid value for cache_hash.  To use hash caching,"
+            " init must be True.",
+        )
+        with pytest.raises(TypeError) as e:
+            make_class("C", {}, init=False, hash=True, cache_hash=True)
+        assert exc_args == e.value.args
+
+    @given(booleans(), booleans())
+    def test_hash_attribute(self, slots, cache_hash):
+        """
+        If `hash` is False on an attribute, ignore that attribute.
+        """
+        C = make_class(
+            "C",
+            {"a": attr.ib(hash=False), "b": attr.ib()},
+            slots=slots,
+            hash=True,
+            cache_hash=cache_hash,
+        )
+
+        assert hash(C(1, 2)) == hash(C(2, 2))
+
+    @given(booleans())
+    def test_hash_attribute_mirrors_eq(self, eq):
+        """
+        If `hash` is None, the hash generation mirrors `eq`.
+        """
+        C = make_class("C", {"a": attr.ib(eq=eq)}, eq=True, frozen=True)
+
+        if eq:
+            assert C(1) != C(2)
+            assert hash(C(1)) != hash(C(2))
+            assert hash(C(1)) == hash(C(1))
+        else:
+            assert C(1) == C(2)
+            assert hash(C(1)) == hash(C(2))
+
+    @given(booleans())
+    def test_hash_mirrors_eq(self, eq):
+        """
+        If `hash` is None, the hash generation mirrors `eq`.
+        """
+        C = make_class("C", {"a": attr.ib()}, eq=eq, frozen=True)
+
+        i = C(1)
+
+        assert i == i
+        assert hash(i) == hash(i)
+
+        if eq:
+            assert C(1) == C(1)
+            assert hash(C(1)) == hash(C(1))
+        else:
+            assert C(1) != C(1)
+            assert hash(C(1)) != hash(C(1))
+
+    @pytest.mark.parametrize(
+        "cls",
+        [
+            HashC,
+            HashCSlots,
+            HashCCached,
+            HashCSlotsCached,
+            HashCFrozenNotSlotsCached,
+        ],
+    )
+    def test_hash_works(self, cls):
+        """
+        __hash__ returns different hashes for different values.
+        """
+        a = cls(1, 2)
+        b = cls(1, 1)
+        assert hash(a) != hash(b)
+        # perform the test again to test the pre-cached path through
+        # __hash__ for the cached-hash versions
+        assert hash(a) != hash(b)
+
+    def test_hash_default(self):
+        """
+        Classes are not hashable by default.
+        """
+        C = make_class("C", {})
+
+        with pytest.raises(TypeError) as e:
+            hash(C())
+
+        assert e.value.args[0] in (
+            "'C' objects are unhashable",  # PyPy
+            "unhashable type: 'C'",  # CPython
+        )
+
+    def test_cache_hashing(self):
+        """
+        Ensure that hash computation if cached if and only if requested
+        """
+
+        class HashCounter:
+            """
+            A class for testing which counts how many times its hash
+            has been requested
+            """
+
+            def __init__(self):
+                self.times_hash_called = 0
+
+            def __hash__(self):
+                self.times_hash_called += 1
+                return 12345
+
+        Uncached = make_class(
+            "Uncached",
+            {"hash_counter": attr.ib(factory=HashCounter)},
+            hash=True,
+            cache_hash=False,
+        )
+        Cached = make_class(
+            "Cached",
+            {"hash_counter": attr.ib(factory=HashCounter)},
+            hash=True,
+            cache_hash=True,
+        )
+
+        uncached_instance = Uncached()
+        cached_instance = Cached()
+
+        hash(uncached_instance)
+        hash(uncached_instance)
+        hash(cached_instance)
+        hash(cached_instance)
+
+        assert 2 == uncached_instance.hash_counter.times_hash_called
+        assert 1 == cached_instance.hash_counter.times_hash_called
+
+    @pytest.mark.parametrize("cache_hash", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_copy_hash_cleared(self, cache_hash, frozen, slots):
+        """
+        Test that the default hash is recalculated after a copy operation.
+        """
+
+        kwargs = dict(frozen=frozen, slots=slots, cache_hash=cache_hash)
+
+        # Give it an explicit hash if we don't have an implicit one
+        if not frozen:
+            kwargs["hash"] = True
+
+        @attr.s(**kwargs)
+        class C(object):
+            x = attr.ib()
+
+        a = C(IncrementingHasher())
+        # Ensure that any hash cache would be calculated before copy
+        orig_hash = hash(a)
+        b = copy.deepcopy(a)
+
+        if kwargs["cache_hash"]:
+            # For cache_hash classes, this call is cached
+            assert orig_hash == hash(a)
+
+        assert orig_hash != hash(b)
+
+    @pytest.mark.parametrize(
+        "klass,cached",
+        [
+            (HashCacheSerializationTestUncached, False),
+            (HashCacheSerializationTestCached, True),
+            (HashCacheSerializationTestCachedSlots, True),
+        ],
+    )
+    def test_cache_hash_serialization_hash_cleared(self, klass, cached):
+        """
+        Tests that the hash cache is cleared on deserialization to fix
+        https://github.com/python-attrs/attrs/issues/482 .
+
+        This test is intended to guard against a stale hash code surviving
+        across serialization (which may cause problems when the hash value
+        is different in different interpreters).
+        """
+
+        obj = klass(IncrementingHasher())
+        original_hash = hash(obj)
+        obj_rt = self._roundtrip_pickle(obj)
+
+        if cached:
+            assert original_hash == hash(obj)
+
+        assert original_hash != hash(obj_rt)
+
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_copy_two_arg_reduce(self, frozen):
+        """
+        If __getstate__ returns None, the tuple returned by object.__reduce__
+        won't contain the state dictionary; this test ensures that the custom
+        __reduce__ generated when cache_hash=True works in that case.
+        """
+
+        @attr.s(frozen=frozen, cache_hash=True, hash=True)
+        class C(object):
+            x = attr.ib()
+
+            def __getstate__(self):
+                return None
+
+        # By the nature of this test it doesn't really create an object that's
+        # in a valid state - it basically does the equivalent of
+        # `object.__new__(C)`, so it doesn't make much sense to assert anything
+        # about the result of the copy. This test will just check that it
+        # doesn't raise an *error*.
+        copy.deepcopy(C(1))
+
+    def _roundtrip_pickle(self, obj):
+        pickle_str = pickle.dumps(obj)
+        return pickle.loads(pickle_str)
+
+
+class TestAddInit(object):
+    """
+    Tests for `_add_init`.
+    """
+
+    @given(booleans(), booleans())
+    def test_init(self, slots, frozen):
+        """
+        If `init` is False, ignore that attribute.
+        """
+        C = make_class(
+            "C",
+            {"a": attr.ib(init=False), "b": attr.ib()},
+            slots=slots,
+            frozen=frozen,
+        )
+        with pytest.raises(TypeError) as e:
+            C(a=1, b=2)
+
+        assert (
+            "__init__() got an unexpected keyword argument 'a'"
+            == e.value.args[0]
+        )
+
+    @given(booleans(), booleans())
+    def test_no_init_default(self, slots, frozen):
+        """
+        If `init` is False but a Factory is specified, don't allow passing that
+        argument but initialize it anyway.
+        """
+        C = make_class(
+            "C",
+            {
+                "_a": attr.ib(init=False, default=42),
+                "_b": attr.ib(init=False, default=Factory(list)),
+                "c": attr.ib(),
+            },
+            slots=slots,
+            frozen=frozen,
+        )
+        with pytest.raises(TypeError):
+            C(a=1, c=2)
+        with pytest.raises(TypeError):
+            C(b=1, c=2)
+
+        i = C(23)
+        assert (42, [], 23) == (i._a, i._b, i.c)
+
+    @given(booleans(), booleans())
+    def test_no_init_order(self, slots, frozen):
+        """
+        If an attribute is `init=False`, it's legal to come after a mandatory
+        attribute.
+        """
+        make_class(
+            "C",
+            {"a": attr.ib(default=Factory(list)), "b": attr.ib(init=False)},
+            slots=slots,
+            frozen=frozen,
+        )
+
+    def test_sets_attributes(self):
+        """
+        The attributes are initialized using the passed keywords.
+        """
+        obj = InitC(a=1, b=2)
+        assert 1 == obj.a
+        assert 2 == obj.b
+
+    def test_default(self):
+        """
+        If a default value is present, it's used as fallback.
+        """
+
+        class C(object):
+            __attrs_attrs__ = [
+                simple_attr(name="a", default=2),
+                simple_attr(name="b", default="hallo"),
+                simple_attr(name="c", default=None),
+            ]
+
+        C = _add_init(C, False)
+        i = C()
+        assert 2 == i.a
+        assert "hallo" == i.b
+        assert None is i.c
+
+    def test_factory(self):
+        """
+        If a default factory is present, it's used as fallback.
+        """
+
+        class D(object):
+            pass
+
+        class C(object):
+            __attrs_attrs__ = [
+                simple_attr(name="a", default=Factory(list)),
+                simple_attr(name="b", default=Factory(D)),
+            ]
+
+        C = _add_init(C, False)
+        i = C()
+
+        assert [] == i.a
+        assert isinstance(i.b, D)
+
+    def test_validator(self):
+        """
+        If a validator is passed, call it with the preliminary instance, the
+        Attribute, and the argument.
+        """
+
+        class VException(Exception):
+            pass
+
+        def raiser(*args):
+            raise VException(*args)
+
+        C = make_class("C", {"a": attr.ib("a", validator=raiser)})
+        with pytest.raises(VException) as e:
+            C(42)
+
+        assert (fields(C).a, 42) == e.value.args[1:]
+        assert isinstance(e.value.args[0], C)
+
+    def test_validator_slots(self):
+        """
+        If a validator is passed, call it with the preliminary instance, the
+        Attribute, and the argument.
+        """
+
+        class VException(Exception):
+            pass
+
+        def raiser(*args):
+            raise VException(*args)
+
+        C = make_class("C", {"a": attr.ib("a", validator=raiser)}, slots=True)
+        with pytest.raises(VException) as e:
+            C(42)
+
+        assert (fields(C)[0], 42) == e.value.args[1:]
+        assert isinstance(e.value.args[0], C)
+
+    @given(booleans())
+    def test_validator_others(self, slots):
+        """
+        Does not interfere when setting non-attrs attributes.
+        """
+        C = make_class(
+            "C", {"a": attr.ib("a", validator=instance_of(int))}, slots=slots
+        )
+        i = C(1)
+
+        assert 1 == i.a
+
+        if not slots:
+            i.b = "foo"
+            assert "foo" == i.b
+        else:
+            with pytest.raises(AttributeError):
+                i.b = "foo"
+
+    def test_underscores(self):
+        """
+        The argument names in `__init__` are without leading and trailing
+        underscores.
+        """
+
+        class C(object):
+            __attrs_attrs__ = [simple_attr("_private")]
+
+        C = _add_init(C, False)
+        i = C(private=42)
+        assert 42 == i._private
+
+
+class TestNothing(object):
+    """
+    Tests for `_Nothing`.
+    """
+
+    def test_copy(self):
+        """
+        __copy__ returns the same object.
+        """
+        n = _Nothing()
+        assert n is copy.copy(n)
+
+    def test_deepcopy(self):
+        """
+        __deepcopy__ returns the same object.
+        """
+        n = _Nothing()
+        assert n is copy.deepcopy(n)
+
+    def test_eq(self):
+        """
+        All instances are equal.
+        """
+        assert _Nothing() == _Nothing() == NOTHING
+        assert not (_Nothing() != _Nothing())
+        assert 1 != _Nothing()
+
+
+@attr.s(hash=True, order=True)
+class C(object):
+    pass
+
+
+# Store this class so that we recreate it.
+OriginalC = C
+
+
+@attr.s(hash=True, order=True)
+class C(object):
+    pass
+
+
+class TestFilenames(object):
+    def test_filenames(self):
+        """
+        The created dunder methods have a "consistent" filename.
+        """
+        assert (
+            OriginalC.__init__.__code__.co_filename
+            == "<attrs generated init tests.test_dunders.C>"
+        )
+        assert (
+            OriginalC.__eq__.__code__.co_filename
+            == "<attrs generated eq tests.test_dunders.C>"
+        )
+        assert (
+            OriginalC.__hash__.__code__.co_filename
+            == "<attrs generated hash tests.test_dunders.C>"
+        )
+        assert (
+            C.__init__.__code__.co_filename
+            == "<attrs generated init tests.test_dunders.C-2>"
+        )
+        assert (
+            C.__eq__.__code__.co_filename
+            == "<attrs generated eq tests.test_dunders.C-2>"
+        )
+        assert (
+            C.__hash__.__code__.co_filename
+            == "<attrs generated hash tests.test_dunders.C-2>"
+        )
diff --git a/tests/test_filters.py b/tests/test_filters.py
new file mode 100644 (file)
index 0000000..7a1a418
--- /dev/null
@@ -0,0 +1,109 @@
+"""
+Tests for `attr.filters`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+import attr
+
+from attr import fields
+from attr.filters import _split_what, exclude, include
+
+
+@attr.s
+class C(object):
+    a = attr.ib()
+    b = attr.ib()
+
+
+class TestSplitWhat(object):
+    """
+    Tests for `_split_what`.
+    """
+
+    def test_splits(self):
+        """
+        Splits correctly.
+        """
+        assert (
+            frozenset((int, str)),
+            frozenset((fields(C).a,)),
+        ) == _split_what((str, fields(C).a, int))
+
+
+class TestInclude(object):
+    """
+    Tests for `include`.
+    """
+
+    @pytest.mark.parametrize(
+        "incl,value",
+        [
+            ((int,), 42),
+            ((str,), "hello"),
+            ((str, fields(C).a), 42),
+            ((str, fields(C).b), "hello"),
+        ],
+    )
+    def test_allow(self, incl, value):
+        """
+        Return True if a class or attribute is whitelisted.
+        """
+        i = include(*incl)
+        assert i(fields(C).a, value) is True
+
+    @pytest.mark.parametrize(
+        "incl,value",
+        [
+            ((str,), 42),
+            ((int,), "hello"),
+            ((str, fields(C).b), 42),
+            ((int, fields(C).b), "hello"),
+        ],
+    )
+    def test_drop_class(self, incl, value):
+        """
+        Return False on non-whitelisted classes and attributes.
+        """
+        i = include(*incl)
+        assert i(fields(C).a, value) is False
+
+
+class TestExclude(object):
+    """
+    Tests for `exclude`.
+    """
+
+    @pytest.mark.parametrize(
+        "excl,value",
+        [
+            ((str,), 42),
+            ((int,), "hello"),
+            ((str, fields(C).b), 42),
+            ((int, fields(C).b), "hello"),
+        ],
+    )
+    def test_allow(self, excl, value):
+        """
+        Return True if class or attribute is not blacklisted.
+        """
+        e = exclude(*excl)
+        assert e(fields(C).a, value) is True
+
+    @pytest.mark.parametrize(
+        "excl,value",
+        [
+            ((int,), 42),
+            ((str,), "hello"),
+            ((str, fields(C).a), 42),
+            ((str, fields(C).b), "hello"),
+        ],
+    )
+    def test_drop_class(self, excl, value):
+        """
+        Return True on non-blacklisted classes and attributes.
+        """
+        e = exclude(*excl)
+        assert e(fields(C).a, value) is False
diff --git a/tests/test_funcs.py b/tests/test_funcs.py
new file mode 100644 (file)
index 0000000..4d99e06
--- /dev/null
@@ -0,0 +1,565 @@
+"""
+Tests for `attr._funcs`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from collections import OrderedDict
+
+import pytest
+
+from hypothesis import assume, given
+from hypothesis import strategies as st
+
+import attr
+
+from attr import asdict, assoc, astuple, evolve, fields, has
+from attr._compat import TYPE, Mapping, Sequence, ordered_dict
+from attr.exceptions import AttrsAttributeNotFoundError
+from attr.validators import instance_of
+
+from .strategies import nested_classes, simple_classes
+
+
+MAPPING_TYPES = (dict, OrderedDict)
+SEQUENCE_TYPES = (list, tuple)
+
+
+@pytest.fixture(scope="session", name="C")
+def fixture_C():
+    """
+    Return a simple but fully featured attrs class with an x and a y attribute.
+    """
+    import attr
+
+    @attr.s
+    class C(object):
+        x = attr.ib()
+        y = attr.ib()
+
+    return C
+
+
+class TestAsDict(object):
+    """
+    Tests for `asdict`.
+    """
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_shallow(self, C, dict_factory):
+        """
+        Shallow asdict returns correct dict.
+        """
+        assert {"x": 1, "y": 2} == asdict(
+            C(x=1, y=2), False, dict_factory=dict_factory
+        )
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_recurse(self, C, dict_class):
+        """
+        Deep asdict returns correct dict.
+        """
+        assert {"x": {"x": 1, "y": 2}, "y": {"x": 3, "y": 4}} == asdict(
+            C(C(1, 2), C(3, 4)), dict_factory=dict_class
+        )
+
+    def test_nested_lists(self, C):
+        """
+        Test unstructuring deeply nested lists.
+        """
+        inner = C(1, 2)
+        outer = C([[inner]], None)
+
+        assert {"x": [[{"x": 1, "y": 2}]], "y": None} == asdict(outer)
+
+    def test_nested_dicts(self, C):
+        """
+        Test unstructuring deeply nested dictionaries.
+        """
+        inner = C(1, 2)
+        outer = C({1: {2: inner}}, None)
+
+        assert {"x": {1: {2: {"x": 1, "y": 2}}}, "y": None} == asdict(outer)
+
+    @given(nested_classes, st.sampled_from(MAPPING_TYPES))
+    def test_recurse_property(self, cls, dict_class):
+        """
+        Property tests for recursive asdict.
+        """
+        obj = cls()
+        obj_dict = asdict(obj, dict_factory=dict_class)
+
+        def assert_proper_dict_class(obj, obj_dict):
+            assert isinstance(obj_dict, dict_class)
+
+            for field in fields(obj.__class__):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_dict_class(field_val, obj_dict[field.name])
+                elif isinstance(field_val, Sequence):
+                    dict_val = obj_dict[field.name]
+                    for item, item_dict in zip(field_val, dict_val):
+                        if has(item.__class__):
+                            assert_proper_dict_class(item, item_dict)
+                elif isinstance(field_val, Mapping):
+                    # This field holds a dictionary.
+                    assert isinstance(obj_dict[field.name], dict_class)
+
+                    for key, val in field_val.items():
+                        if has(val.__class__):
+                            assert_proper_dict_class(
+                                val, obj_dict[field.name][key]
+                            )
+
+        assert_proper_dict_class(obj, obj_dict)
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_filter(self, C, dict_factory):
+        """
+        Attributes that are supposed to be skipped are skipped.
+        """
+        assert {"x": {"x": 1}} == asdict(
+            C(C(1, 2), C(3, 4)),
+            filter=lambda a, v: a.name != "y",
+            dict_factory=dict_factory,
+        )
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples(self, container, C):
+        """
+        If recurse is True, also recurse into lists.
+        """
+        assert {
+            "x": 1,
+            "y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"],
+        } == asdict(C(1, container([C(2, 3), C(4, 5), "a"])))
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert {
+            "x": 1,
+            "y": container([{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"]),
+        } == asdict(
+            C(1, container([C(2, 3), C(4, 5), "a"])),
+            retain_collection_types=True,
+        )
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_dicts(self, C, dict_factory):
+        """
+        If recurse is True, also recurse into dicts.
+        """
+        res = asdict(C(1, {"a": C(4, 5)}), dict_factory=dict_factory)
+        assert {"x": 1, "y": {"a": {"x": 4, "y": 5}}} == res
+        assert isinstance(res, dict_factory)
+
+    @given(simple_classes(private_attrs=False), st.sampled_from(MAPPING_TYPES))
+    def test_roundtrip(self, cls, dict_class):
+        """
+        Test dumping to dicts and back for Hypothesis-generated classes.
+
+        Private attributes don't round-trip (the attribute name is different
+        than the initializer argument).
+        """
+        instance = cls()
+        dict_instance = asdict(instance, dict_factory=dict_class)
+
+        assert isinstance(dict_instance, dict_class)
+
+        roundtrip_instance = cls(**dict_instance)
+
+        assert instance == roundtrip_instance
+
+    @given(simple_classes())
+    def test_asdict_preserve_order(self, cls):
+        """
+        Field order should be preserved when dumping to an ordered_dict.
+        """
+        instance = cls()
+        dict_instance = asdict(instance, dict_factory=ordered_dict)
+
+        assert [a.name for a in fields(cls)] == list(dict_instance.keys())
+
+
+class TestAsTuple(object):
+    """
+    Tests for `astuple`.
+    """
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_shallow(self, C, tuple_factory):
+        """
+        Shallow astuple returns correct dict.
+        """
+        assert tuple_factory([1, 2]) == astuple(
+            C(x=1, y=2), False, tuple_factory=tuple_factory
+        )
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_recurse(self, C, tuple_factory):
+        """
+        Deep astuple returns correct tuple.
+        """
+        assert tuple_factory(
+            [tuple_factory([1, 2]), tuple_factory([3, 4])]
+        ) == astuple(C(C(1, 2), C(3, 4)), tuple_factory=tuple_factory)
+
+    @given(nested_classes, st.sampled_from(SEQUENCE_TYPES))
+    def test_recurse_property(self, cls, tuple_class):
+        """
+        Property tests for recursive astuple.
+        """
+        obj = cls()
+        obj_tuple = astuple(obj, tuple_factory=tuple_class)
+
+        def assert_proper_tuple_class(obj, obj_tuple):
+            assert isinstance(obj_tuple, tuple_class)
+            for index, field in enumerate(fields(obj.__class__)):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_tuple_class(field_val, obj_tuple[index])
+
+        assert_proper_tuple_class(obj, obj_tuple)
+
+    @given(nested_classes, st.sampled_from(SEQUENCE_TYPES))
+    def test_recurse_retain(self, cls, tuple_class):
+        """
+        Property tests for asserting collection types are retained.
+        """
+        obj = cls()
+        obj_tuple = astuple(
+            obj, tuple_factory=tuple_class, retain_collection_types=True
+        )
+
+        def assert_proper_col_class(obj, obj_tuple):
+            # Iterate over all attributes, and if they are lists or mappings
+            # in the original, assert they are the same class in the dumped.
+            for index, field in enumerate(fields(obj.__class__)):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_col_class(field_val, obj_tuple[index])
+                elif isinstance(field_val, (list, tuple)):
+                    # This field holds a sequence of something.
+                    expected_type = type(obj_tuple[index])
+                    assert type(field_val) is expected_type
+                    for obj_e, obj_tuple_e in zip(field_val, obj_tuple[index]):
+                        if has(obj_e.__class__):
+                            assert_proper_col_class(obj_e, obj_tuple_e)
+                elif isinstance(field_val, dict):
+                    orig = field_val
+                    tupled = obj_tuple[index]
+                    assert type(orig) is type(tupled)
+                    for obj_e, obj_tuple_e in zip(
+                        orig.items(), tupled.items()
+                    ):
+                        if has(obj_e[0].__class__):  # Dict key
+                            assert_proper_col_class(obj_e[0], obj_tuple_e[0])
+                        if has(obj_e[1].__class__):  # Dict value
+                            assert_proper_col_class(obj_e[1], obj_tuple_e[1])
+
+        assert_proper_col_class(obj, obj_tuple)
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_filter(self, C, tuple_factory):
+        """
+        Attributes that are supposed to be skipped are skipped.
+        """
+        assert tuple_factory([tuple_factory([1])]) == astuple(
+            C(C(1, 2), C(3, 4)),
+            filter=lambda a, v: a.name != "y",
+            tuple_factory=tuple_factory,
+        )
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples(self, container, C):
+        """
+        If recurse is True, also recurse into lists.
+        """
+        assert (1, [(2, 3), (4, 5), "a"]) == astuple(
+            C(1, container([C(2, 3), C(4, 5), "a"]))
+        )
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_dicts(self, C, tuple_factory):
+        """
+        If recurse is True, also recurse into dicts.
+        """
+        res = astuple(C(1, {"a": C(4, 5)}), tuple_factory=tuple_factory)
+        assert tuple_factory([1, {"a": tuple_factory([4, 5])}]) == res
+        assert isinstance(res, tuple_factory)
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert (1, container([(2, 3), (4, 5), "a"])) == astuple(
+            C(1, container([C(2, 3), C(4, 5), "a"])),
+            retain_collection_types=True,
+        )
+
+    @given(container=st.sampled_from(MAPPING_TYPES))
+    def test_dicts_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert (1, container({"a": (4, 5)})) == astuple(
+            C(1, container({"a": C(4, 5)})), retain_collection_types=True
+        )
+
+    @given(simple_classes(), st.sampled_from(SEQUENCE_TYPES))
+    def test_roundtrip(self, cls, tuple_class):
+        """
+        Test dumping to tuple and back for Hypothesis-generated classes.
+        """
+        instance = cls()
+        tuple_instance = astuple(instance, tuple_factory=tuple_class)
+
+        assert isinstance(tuple_instance, tuple_class)
+
+        roundtrip_instance = cls(*tuple_instance)
+
+        assert instance == roundtrip_instance
+
+
+class TestHas(object):
+    """
+    Tests for `has`.
+    """
+
+    def test_positive(self, C):
+        """
+        Returns `True` on decorated classes.
+        """
+        assert has(C)
+
+    def test_positive_empty(self):
+        """
+        Returns `True` on decorated classes even if there are no attributes.
+        """
+
+        @attr.s
+        class D(object):
+            pass
+
+        assert has(D)
+
+    def test_negative(self):
+        """
+        Returns `False` on non-decorated classes.
+        """
+        assert not has(object)
+
+
+class TestAssoc(object):
+    """
+    Tests for `assoc`.
+    """
+
+    @given(slots=st.booleans(), frozen=st.booleans())
+    def test_empty(self, slots, frozen):
+        """
+        Empty classes without changes get copied.
+        """
+
+        @attr.s(slots=slots, frozen=frozen)
+        class C(object):
+            pass
+
+        i1 = C()
+        with pytest.deprecated_call():
+            i2 = assoc(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes())
+    def test_no_changes(self, C):
+        """
+        No changes means a verbatim copy.
+        """
+        i1 = C()
+        with pytest.deprecated_call():
+            i2 = assoc(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes(), st.data())
+    def test_change(self, C, data):
+        """
+        Changes work.
+        """
+        # Take the first attribute, and change it.
+        assume(fields(C))  # Skip classes with no attributes.
+        field_names = [a.name for a in fields(C)]
+        original = C()
+        chosen_names = data.draw(st.sets(st.sampled_from(field_names)))
+        change_dict = {name: data.draw(st.integers()) for name in chosen_names}
+
+        with pytest.deprecated_call():
+            changed = assoc(original, **change_dict)
+
+        for k, v in change_dict.items():
+            assert getattr(changed, k) == v
+
+    @given(simple_classes())
+    def test_unknown(self, C):
+        """
+        Wanting to change an unknown attribute raises an
+        AttrsAttributeNotFoundError.
+        """
+        # No generated class will have a four letter attribute.
+        with pytest.raises(
+            AttrsAttributeNotFoundError
+        ) as e, pytest.deprecated_call():
+            assoc(C(), aaaa=2)
+
+        assert (
+            "aaaa is not an attrs attribute on {cls!r}.".format(cls=C),
+        ) == e.value.args
+
+    def test_frozen(self):
+        """
+        Works on frozen classes.
+        """
+
+        @attr.s(frozen=True)
+        class C(object):
+            x = attr.ib()
+            y = attr.ib()
+
+        with pytest.deprecated_call():
+            assert C(3, 2) == assoc(C(1, 2), x=3)
+
+    def test_warning(self):
+        """
+        DeprecationWarning points to the correct file.
+        """
+
+        @attr.s
+        class C(object):
+            x = attr.ib()
+
+        with pytest.warns(DeprecationWarning) as wi:
+            assert C(2) == assoc(C(1), x=2)
+
+        assert __file__ == wi.list[0].filename
+
+
+class TestEvolve(object):
+    """
+    Tests for `evolve`.
+    """
+
+    @given(slots=st.booleans(), frozen=st.booleans())
+    def test_empty(self, slots, frozen):
+        """
+        Empty classes without changes get copied.
+        """
+
+        @attr.s(slots=slots, frozen=frozen)
+        class C(object):
+            pass
+
+        i1 = C()
+        i2 = evolve(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes())
+    def test_no_changes(self, C):
+        """
+        No changes means a verbatim copy.
+        """
+        i1 = C()
+        i2 = evolve(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes(), st.data())
+    def test_change(self, C, data):
+        """
+        Changes work.
+        """
+        # Take the first attribute, and change it.
+        assume(fields(C))  # Skip classes with no attributes.
+        field_names = [a.name for a in fields(C)]
+        original = C()
+        chosen_names = data.draw(st.sets(st.sampled_from(field_names)))
+        # We pay special attention to private attributes, they should behave
+        # like in `__init__`.
+        change_dict = {
+            name.replace("_", ""): data.draw(st.integers())
+            for name in chosen_names
+        }
+        changed = evolve(original, **change_dict)
+        for name in chosen_names:
+            assert getattr(changed, name) == change_dict[name.replace("_", "")]
+
+    @given(simple_classes())
+    def test_unknown(self, C):
+        """
+        Wanting to change an unknown attribute raises an
+        AttrsAttributeNotFoundError.
+        """
+        # No generated class will have a four letter attribute.
+        with pytest.raises(TypeError) as e:
+            evolve(C(), aaaa=2)
+        expected = "__init__() got an unexpected keyword argument 'aaaa'"
+        assert (expected,) == e.value.args
+
+    def test_validator_failure(self):
+        """
+        TypeError isn't swallowed when validation fails within evolve.
+        """
+
+        @attr.s
+        class C(object):
+            a = attr.ib(validator=instance_of(int))
+
+        with pytest.raises(TypeError) as e:
+            evolve(C(a=1), a="some string")
+        m = e.value.args[0]
+
+        assert m.startswith("'a' must be <{type} 'int'>".format(type=TYPE))
+
+    def test_private(self):
+        """
+        evolve() acts as `__init__` with regards to private attributes.
+        """
+
+        @attr.s
+        class C(object):
+            _a = attr.ib()
+
+        assert evolve(C(1), a=2)._a == 2
+
+        with pytest.raises(TypeError):
+            evolve(C(1), _a=2)
+
+        with pytest.raises(TypeError):
+            evolve(C(1), a=3, _a=2)
+
+    def test_non_init_attrs(self):
+        """
+        evolve() handles `init=False` attributes.
+        """
+
+        @attr.s
+        class C(object):
+            a = attr.ib()
+            b = attr.ib(init=False, default=0)
+
+        assert evolve(C(1), a=2).a == 2
diff --git a/tests/test_functional.py b/tests/test_functional.py
new file mode 100644 (file)
index 0000000..f6a1969
--- /dev/null
@@ -0,0 +1,687 @@
+"""
+End-to-end tests.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pickle
+
+from copy import deepcopy
+
+import pytest
+import six
+
+from hypothesis import assume, given
+from hypothesis.strategies import booleans
+
+import attr
+
+from attr._compat import PY2, TYPE
+from attr._make import NOTHING, Attribute
+from attr.exceptions import FrozenInstanceError
+
+from .strategies import optional_bool
+
+
+@attr.s
+class C1(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class C1Slots(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+foo = None
+
+
+@attr.s()
+class C2(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s(slots=True)
+class C2Slots(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s
+class Base(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s(slots=True)
+class BaseSlots(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s
+class Sub(Base):
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class SubSlots(BaseSlots):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=True)
+class Frozen(object):
+    x = attr.ib()
+
+
+@attr.s
+class SubFrozen(Frozen):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=False)
+class FrozenNoSlots(object):
+    x = attr.ib()
+
+
+class Meta(type):
+    pass
+
+
+@attr.s
+@six.add_metaclass(Meta)
+class WithMeta(object):
+    pass
+
+
+@attr.s(slots=True)
+@six.add_metaclass(Meta)
+class WithMetaSlots(object):
+    pass
+
+
+FromMakeClass = attr.make_class("FromMakeClass", ["x"])
+
+
+class TestFunctional(object):
+    """
+    Functional tests.
+    """
+
+    @pytest.mark.parametrize("cls", [C2, C2Slots])
+    def test_fields(self, cls):
+        """
+        `attr.fields` works.
+        """
+        assert (
+            Attribute(
+                name="x",
+                default=foo,
+                validator=None,
+                repr=True,
+                cmp=None,
+                eq=True,
+                order=True,
+                hash=None,
+                init=True,
+                inherited=False,
+            ),
+            Attribute(
+                name="y",
+                default=attr.Factory(list),
+                validator=None,
+                repr=True,
+                cmp=None,
+                eq=True,
+                order=True,
+                hash=None,
+                init=True,
+                inherited=False,
+            ),
+        ) == attr.fields(cls)
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_asdict(self, cls):
+        """
+        `attr.asdict` works.
+        """
+        assert {"x": 1, "y": 2} == attr.asdict(cls(x=1, y=2))
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_validator(self, cls):
+        """
+        `instance_of` raises `TypeError` on type mismatch.
+        """
+        with pytest.raises(TypeError) as e:
+            cls("1", 2)
+
+        # Using C1 explicitly, since slotted classes don't support this.
+        assert (
+            "'x' must be <{type} 'int'> (got '1' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            attr.fields(C1).x,
+            int,
+            "1",
+        ) == e.value.args
+
+    @given(booleans())
+    def test_renaming(self, slots):
+        """
+        Private members are renamed but only in `__init__`.
+        """
+
+        @attr.s(slots=slots)
+        class C3(object):
+            _x = attr.ib()
+
+        assert "C3(_x=1)" == repr(C3(x=1))
+
+    @given(booleans(), booleans())
+    def test_programmatic(self, slots, frozen):
+        """
+        `attr.make_class` works.
+        """
+        PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen)
+
+        assert (
+            Attribute(
+                name="a",
+                default=NOTHING,
+                validator=None,
+                repr=True,
+                cmp=None,
+                eq=True,
+                order=True,
+                hash=None,
+                init=True,
+                inherited=False,
+            ),
+            Attribute(
+                name="b",
+                default=NOTHING,
+                validator=None,
+                repr=True,
+                cmp=None,
+                eq=True,
+                order=True,
+                hash=None,
+                init=True,
+                inherited=False,
+            ),
+        ) == attr.fields(PC)
+
+    @pytest.mark.parametrize("cls", [Sub, SubSlots])
+    def test_subclassing_with_extra_attrs(self, cls):
+        """
+        Subclassing (where the subclass has extra attrs) does what you'd hope
+        for.
+        """
+        obj = object()
+        i = cls(x=obj, y=2)
+        assert i.x is i.meth() is obj
+        assert i.y == 2
+        if cls is Sub:
+            assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i)
+        else:
+            assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize("base", [Base, BaseSlots])
+    def test_subclass_without_extra_attrs(self, base):
+        """
+        Subclassing (where the subclass does not have extra attrs) still
+        behaves the same as a subclass with extra attrs.
+        """
+
+        class Sub2(base):
+            pass
+
+        obj = object()
+        i = Sub2(x=obj)
+        assert i.x is i.meth() is obj
+        assert "Sub2(x={obj})".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize(
+        "frozen_class",
+        [
+            Frozen,  # has slots=True
+            attr.make_class("FrozenToo", ["x"], slots=False, frozen=True),
+        ],
+    )
+    def test_frozen_instance(self, frozen_class):
+        """
+        Frozen instances can't be modified (easily).
+        """
+        frozen = frozen_class(1)
+
+        with pytest.raises(FrozenInstanceError) as e:
+            frozen.x = 2
+
+        with pytest.raises(FrozenInstanceError) as e:
+            del frozen.x
+
+        assert e.value.args[0] == "can't set attribute"
+        assert 1 == frozen.x
+
+    @pytest.mark.parametrize(
+        "cls",
+        [
+            C1,
+            C1Slots,
+            C2,
+            C2Slots,
+            Base,
+            BaseSlots,
+            Sub,
+            SubSlots,
+            Frozen,
+            FrozenNoSlots,
+            FromMakeClass,
+        ],
+    )
+    @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_attributes(self, cls, protocol):
+        """
+        Pickling/un-pickling of Attribute instances works.
+        """
+        for attribute in attr.fields(cls):
+            assert attribute == pickle.loads(pickle.dumps(attribute, protocol))
+
+    @pytest.mark.parametrize(
+        "cls",
+        [
+            C1,
+            C1Slots,
+            C2,
+            C2Slots,
+            Base,
+            BaseSlots,
+            Sub,
+            SubSlots,
+            Frozen,
+            FrozenNoSlots,
+            FromMakeClass,
+        ],
+    )
+    @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_object(self, cls, protocol):
+        """
+        Pickle object serialization works on all kinds of attrs classes.
+        """
+        if len(attr.fields(cls)) == 2:
+            obj = cls(123, 456)
+        else:
+            obj = cls(123)
+
+        assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol)))
+
+    def test_subclassing_frozen_gives_frozen(self):
+        """
+        The frozen-ness of classes is inherited.  Subclasses of frozen classes
+        are also frozen and can be instantiated.
+        """
+        i = SubFrozen("foo", "bar")
+
+        assert i.x == "foo"
+        assert i.y == "bar"
+
+        with pytest.raises(FrozenInstanceError):
+            i.x = "baz"
+
+    @pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots])
+    def test_metaclass_preserved(self, cls):
+        """
+        Metaclass data is preserved.
+        """
+        assert Meta == type(cls)
+
+    def test_default_decorator(self):
+        """
+        Default decorator sets the default and the respective method gets
+        called.
+        """
+
+        @attr.s
+        class C(object):
+            x = attr.ib(default=1)
+            y = attr.ib()
+
+            @y.default
+            def compute(self):
+                return self.x + 1
+
+        assert C(1, 2) == C()
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    @pytest.mark.parametrize("weakref_slot", [True, False])
+    def test_attrib_overwrite(self, slots, frozen, weakref_slot):
+        """
+        Subclasses can overwrite attributes of their base class.
+        """
+
+        @attr.s(slots=slots, frozen=frozen, weakref_slot=weakref_slot)
+        class SubOverwrite(Base):
+            x = attr.ib(default=attr.Factory(list))
+
+        assert SubOverwrite([]) == SubOverwrite()
+
+    def test_dict_patch_class(self):
+        """
+        dict-classes are never replaced.
+        """
+
+        class C(object):
+            x = attr.ib()
+
+        C_new = attr.s(C)
+
+        assert C_new is C
+
+    def test_hash_by_id(self):
+        """
+        With dict classes, hashing by ID is active for hash=False even on
+        Python 3.  This is incorrect behavior but we have to retain it for
+        backward compatibility.
+        """
+
+        @attr.s(hash=False)
+        class HashByIDBackwardCompat(object):
+            x = attr.ib()
+
+        assert hash(HashByIDBackwardCompat(1)) != hash(
+            HashByIDBackwardCompat(1)
+        )
+
+        @attr.s(hash=False, eq=False)
+        class HashByID(object):
+            x = attr.ib()
+
+        assert hash(HashByID(1)) != hash(HashByID(1))
+
+        @attr.s(hash=True)
+        class HashByValues(object):
+            x = attr.ib()
+
+        assert hash(HashByValues(1)) == hash(HashByValues(1))
+
+    def test_handles_different_defaults(self):
+        """
+        Unhashable defaults + subclassing values work.
+        """
+
+        @attr.s
+        class Unhashable(object):
+            pass
+
+        @attr.s
+        class C(object):
+            x = attr.ib(default=Unhashable())
+
+        @attr.s
+        class D(C):
+            pass
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_hash_false_eq_false(self, slots):
+        """
+        hash=False and eq=False make a class hashable by ID.
+        """
+
+        @attr.s(hash=False, eq=False, slots=slots)
+        class C(object):
+            pass
+
+        assert hash(C()) != hash(C())
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_eq_false(self, slots):
+        """
+        eq=False makes a class hashable by ID.
+        """
+
+        @attr.s(eq=False, slots=slots)
+        class C(object):
+            pass
+
+        assert hash(C()) != hash(C())
+
+    def test_overwrite_base(self):
+        """
+        Base classes can overwrite each other and the attributes are added
+        in the order they are defined.
+        """
+
+        @attr.s
+        class C(object):
+            c = attr.ib(default=100)
+            x = attr.ib(default=1)
+            b = attr.ib(default=23)
+
+        @attr.s
+        class D(C):
+            a = attr.ib(default=42)
+            x = attr.ib(default=2)
+            d = attr.ib(default=3.14)
+
+        @attr.s
+        class E(D):
+            y = attr.ib(default=3)
+            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_weakref_slot", [True, False])
+    @pytest.mark.parametrize("sub_weakref_slot", [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_weakref_slot,
+        sub_weakref_slot,
+        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,
+            weakref_slot=base_weakref_slot,
+        )
+        class Base(object):
+            a = attr.ib(converter=int if base_converter else None)
+
+        @attr.s(
+            frozen=sub_frozen, slots=sub_slots, weakref_slot=sub_weakref_slot
+        )
+        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"
+
+    def test_tuple_class_aliasing(self):
+        """
+        itemgetter and property are legal attribute names.
+        """
+
+        @attr.s
+        class C(object):
+            property = attr.ib()
+            itemgetter = attr.ib()
+            x = attr.ib()
+
+        assert "property" == attr.fields(C).property.name
+        assert "itemgetter" == attr.fields(C).itemgetter.name
+        assert "x" == attr.fields(C).x.name
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_auto_exc(self, slots, frozen):
+        """
+        Classes with auto_exc=True have a Exception-style __str__, compare and
+        hash by id, and store the fields additionally in self.args.
+        """
+
+        @attr.s(auto_exc=True, slots=slots, frozen=frozen)
+        class FooError(Exception):
+            x = attr.ib()
+            y = attr.ib(init=False, default=42)
+            z = attr.ib(init=False)
+            a = attr.ib()
+
+        FooErrorMade = attr.make_class(
+            "FooErrorMade",
+            bases=(Exception,),
+            attrs={
+                "x": attr.ib(),
+                "y": attr.ib(init=False, default=42),
+                "z": attr.ib(init=False),
+                "a": attr.ib(),
+            },
+            auto_exc=True,
+            slots=slots,
+            frozen=frozen,
+        )
+
+        assert FooError(1, "foo") != FooError(1, "foo")
+        assert FooErrorMade(1, "foo") != FooErrorMade(1, "foo")
+
+        for cls in (FooError, FooErrorMade):
+            with pytest.raises(cls) as ei1:
+                raise cls(1, "foo")
+
+            with pytest.raises(cls) as ei2:
+                raise cls(1, "foo")
+
+            e1 = ei1.value
+            e2 = ei2.value
+
+            assert e1 is e1
+            assert e1 == e1
+            assert e2 == e2
+            assert e1 != e2
+            assert "(1, 'foo')" == str(e1) == str(e2)
+            assert (1, "foo") == e1.args == e2.args
+
+            hash(e1) == hash(e1)
+            hash(e2) == hash(e2)
+
+            if not frozen:
+                deepcopy(e1)
+                deepcopy(e2)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_auto_exc_one_attrib(self, slots, frozen):
+        """
+        Having one attribute works with auto_exc=True.
+
+        Easy to get wrong with tuple literals.
+        """
+
+        @attr.s(auto_exc=True, slots=slots, frozen=frozen)
+        class FooError(Exception):
+            x = attr.ib()
+
+        FooError(1)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_eq_only(self, slots, frozen):
+        """
+        Classes with order=False cannot be ordered.
+
+        Python 3 throws a TypeError, in Python2 we have to check for the
+        absence.
+        """
+
+        @attr.s(eq=True, order=False, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+        if not PY2:
+            possible_errors = (
+                "unorderable types: C() < C()",
+                "'<' not supported between instances of 'C' and 'C'",
+                "unorderable types: C < C",  # old PyPy 3
+            )
+
+            with pytest.raises(TypeError) as ei:
+                C(5) < C(6)
+
+            assert ei.value.args[0] in possible_errors
+        else:
+            i = C(42)
+            for m in ("lt", "le", "gt", "ge"):
+                assert None is getattr(i, "__%s__" % (m,), None)
+
+    @given(cmp=optional_bool, eq=optional_bool, order=optional_bool)
+    def test_cmp_deprecated_attribute(self, cmp, eq, order):
+        """
+        Accessing Attribute.cmp raises a deprecation warning but returns True
+        if cmp is True, or eq and order are *both* effectively True.
+        """
+        # These cases are invalid and raise a ValueError.
+        assume(cmp is None or (eq is None and order is None))
+        assume(not (eq is False and order is True))
+
+        if cmp is not None:
+            rv = cmp
+        elif eq is True or eq is None:
+            rv = order is None or order is True
+        elif cmp is None and eq is None and order is None:
+            rv = True
+        elif cmp is None or eq is None:
+            rv = False
+        else:
+            pytest.fail(
+                "Unexpected state: cmp=%r eq=%r order=%r" % (cmp, eq, order)
+            )
+
+        with pytest.deprecated_call() as dc:
+
+            @attr.s
+            class C(object):
+                x = attr.ib(cmp=cmp, eq=eq, order=order)
+
+            assert rv == attr.fields(C).x.cmp
+
+        if cmp is not None:
+            # Remove warning from creating the attribute if cmp is not None.
+            dc.pop()
+
+        (w,) = dc.list
+
+        assert (
+            "The usage of `cmp` is deprecated and will be removed on or after "
+            "2021-06-01.  Please use `eq` and `order` instead."
+            == w.message.args[0]
+        )
diff --git a/tests/test_init_subclass.py b/tests/test_init_subclass.py
new file mode 100644 (file)
index 0000000..2748655
--- /dev/null
@@ -0,0 +1,46 @@
+"""
+Tests for `__init_subclass__` related tests.
+
+Python 3.6+ only.
+"""
+
+import pytest
+
+import attr
+
+
+@pytest.mark.parametrize("slots", [True, False])
+def test_init_subclass_vanilla(slots):
+    """
+    `super().__init_subclass__` can be used if the subclass is not an attrs
+    class both with dict and slotted classes.
+    """
+
+    @attr.s(slots=slots)
+    class Base:
+        def __init_subclass__(cls, param, **kw):
+            super().__init_subclass__(**kw)
+            cls.param = param
+
+    class Vanilla(Base, param="foo"):
+        pass
+
+    assert "foo" == Vanilla().param
+
+
+def test_init_subclass_attrs():
+    """
+    `__init_subclass__` works with attrs classes as long as slots=False.
+    """
+
+    @attr.s(slots=False)
+    class Base:
+        def __init_subclass__(cls, param, **kw):
+            super().__init_subclass__(**kw)
+            cls.param = param
+
+    @attr.s
+    class Attrs(Base, param="foo"):
+        pass
+
+    assert "foo" == Attrs().param
diff --git a/tests/test_make.py b/tests/test_make.py
new file mode 100644 (file)
index 0000000..d3f4a27
--- /dev/null
@@ -0,0 +1,2208 @@
+"""
+Tests for `attr._make`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import copy
+import functools
+import gc
+import inspect
+import itertools
+import sys
+
+from operator import attrgetter
+
+import pytest
+
+from hypothesis import assume, given
+from hypothesis.strategies import booleans, integers, lists, sampled_from, text
+
+import attr
+
+from attr import _config
+from attr._compat import PY2, ordered_dict
+from attr._make import (
+    Attribute,
+    Factory,
+    _AndValidator,
+    _Attributes,
+    _ClassBuilder,
+    _CountingAttr,
+    _determine_eq_order,
+    _determine_whether_to_implement,
+    _transform_attrs,
+    and_,
+    fields,
+    fields_dict,
+    make_class,
+    validate,
+)
+from attr.exceptions import (
+    DefaultAlreadySetError,
+    NotAnAttrsClassError,
+    PythonTooOldError,
+)
+
+from .strategies import (
+    gen_attr_names,
+    list_of_attrs,
+    optional_bool,
+    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))
+
+
+class TestCountingAttr(object):
+    """
+    Tests for `attr`.
+    """
+
+    def test_returns_Attr(self):
+        """
+        Returns an instance of _CountingAttr.
+        """
+        a = attr.ib()
+
+        assert isinstance(a, _CountingAttr)
+
+    def test_validators_lists_to_wrapped_tuples(self):
+        """
+        If a list is passed as validator, it's just converted to a tuple.
+        """
+
+        def v1(_, __):
+            pass
+
+        def v2(_, __):
+            pass
+
+        a = attr.ib(validator=[v1, v2])
+
+        assert _AndValidator((v1, v2)) == a._validator
+
+    def test_validator_decorator_single(self):
+        """
+        If _CountingAttr.validator is used as a decorator and there is no
+        decorator set, the decorated method is used as the validator.
+        """
+        a = attr.ib()
+
+        @a.validator
+        def v():
+            pass
+
+        assert v == a._validator
+
+    @pytest.mark.parametrize(
+        "wrap", [lambda v: v, lambda v: [v], lambda v: and_(v)]
+    )
+    def test_validator_decorator(self, wrap):
+        """
+        If _CountingAttr.validator is used as a decorator and there is already
+        a decorator set, the decorators are composed using `and_`.
+        """
+
+        def v(_, __):
+            pass
+
+        a = attr.ib(validator=wrap(v))
+
+        @a.validator
+        def v2(self, _, __):
+            pass
+
+        assert _AndValidator((v, v2)) == a._validator
+
+    def test_default_decorator_already_set(self):
+        """
+        Raise DefaultAlreadySetError if the decorator is used after a default
+        has been set.
+        """
+        a = attr.ib(default=42)
+
+        with pytest.raises(DefaultAlreadySetError):
+
+            @a.default
+            def f(self):
+                pass
+
+    def test_default_decorator_sets(self):
+        """
+        Decorator wraps the method in a Factory with pass_self=True and sets
+        the default.
+        """
+        a = attr.ib()
+
+        @a.default
+        def f(self):
+            pass
+
+        assert Factory(f, True) == a._default
+
+
+def make_tc():
+    class TransformC(object):
+        z = attr.ib()
+        y = attr.ib()
+        x = attr.ib()
+        a = 42
+
+    return TransformC
+
+
+class TestTransformAttrs(object):
+    """
+    Tests for `_transform_attrs`.
+    """
+
+    def test_no_modifications(self):
+        """
+        Does not attach __attrs_attrs__ to the class.
+        """
+        C = make_tc()
+        _transform_attrs(C, None, False, False, True)
+
+        assert None is getattr(C, "__attrs_attrs__", None)
+
+    def test_normal(self):
+        """
+        Transforms every `_CountingAttr` and leaves others (a) be.
+        """
+        C = make_tc()
+        attrs, _, _ = _transform_attrs(C, None, False, False, True)
+
+        assert ["z", "y", "x"] == [a.name for a in attrs]
+
+    def test_empty(self):
+        """
+        No attributes works as expected.
+        """
+
+        @attr.s
+        class C(object):
+            pass
+
+        assert _Attributes(((), [], {})) == _transform_attrs(
+            C, None, False, False, True
+        )
+
+    def test_transforms_to_attribute(self):
+        """
+        All `_CountingAttr`s are transformed into `Attribute`s.
+        """
+        C = make_tc()
+        attrs, base_attrs, _ = _transform_attrs(C, None, False, False, True)
+
+        assert [] == base_attrs
+        assert 3 == len(attrs)
+        assert all(isinstance(a, Attribute) for a in attrs)
+
+    def test_conflicting_defaults(self):
+        """
+        Raises `ValueError` if attributes with defaults are followed by
+        mandatory attributes.
+        """
+
+        class C(object):
+            x = attr.ib(default=None)
+            y = attr.ib()
+
+        with pytest.raises(ValueError) as e:
+            _transform_attrs(C, None, False, False, True)
+        assert (
+            "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, "
+            "eq=True, order=True, hash=None, init=True, "
+            "metadata=mappingproxy({}), type=None, converter=None, "
+            "kw_only=False, inherited=False, on_setattr=None)",
+        ) == e.value.args
+
+    def test_kw_only(self):
+        """
+        Converts all attributes, including base class' attributes, if `kw_only`
+        is provided. Therefore, `kw_only` allows attributes with defaults to
+        preceed mandatory attributes.
+
+        Updates in the subclass *don't* affect the base class attributes.
+        """
+
+        @attr.s
+        class B(object):
+            b = attr.ib()
+
+        for b_a in B.__attrs_attrs__:
+            assert b_a.kw_only is False
+
+        class C(B):
+            x = attr.ib(default=None)
+            y = attr.ib()
+
+        attrs, base_attrs, _ = _transform_attrs(C, None, False, True, True)
+
+        assert len(attrs) == 3
+        assert len(base_attrs) == 1
+
+        for a in attrs:
+            assert a.kw_only is True
+
+        for b_a in B.__attrs_attrs__:
+            assert b_a.kw_only is False
+
+    def test_these(self):
+        """
+        If these is passed, use it and ignore body and base classes.
+        """
+
+        class Base(object):
+            z = attr.ib()
+
+        class C(Base):
+            y = attr.ib()
+
+        attrs, base_attrs, _ = _transform_attrs(
+            C, {"x": attr.ib()}, False, False, True
+        )
+
+        assert [] == base_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_old(self):
+        """
+        Old multiple inheritance attributre collection behavior is retained.
+
+        See #285
+        """
+
+        @attr.s
+        class A(object):
+            a1 = attr.ib(default="a1")
+            a2 = attr.ib(default="a2")
+
+        @attr.s
+        class B(A):
+            b1 = attr.ib(default="b1")
+            b2 = attr.ib(default="b2")
+
+        @attr.s
+        class C(B, A):
+            c1 = attr.ib(default="c1")
+            c2 = attr.ib(default="c2")
+
+        @attr.s
+        class D(A):
+            d1 = attr.ib(default="d1")
+            d2 = attr.ib(default="d2")
+
+        @attr.s
+        class E(C, D):
+            e1 = attr.ib(default="e1")
+            e2 = attr.ib(default="e2")
+
+        assert (
+            "E(a1='a1', a2='a2', b1='b1', b2='b2', c1='c1', c2='c2', d1='d1', "
+            "d2='d2', e1='e1', e2='e2')"
+        ) == repr(E())
+
+    def test_overwrite_proper_mro(self):
+        """
+        The proper MRO path works single overwrites too.
+        """
+
+        @attr.s(collect_by_mro=True)
+        class C(object):
+            x = attr.ib(default=1)
+
+        @attr.s(collect_by_mro=True)
+        class D(C):
+            x = attr.ib(default=2)
+
+        assert "D(x=2)" == repr(D())
+
+    def test_multiple_inheritance_proper_mro(self):
+        """
+        Attributes are collected according to the MRO.
+
+        See #428
+        """
+
+        @attr.s
+        class A(object):
+            a1 = attr.ib(default="a1")
+            a2 = attr.ib(default="a2")
+
+        @attr.s
+        class B(A):
+            b1 = attr.ib(default="b1")
+            b2 = attr.ib(default="b2")
+
+        @attr.s
+        class C(B, A):
+            c1 = attr.ib(default="c1")
+            c2 = attr.ib(default="c2")
+
+        @attr.s
+        class D(A):
+            d1 = attr.ib(default="d1")
+            d2 = attr.ib(default="d2")
+
+        @attr.s(collect_by_mro=True)
+        class E(C, D):
+            e1 = attr.ib(default="e1")
+            e2 = attr.ib(default="e2")
+
+        assert (
+            "E(a1='a1', a2='a2', d1='d1', d2='d2', b1='b1', b2='b2', c1='c1', "
+            "c2='c2', e1='e1', e2='e2')"
+        ) == repr(E())
+
+    def test_mro(self):
+        """
+        Attributes and methods are looked up the same way.
+
+        See #428
+        """
+
+        @attr.s
+        class A(object):
+
+            x = attr.ib(10)
+
+            def xx(self):
+                return 10
+
+        @attr.s
+        class B(A):
+            y = attr.ib(20)
+
+        @attr.s
+        class C(A):
+            x = attr.ib(50)
+
+            def xx(self):
+                return 50
+
+        @attr.s(collect_by_mro=True)
+        class D(B, C):
+            pass
+
+        d = D()
+
+        assert d.x == d.xx()
+
+    def test_inherited(self):
+        """
+        Inherited Attributes have `.inherited` True, otherwise False.
+        """
+
+        @attr.s
+        class A(object):
+            a = attr.ib()
+
+        @attr.s
+        class B(A):
+            b = attr.ib()
+
+        @attr.s
+        class C(B):
+            a = attr.ib()
+            c = attr.ib()
+
+        f = attr.fields
+
+        assert False is f(A).a.inherited
+
+        assert True is f(B).a.inherited
+        assert False is f(B).b.inherited
+
+        assert False is f(C).a.inherited
+        assert True is f(C).b.inherited
+        assert False is f(C).c.inherited
+
+
+class TestAttributes(object):
+    """
+    Tests for the `attrs`/`attr.s` class decorator.
+    """
+
+    @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3")
+    def test_catches_old_style(self):
+        """
+        Raises TypeError on old-style classes.
+        """
+        with pytest.raises(TypeError) as e:
+
+            @attr.s
+            class C:
+                pass
+
+        assert ("attrs only works with new-style classes.",) == e.value.args
+
+    def test_sets_attrs(self):
+        """
+        Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s.
+        """
+
+        @attr.s
+        class C(object):
+            x = attr.ib()
+
+        assert "x" == C.__attrs_attrs__[0].name
+        assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__)
+
+    def test_empty(self):
+        """
+        No attributes, no problems.
+        """
+
+        @attr.s
+        class C3(object):
+            pass
+
+        assert "C3()" == repr(C3())
+        assert C3() == C3()
+
+    @given(attr=attrs_st, attr_name=sampled_from(Attribute.__slots__))
+    def test_immutable(self, attr, attr_name):
+        """
+        Attribute instances are immutable.
+        """
+        with pytest.raises(AttributeError):
+            setattr(attr, attr_name, 1)
+
+    @pytest.mark.parametrize(
+        "method_name", ["__repr__", "__eq__", "__hash__", "__init__"]
+    )
+    def test_adds_all_by_default(self, method_name):
+        """
+        If no further arguments are supplied, all add_XXX functions except
+        add_hash are applied.  __hash__ is set to None.
+        """
+        # Set the method name to a sentinel and check whether it has been
+        # overwritten afterwards.
+        sentinel = object()
+
+        class C(object):
+            x = attr.ib()
+
+        setattr(C, method_name, sentinel)
+
+        C = attr.s(C)
+        meth = getattr(C, method_name)
+
+        assert sentinel != meth
+        if method_name == "__hash__":
+            assert meth is None
+
+    @pytest.mark.parametrize(
+        "arg_name, method_name",
+        [
+            ("repr", "__repr__"),
+            ("eq", "__eq__"),
+            ("order", "__le__"),
+            ("hash", "__hash__"),
+            ("init", "__init__"),
+        ],
+    )
+    def test_respects_add_arguments(self, arg_name, method_name):
+        """
+        If a certain `XXX` is `False`, `__XXX__` is not added to the class.
+        """
+        # Set the method name to a sentinel and check whether it has been
+        # overwritten afterwards.
+        sentinel = object()
+
+        am_args = {
+            "repr": True,
+            "eq": True,
+            "order": True,
+            "hash": True,
+            "init": True,
+        }
+        am_args[arg_name] = False
+        if arg_name == "eq":
+            am_args["order"] = False
+
+        class C(object):
+            x = attr.ib()
+
+        setattr(C, method_name, sentinel)
+
+        C = attr.s(**am_args)(C)
+
+        assert sentinel == getattr(C, method_name)
+
+    @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.")
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_repr_qualname(self, slots_outer, slots_inner):
+        """
+        On Python 3, the name in repr is the __qualname__.
+        """
+
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(slots=slots_inner)
+            class D(object):
+                pass
+
+        assert "C.D()" == repr(C.D())
+        assert "GC.D()" == repr(GC.D())
+
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_repr_fake_qualname(self, slots_outer, slots_inner):
+        """
+        Setting repr_ns overrides a potentially guessed namespace.
+        """
+
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(repr_ns="C", slots=slots_inner)
+            class D(object):
+                pass
+
+        assert "C.D()" == repr(C.D())
+
+    @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.")
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_name_not_overridden(self, slots_outer, slots_inner):
+        """
+        On Python 3, __name__ is different from __qualname__.
+        """
+
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(slots=slots_inner)
+            class D(object):
+                pass
+
+        assert C.D.__name__ == "D"
+        assert C.D.__qualname__ == C.__qualname__ + ".D"
+
+    @pytest.mark.parametrize("with_validation", [True, False])
+    def test_post_init(self, with_validation, monkeypatch):
+        """
+        Verify that __attrs_post_init__ gets called if defined.
+        """
+        monkeypatch.setattr(_config, "_run_validators", with_validation)
+
+        @attr.s
+        class C(object):
+            x = attr.ib()
+            y = attr.ib()
+
+            def __attrs_post_init__(self2):
+                self2.z = self2.x + self2.y
+
+        c = C(x=10, y=20)
+
+        assert 30 == getattr(c, "z", None)
+
+    def test_types(self):
+        """
+        Sets the `Attribute.type` attr from type argument.
+        """
+
+        @attr.s
+        class C(object):
+            x = attr.ib(type=int)
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        assert int is fields(C).x.type
+        assert str is fields(C).y.type
+        assert None is fields(C).z.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_clean_class(self, slots):
+        """
+        Attribute definitions do not appear on the class body after @attr.s.
+        """
+
+        @attr.s(slots=slots)
+        class C(object):
+            x = attr.ib()
+
+        x = getattr(C, "x", None)
+
+        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))
+
+    def test_inherited_does_not_affect_hashing_and_equality(self):
+        """
+        Whether or not an Attribute has been inherited doesn't affect how it's
+        hashed and compared.
+        """
+
+        @attr.s
+        class BaseClass(object):
+            x = attr.ib()
+
+        @attr.s
+        class SubClass(BaseClass):
+            pass
+
+        ba = attr.fields(BaseClass)[0]
+        sa = attr.fields(SubClass)[0]
+
+        assert ba == sa
+        assert hash(ba) == hash(sa)
+
+
+@pytest.mark.skipif(PY2, reason="keyword-only arguments are PY3-only.")
+class TestKeywordOnlyAttributes(object):
+    """
+    Tests for keyword-only attributes.
+    """
+
+    def test_adds_keyword_only_arguments(self):
+        """
+        Attributes can be added as keyword-only.
+        """
+
+        @attr.s
+        class C:
+            a = attr.ib()
+            b = attr.ib(default=2, kw_only=True)
+            c = attr.ib(kw_only=True)
+            d = attr.ib(default=attr.Factory(lambda: 4), kw_only=True)
+
+        c = C(1, c=3)
+
+        assert c.a == 1
+        assert c.b == 2
+        assert c.c == 3
+        assert c.d == 4
+
+    def test_ignores_kw_only_when_init_is_false(self):
+        """
+        Specifying ``kw_only=True`` when ``init=False`` is essentially a no-op.
+        """
+
+        @attr.s
+        class C:
+            x = attr.ib(init=False, default=0, kw_only=True)
+            y = attr.ib()
+
+        c = C(1)
+
+        assert c.x == 0
+        assert c.y == 1
+
+    def test_keyword_only_attributes_presence(self):
+        """
+        Raises `TypeError` when keyword-only arguments are
+        not specified.
+        """
+
+        @attr.s
+        class C:
+            x = attr.ib(kw_only=True)
+
+        with pytest.raises(TypeError) as e:
+            C()
+
+        assert (
+            "missing 1 required keyword-only argument: 'x'"
+        ) in e.value.args[0]
+
+    def test_keyword_only_attributes_can_come_in_any_order(self):
+        """
+        Mandatory vs non-mandatory attr order only matters when they are part
+        of the __init__ signature and when they aren't kw_only (which are
+        moved to the end and can be mandatory or non-mandatory in any order,
+        as they will be specified as keyword args anyway).
+        """
+
+        @attr.s
+        class C:
+            a = attr.ib(kw_only=True)
+            b = attr.ib(kw_only=True, default="b")
+            c = attr.ib(kw_only=True)
+            d = attr.ib()
+            e = attr.ib(default="e")
+            f = attr.ib(kw_only=True)
+            g = attr.ib(kw_only=True, default="g")
+            h = attr.ib(kw_only=True)
+            i = attr.ib(init=False)
+
+        c = C("d", a="a", c="c", f="f", h="h")
+
+        assert c.a == "a"
+        assert c.b == "b"
+        assert c.c == "c"
+        assert c.d == "d"
+        assert c.e == "e"
+        assert c.f == "f"
+        assert c.g == "g"
+        assert c.h == "h"
+
+    def test_keyword_only_attributes_allow_subclassing(self):
+        """
+        Subclass can define keyword-only attributed without defaults,
+        when the base class has attributes with defaults.
+        """
+
+        @attr.s
+        class Base:
+            x = attr.ib(default=0)
+
+        @attr.s
+        class C(Base):
+            y = attr.ib(kw_only=True)
+
+        c = C(y=1)
+
+        assert c.x == 0
+        assert c.y == 1
+
+    def test_keyword_only_class_level(self):
+        """
+        `kw_only` can be provided at the attr.s level, converting all
+        attributes to `kw_only.`
+        """
+
+        @attr.s(kw_only=True)
+        class C:
+            x = attr.ib()
+            y = attr.ib(kw_only=True)
+
+        with pytest.raises(TypeError):
+            C(0, y=1)
+
+        c = C(x=0, y=1)
+
+        assert c.x == 0
+        assert c.y == 1
+
+    def test_keyword_only_class_level_subclassing(self):
+        """
+        Subclass `kw_only` propagates to attrs inherited from the base,
+        allowing non-default following default.
+        """
+
+        @attr.s
+        class Base:
+            x = attr.ib(default=0)
+
+        @attr.s(kw_only=True)
+        class C(Base):
+            y = attr.ib()
+
+        with pytest.raises(TypeError):
+            C(1)
+
+        c = C(x=0, y=1)
+
+        assert c.x == 0
+        assert c.y == 1
+
+    def test_init_false_attribute_after_keyword_attribute(self):
+        """
+        A positional attribute cannot follow a `kw_only` attribute,
+        but an `init=False` attribute can because it won't appear
+        in `__init__`
+        """
+
+        @attr.s
+        class KwArgBeforeInitFalse:
+            kwarg = attr.ib(kw_only=True)
+            non_init_function_default = attr.ib(init=False)
+            non_init_keyword_default = attr.ib(
+                init=False, default="default-by-keyword"
+            )
+
+            @non_init_function_default.default
+            def _init_to_init(self):
+                return self.kwarg + "b"
+
+        c = KwArgBeforeInitFalse(kwarg="a")
+
+        assert c.kwarg == "a"
+        assert c.non_init_function_default == "ab"
+        assert c.non_init_keyword_default == "default-by-keyword"
+
+    def test_init_false_attribute_after_keyword_attribute_with_inheritance(
+        self,
+    ):
+        """
+        A positional attribute cannot follow a `kw_only` attribute,
+        but an `init=False` attribute can because it won't appear
+        in `__init__`. This test checks that we allow this
+        even when the `kw_only` attribute appears in a parent class
+        """
+
+        @attr.s
+        class KwArgBeforeInitFalseParent:
+            kwarg = attr.ib(kw_only=True)
+
+        @attr.s
+        class KwArgBeforeInitFalseChild(KwArgBeforeInitFalseParent):
+            non_init_function_default = attr.ib(init=False)
+            non_init_keyword_default = attr.ib(
+                init=False, default="default-by-keyword"
+            )
+
+            @non_init_function_default.default
+            def _init_to_init(self):
+                return self.kwarg + "b"
+
+        c = KwArgBeforeInitFalseChild(kwarg="a")
+
+        assert c.kwarg == "a"
+        assert c.non_init_function_default == "ab"
+        assert c.non_init_keyword_default == "default-by-keyword"
+
+
+@pytest.mark.skipif(not PY2, reason="PY2-specific keyword-only error behavior")
+class TestKeywordOnlyAttributesOnPy2(object):
+    """
+    Tests for keyword-only attribute behavior on py2.
+    """
+
+    def test_syntax_error(self):
+        """
+        Keyword-only attributes raise Syntax error on ``__init__`` generation.
+        """
+
+        with pytest.raises(PythonTooOldError):
+
+            @attr.s(kw_only=True)
+            class ClassLevel(object):
+                a = attr.ib()
+
+        with pytest.raises(PythonTooOldError):
+
+            @attr.s()
+            class AttrLevel(object):
+                a = attr.ib(kw_only=True)
+
+    def test_no_init(self):
+        """
+        Keyworld-only is a no-op, not any error, if ``init=false``.
+        """
+
+        @attr.s(kw_only=True, init=False)
+        class ClassLevel(object):
+            a = attr.ib()
+
+        @attr.s(init=False)
+        class AttrLevel(object):
+            a = attr.ib(kw_only=True)
+
+
+@attr.s
+class GC(object):
+    @attr.s
+    class D(object):
+        pass
+
+
+class TestMakeClass(object):
+    """
+    Tests for `make_class`.
+    """
+
+    @pytest.mark.parametrize("ls", [list, tuple])
+    def test_simple(self, ls):
+        """
+        Passing a list of strings creates attributes with default args.
+        """
+        C1 = make_class("C1", ls(["a", "b"]))
+
+        @attr.s
+        class C2(object):
+            a = attr.ib()
+            b = attr.ib()
+
+        assert C1.__attrs_attrs__ == C2.__attrs_attrs__
+
+    def test_dict(self):
+        """
+        Passing a dict of name: _CountingAttr creates an equivalent class.
+        """
+        C1 = make_class(
+            "C1", {"a": attr.ib(default=42), "b": attr.ib(default=None)}
+        )
+
+        @attr.s
+        class C2(object):
+            a = attr.ib(default=42)
+            b = attr.ib(default=None)
+
+        assert C1.__attrs_attrs__ == C2.__attrs_attrs__
+
+    def test_attr_args(self):
+        """
+        attributes_arguments are passed to attributes
+        """
+        C = make_class("C", ["x"], repr=False)
+
+        assert repr(C(1)).startswith("<tests.test_make.C object at 0x")
+
+    def test_catches_wrong_attrs_type(self):
+        """
+        Raise `TypeError` if an invalid type for attrs is passed.
+        """
+        with pytest.raises(TypeError) as e:
+            make_class("C", object())
+
+        assert ("attrs argument must be a dict or a list.",) == e.value.args
+
+    def test_bases(self):
+        """
+        Parameter bases default to (object,) and subclasses correctly
+        """
+
+        class D(object):
+            pass
+
+        cls = make_class("C", {})
+
+        assert cls.__mro__[-1] == object
+
+        cls = make_class("C", {}, bases=(D,))
+
+        assert D in cls.__mro__
+        assert isinstance(cls(), D)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_clean_class(self, slots):
+        """
+        Attribute definitions do not appear on the class body.
+        """
+        C = make_class("C", ["x"], slots=slots)
+
+        x = getattr(C, "x", None)
+
+        assert not isinstance(x, _CountingAttr)
+
+    def test_missing_sys_getframe(self, monkeypatch):
+        """
+        `make_class()` does not fail when `sys._getframe()` is not available.
+        """
+        monkeypatch.delattr(sys, "_getframe")
+        C = make_class("C", ["x"])
+
+        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):
+    """
+    Tests for `fields`.
+    """
+
+    @given(simple_classes())
+    def test_instance(self, C):
+        """
+        Raises `TypeError` on non-classes.
+        """
+        with pytest.raises(TypeError) as e:
+            fields(C())
+
+        assert "Passed object must be a class." == e.value.args[0]
+
+    def test_handler_non_attrs_class(self):
+        """
+        Raises `ValueError` if passed a non-``attrs`` instance.
+        """
+        with pytest.raises(NotAnAttrsClassError) as e:
+            fields(object)
+
+        assert (
+            "{o!r} is not an attrs-decorated class.".format(o=object)
+        ) == e.value.args[0]
+
+    @given(simple_classes())
+    def test_fields(self, C):
+        """
+        Returns a list of `Attribute`a.
+        """
+        assert all(isinstance(a, Attribute) for a in fields(C))
+
+    @given(simple_classes())
+    def test_fields_properties(self, C):
+        """
+        Fields returns a tuple with properties.
+        """
+        for attribute in fields(C):
+            assert getattr(fields(C), attribute.name) is attribute
+
+
+class TestFieldsDict(object):
+    """
+    Tests for `fields_dict`.
+    """
+
+    @given(simple_classes())
+    def test_instance(self, C):
+        """
+        Raises `TypeError` on non-classes.
+        """
+        with pytest.raises(TypeError) as e:
+            fields_dict(C())
+
+        assert "Passed object must be a class." == e.value.args[0]
+
+    def test_handler_non_attrs_class(self):
+        """
+        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 converter is used as the attribute's value.
+        """
+        C = make_class(
+            "C", {"x": attr.ib(converter=lambda v: v + 1), "y": attr.ib()}
+        )
+        c = C(1, 2)
+
+        assert c.x == 2
+        assert c.y == 2
+
+    @given(integers(), booleans())
+    def test_convert_property(self, val, init):
+        """
+        Property tests for attributes using converter.
+        """
+        C = make_class(
+            "C",
+            {
+                "y": attr.ib(),
+                "x": attr.ib(
+                    init=init, default=val, converter=lambda v: v + 1
+                ),
+            },
+        )
+        c = C(2)
+
+        assert c.x == val + 1
+        assert c.y == 2
+
+    @given(integers(), booleans())
+    def test_converter_factory_property(self, val, init):
+        """
+        Property tests for attributes with converter, and a factory default.
+        """
+        C = make_class(
+            "C",
+            ordered_dict(
+                [
+                    ("y", attr.ib()),
+                    (
+                        "x",
+                        attr.ib(
+                            init=init,
+                            default=Factory(lambda: val),
+                            converter=lambda v: v + 1,
+                        ),
+                    ),
+                ]
+            ),
+        )
+        c = C(2)
+
+        assert c.x == val + 1
+        assert c.y == 2
+
+    def test_factory_takes_self(self):
+        """
+        If takes_self on factories is True, self is passed.
+        """
+        C = make_class(
+            "C",
+            {
+                "x": attr.ib(
+                    default=Factory((lambda self: self), takes_self=True)
+                )
+            },
+        )
+
+        i = C()
+
+        assert i is i.x
+
+    def test_factory_hashable(self):
+        """
+        Factory is hashable.
+        """
+        assert hash(Factory(None, False)) == hash(Factory(None, False))
+
+    def test_convert_before_validate(self):
+        """
+        Validation happens after conversion.
+        """
+
+        def validator(inst, attr, val):
+            raise RuntimeError("foo")
+
+        C = make_class(
+            "C",
+            {
+                "x": attr.ib(validator=validator, converter=lambda v: 1 / 0),
+                "y": attr.ib(),
+            },
+        )
+        with pytest.raises(ZeroDivisionError):
+            C(1, 2)
+
+    def test_frozen(self):
+        """
+        Converters circumvent immutability.
+        """
+        C = make_class(
+            "C", {"x": attr.ib(converter=lambda v: int(v))}, frozen=True
+        )
+        C("1")
+
+
+class TestValidate(object):
+    """
+    Tests for `validate`.
+    """
+
+    def test_success(self):
+        """
+        If the validator succeeds, nothing gets raised.
+        """
+        C = make_class(
+            "C", {"x": attr.ib(validator=lambda *a: None), "y": attr.ib()}
+        )
+        validate(C(1, 2))
+
+    def test_propagates(self):
+        """
+        The exception of the validator is handed through.
+        """
+
+        def raiser(_, __, value):
+            if value == 42:
+                raise FloatingPointError
+
+        C = make_class("C", {"x": attr.ib(validator=raiser)})
+        i = C(1)
+        i.x = 42
+
+        with pytest.raises(FloatingPointError):
+            validate(i)
+
+    def test_run_validators(self):
+        """
+        Setting `_run_validators` to False prevents validators from running.
+        """
+        _config._run_validators = False
+        obj = object()
+
+        def raiser(_, __, ___):
+            raise Exception(obj)
+
+        C = make_class("C", {"x": attr.ib(validator=raiser)})
+        c = C(1)
+        validate(c)
+        assert 1 == c.x
+        _config._run_validators = True
+
+        with pytest.raises(Exception):
+            validate(c)
+
+        with pytest.raises(Exception) as e:
+            C(1)
+        assert (obj,) == e.value.args
+
+    def test_multiple_validators(self):
+        """
+        If a list is passed as a validator, all of its items are treated as one
+        and must pass.
+        """
+
+        def v1(_, __, value):
+            if value == 23:
+                raise TypeError("omg")
+
+        def v2(_, __, value):
+            if value == 42:
+                raise ValueError("omg")
+
+        C = make_class("C", {"x": attr.ib(validator=[v1, v2])})
+
+        validate(C(1))
+
+        with pytest.raises(TypeError) as e:
+            C(23)
+
+        assert "omg" == e.value.args[0]
+
+        with pytest.raises(ValueError) as e:
+            C(42)
+
+        assert "omg" == e.value.args[0]
+
+    def test_multiple_empty(self):
+        """
+        Empty list/tuple for validator is the same as None.
+        """
+        C1 = make_class("C", {"x": attr.ib(validator=[])})
+        C2 = make_class("C", {"x": attr.ib(validator=None)})
+
+        assert inspect.getsource(C1.__init__) == inspect.getsource(C2.__init__)
+
+
+# Hypothesis seems to cache values, so the lists of attributes come out
+# unsorted.
+sorted_lists_of_attrs = list_of_attrs.map(
+    lambda l: sorted(l, key=attrgetter("counter"))
+)
+
+
+class TestMetadata(object):
+    """
+    Tests for metadata handling.
+    """
+
+    @given(sorted_lists_of_attrs)
+    def test_metadata_present(self, list_of_attrs):
+        """
+        Assert dictionaries are copied and present.
+        """
+        C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))
+
+        for hyp_attr, class_attr in zip(list_of_attrs, fields(C)):
+            if hyp_attr.metadata is None:
+                # The default is a singleton empty dict.
+                assert class_attr.metadata is not None
+                assert len(class_attr.metadata) == 0
+            else:
+                assert hyp_attr.metadata == class_attr.metadata
+
+                # Once more, just to assert getting items and iteration.
+                for k in class_attr.metadata:
+                    assert hyp_attr.metadata[k] == class_attr.metadata[k]
+                    assert hyp_attr.metadata.get(k) == class_attr.metadata.get(
+                        k
+                    )
+
+    @given(simple_classes(), text())
+    def test_metadata_immutability(self, C, string):
+        """
+        The metadata dict should be best-effort immutable.
+        """
+        for a in fields(C):
+            with pytest.raises(TypeError):
+                a.metadata[string] = string
+            with pytest.raises(AttributeError):
+                a.metadata.update({string: string})
+            with pytest.raises(AttributeError):
+                a.metadata.clear()
+            with pytest.raises(AttributeError):
+                a.metadata.setdefault(string, string)
+
+            for k in a.metadata:
+                # For some reason, Python 3's MappingProxyType throws an
+                # IndexError for deletes on a large integer key.
+                with pytest.raises((TypeError, IndexError)):
+                    del a.metadata[k]
+                with pytest.raises(AttributeError):
+                    a.metadata.pop(k)
+            with pytest.raises(AttributeError):
+                a.metadata.popitem()
+
+    @given(lists(simple_attrs_without_metadata, min_size=2, max_size=5))
+    def test_empty_metadata_singleton(self, list_of_attrs):
+        """
+        All empty metadata attributes share the same empty metadata dict.
+        """
+        C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))
+        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):
+    """
+    Tests for `_ClassBuilder`.
+    """
+
+    def test_repr_str(self):
+        """
+        Trying to add a `__str__` without having a `__repr__` raises a
+        ValueError.
+        """
+        with pytest.raises(ValueError) as ei:
+            make_class("C", {}, repr=False, str=True)
+
+        assert (
+            "__str__ can only be generated if a __repr__ exists.",
+        ) == ei.value.args
+
+    def test_repr(self):
+        """
+        repr of builder itself makes sense.
+        """
+
+        class C(object):
+            pass
+
+        b = _ClassBuilder(
+            C,
+            None,
+            True,
+            True,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            True,
+            None,
+            False,
+        )
+
+        assert "<_ClassBuilder(cls=C)>" == repr(b)
+
+    def test_returns_self(self):
+        """
+        All methods return the builder for chaining.
+        """
+
+        class C(object):
+            x = attr.ib()
+
+        b = _ClassBuilder(
+            C,
+            None,
+            True,
+            True,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            True,
+            None,
+            False,
+        )
+
+        cls = (
+            b.add_eq()
+            .add_order()
+            .add_hash()
+            .add_init()
+            .add_repr("ns")
+            .add_str()
+            .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
+
+        @attr.s(hash=True, str=True)
+        class D(object):
+            pass
+
+        meth_C = getattr(C, meth_name)
+        meth_D = getattr(D, meth_name)
+
+        assert meth_name == meth_C.__name__ == meth_D.__name__
+        assert C.organic.__module__ == meth_C.__module__ == meth_D.__module__
+        if not PY2:
+            # This is assertion that would fail if a single __ne__ instance
+            # was reused across multiple _make_eq calls.
+            organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0]
+            assert organic_prefix + "." + meth_name == meth_C.__qualname__
+
+    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,
+            weakref_slot=True,
+            getstate_setstate=False,
+            auto_attribs=False,
+            is_exc=False,
+            kw_only=False,
+            cache_hash=False,
+            collect_by_mro=True,
+            on_setattr=None,
+            has_custom_setattr=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
+        slotted classes.
+        """
+
+        @attr.s(slots=True)
+        class C(object):
+            __weakref__ = attr.ib(
+                init=False, hash=False, repr=False, eq=False, order=False
+            )
+
+        assert C() == copy.deepcopy(C())
+
+    def test_no_references_to_original(self):
+        """
+        When subclassing a slotted class, there are no stray references to the
+        original class.
+        """
+
+        @attr.s(slots=True)
+        class C(object):
+            pass
+
+        @attr.s(slots=True)
+        class C2(C):
+            pass
+
+        # The original C2 is in a reference cycle, so force a collect:
+        gc.collect()
+
+        assert [C2] == C.__subclasses__()
+
+    def _get_copy_kwargs(include_slots=True):
+        """
+        Generate a list of compatible attr.s arguments for the `copy` tests.
+        """
+        options = ["frozen", "hash", "cache_hash"]
+
+        if include_slots:
+            options.extend(["slots", "weakref_slot"])
+
+        out_kwargs = []
+        for args in itertools.product([True, False], repeat=len(options)):
+            kwargs = dict(zip(options, args))
+
+            kwargs["hash"] = kwargs["hash"] or None
+
+            if kwargs["cache_hash"] and not (
+                kwargs["frozen"] or kwargs["hash"]
+            ):
+                continue
+
+            out_kwargs.append(kwargs)
+
+        return out_kwargs
+
+    @pytest.mark.parametrize("kwargs", _get_copy_kwargs())
+    def test_copy(self, kwargs):
+        """
+        Ensure that an attrs class can be copied successfully.
+        """
+
+        @attr.s(eq=True, **kwargs)
+        class C(object):
+            x = attr.ib()
+
+        a = C(1)
+        b = copy.deepcopy(a)
+
+        assert a == b
+
+    @pytest.mark.parametrize("kwargs", _get_copy_kwargs(include_slots=False))
+    def test_copy_custom_setstate(self, kwargs):
+        """
+        Ensure that non-slots classes respect a custom __setstate__.
+        """
+
+        @attr.s(eq=True, **kwargs)
+        class C(object):
+            x = attr.ib()
+
+            def __getstate__(self):
+                return self.__dict__
+
+            def __setstate__(self, state):
+                state["x"] *= 5
+                self.__dict__.update(state)
+
+        expected = C(25)
+        actual = copy.copy(C(5))
+
+        assert actual == expected
+
+
+class TestMakeOrder:
+    """
+    Tests for _make_order().
+    """
+
+    def test_subclasses_cannot_be_compared(self):
+        """
+        Calling comparison methods on subclasses raises a TypeError.
+
+        We use the actual operation so we get an error raised on Python 3.
+        """
+
+        @attr.s
+        class A(object):
+            a = attr.ib()
+
+        @attr.s
+        class B(A):
+            pass
+
+        a = A(42)
+        b = B(42)
+
+        assert a <= a
+        assert a >= a
+        assert not a < a
+        assert not a > a
+
+        assert (
+            NotImplemented
+            == a.__lt__(b)
+            == a.__le__(b)
+            == a.__gt__(b)
+            == a.__ge__(b)
+        )
+
+        if not PY2:
+            with pytest.raises(TypeError):
+                a <= b
+
+            with pytest.raises(TypeError):
+                a >= b
+
+            with pytest.raises(TypeError):
+                a < b
+
+            with pytest.raises(TypeError):
+                a > b
+
+
+class TestDetermineEqOrder(object):
+    def test_default(self):
+        """
+        If all are set to None, set both eq and order to the passed default.
+        """
+        assert (42, 42) == _determine_eq_order(None, None, None, 42)
+
+    @pytest.mark.parametrize("eq", [True, False])
+    def test_order_mirrors_eq_by_default(self, eq):
+        """
+        If order is None, it mirrors eq.
+        """
+        assert (eq, eq) == _determine_eq_order(None, eq, None, True)
+
+    def test_order_without_eq(self):
+        """
+        eq=False, order=True raises a meaningful ValueError.
+        """
+        with pytest.raises(
+            ValueError, match="`order` can only be True if `eq` is True too."
+        ):
+            _determine_eq_order(None, False, True, True)
+
+    @given(cmp=booleans(), eq=optional_bool, order=optional_bool)
+    def test_mix(self, cmp, eq, order):
+        """
+        If cmp is not None, eq and order must be None and vice versa.
+        """
+        assume(eq is not None or order is not None)
+
+        with pytest.raises(
+            ValueError, match="Don't mix `cmp` with `eq' and `order`."
+        ):
+            _determine_eq_order(cmp, eq, order, True)
+
+    def test_cmp_deprecated(self):
+        """
+        Passing a cmp that is not None raises a DeprecationWarning.
+        """
+        with pytest.deprecated_call() as dc:
+
+            @attr.s(cmp=True)
+            class C(object):
+                pass
+
+        (w,) = dc.list
+
+        assert (
+            "The usage of `cmp` is deprecated and will be removed on or after "
+            "2021-06-01.  Please use `eq` and `order` instead."
+            == w.message.args[0]
+        )
+
+
+class TestDocs:
+    @pytest.mark.parametrize(
+        "meth_name",
+        [
+            "__init__",
+            "__repr__",
+            "__eq__",
+            "__ne__",
+            "__lt__",
+            "__le__",
+            "__gt__",
+            "__ge__",
+        ],
+    )
+    def test_docs(self, meth_name):
+        """
+        Tests the presence and correctness of the documentation
+        for the generated methods
+        """
+
+        @attr.s
+        class A(object):
+            pass
+
+        if hasattr(A, "__qualname__"):
+            method = getattr(A, meth_name)
+            expected = "Method generated by attrs for class {}.".format(
+                A.__qualname__
+            )
+            assert expected == method.__doc__
+
+
+@pytest.mark.skipif(not PY2, reason="Needs to be only caught on Python 2.")
+def test_auto_detect_raises_on_py2():
+    """
+    Trying to pass auto_detect=True to attr.s raises PythonTooOldError.
+    """
+    with pytest.raises(PythonTooOldError):
+        attr.s(auto_detect=True)
+
+
+class BareC(object):
+    pass
+
+
+class BareSlottedC(object):
+    __slots__ = ()
+
+
+@pytest.mark.skipif(PY2, reason="Auto-detection is Python 3-only.")
+class TestAutoDetect:
+    @pytest.mark.parametrize("C", (BareC, BareSlottedC))
+    def test_determine_detects_non_presence_correctly(self, C):
+        """
+        On an empty class, nothing should be detected.
+        """
+        assert True is _determine_whether_to_implement(
+            C, None, True, ("__init__",)
+        )
+        assert True is _determine_whether_to_implement(
+            C, None, True, ("__repr__",)
+        )
+        assert True is _determine_whether_to_implement(
+            C, None, True, ("__eq__", "__ne__")
+        )
+        assert True is _determine_whether_to_implement(
+            C, None, True, ("__le__", "__lt__", "__ge__", "__gt__")
+        )
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_make_all_by_default(self, slots, frozen):
+        """
+        If nothing is there to be detected, imply init=True, repr=True,
+        hash=None, eq=True, order=True.
+        """
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+        i = C(1)
+        o = object()
+
+        assert i.__init__ is not o.__init__
+        assert i.__repr__ is not o.__repr__
+        assert i.__eq__ is not o.__eq__
+        assert i.__ne__ is not o.__ne__
+        assert i.__le__ is not o.__le__
+        assert i.__lt__ is not o.__lt__
+        assert i.__ge__ is not o.__ge__
+        assert i.__gt__ is not o.__gt__
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_detect_auto_init(self, slots, frozen):
+        """
+        If auto_detect=True and an __init__ exists, don't write one.
+        """
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class CI(object):
+            x = attr.ib()
+
+            def __init__(self):
+                object.__setattr__(self, "x", 42)
+
+        assert 42 == CI().x
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_detect_auto_repr(self, slots, frozen):
+        """
+        If auto_detect=True and an __repr__ exists, don't write one.
+        """
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __repr__(self):
+                return "hi"
+
+        assert "hi" == repr(C(42))
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_detect_auto_hash(self, slots, frozen):
+        """
+        If auto_detect=True and an __hash__ exists, don't write one.
+        """
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __hash__(self):
+                return 0xC0FFEE
+
+        assert 0xC0FFEE == hash(C(42))
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_detect_auto_eq(self, slots, frozen):
+        """
+        If auto_detect=True and an __eq__ or an __ne__, exist, don't write one.
+        """
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __eq__(self, o):
+                raise ValueError("worked")
+
+        with pytest.raises(ValueError, match="worked"):
+            C(1) == C(1)
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class D(object):
+            x = attr.ib()
+
+            def __ne__(self, o):
+                raise ValueError("worked")
+
+        with pytest.raises(ValueError, match="worked"):
+            D(1) != D(1)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_detect_auto_order(self, slots, frozen):
+        """
+        If auto_detect=True and an __ge__, __gt__, __le__, or and __lt__ exist,
+        don't write one.
+
+        It's surprisingly difficult to test this programmatically, so we do it
+        by hand.
+        """
+
+        def assert_not_set(cls, ex, meth_name):
+            __tracebackhide__ = True
+
+            a = getattr(cls, meth_name)
+            if meth_name == ex:
+                assert a == 42
+            else:
+                assert a is getattr(object, meth_name)
+
+        def assert_none_set(cls, ex):
+            __tracebackhide__ = True
+
+            for m in ("le", "lt", "ge", "gt"):
+                assert_not_set(cls, ex, "__" + m + "__")
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class LE(object):
+            __le__ = 42
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class LT(object):
+            __lt__ = 42
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class GE(object):
+            __ge__ = 42
+
+        @attr.s(auto_detect=True, slots=slots, frozen=frozen)
+        class GT(object):
+            __gt__ = 42
+
+        assert_none_set(LE, "__le__")
+        assert_none_set(LT, "__lt__")
+        assert_none_set(GE, "__ge__")
+        assert_none_set(GT, "__gt__")
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_override_init(self, slots, frozen):
+        """
+        If init=True is passed, ignore __init__.
+        """
+
+        @attr.s(init=True, auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __init__(self):
+                pytest.fail("should not be called")
+
+        assert C(1) == C(1)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_override_repr(self, slots, frozen):
+        """
+        If repr=True is passed, ignore __repr__.
+        """
+
+        @attr.s(repr=True, auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __repr__(self):
+                pytest.fail("should not be called")
+
+        assert "C(x=1)" == repr(C(1))
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_override_hash(self, slots, frozen):
+        """
+        If hash=True is passed, ignore __hash__.
+        """
+
+        @attr.s(hash=True, auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __hash__(self):
+                pytest.fail("should not be called")
+
+        assert hash(C(1))
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_override_eq(self, slots, frozen):
+        """
+        If eq=True is passed, ignore __eq__ and __ne__.
+        """
+
+        @attr.s(eq=True, auto_detect=True, slots=slots, frozen=frozen)
+        class C(object):
+            x = attr.ib()
+
+            def __eq__(self, o):
+                pytest.fail("should not be called")
+
+            def __ne__(self, o):
+                pytest.fail("should not be called")
+
+        assert C(1) == C(1)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    @pytest.mark.parametrize(
+        "eq,order,cmp",
+        [
+            (True, None, None),
+            (True, True, None),
+            (None, True, None),
+            (None, None, True),
+        ],
+    )
+    def test_override_order(self, slots, frozen, eq, order, cmp, recwarn):
+        """
+        If order=True is passed, ignore __le__, __lt__, __gt__, __ge__.
+
+        eq=True and cmp=True both imply order=True so test it too.
+        """
+
+        def meth(self, o):
+            pytest.fail("should not be called")
+
+        @attr.s(
+            cmp=cmp,
+            order=order,
+            eq=eq,
+            auto_detect=True,
+            slots=slots,
+            frozen=frozen,
+        )
+        class C(object):
+            x = attr.ib()
+            __le__ = __lt__ = __gt__ = __ge__ = meth
+
+        assert C(1) < C(2)
+        assert C(1) <= C(2)
+        assert C(2) > C(1)
+        assert C(2) >= C(1)
+
+        if cmp:
+            assert 1 == len(recwarn.list)
+        else:
+            assert 0 == len(recwarn.list)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("first", [True, False])
+    def test_total_ordering(self, slots, first):
+        """
+        functools.total_ordering works as expected if an order method and an eq
+        method are detected.
+
+        Ensure the order doesn't matter.
+        """
+
+        class C(object):
+            x = attr.ib()
+            own_eq_called = attr.ib(default=False)
+            own_le_called = attr.ib(default=False)
+
+            def __eq__(self, o):
+                self.own_eq_called = True
+                return self.x == o.x
+
+            def __le__(self, o):
+                self.own_le_called = True
+                return self.x <= o.x
+
+        if first:
+            C = functools.total_ordering(
+                attr.s(auto_detect=True, slots=slots)(C)
+            )
+        else:
+            C = attr.s(auto_detect=True, slots=slots)(
+                functools.total_ordering(C)
+            )
+
+        c1, c2 = C(1), C(2)
+
+        assert c1 < c2
+        assert c1.own_le_called
+
+        c1, c2 = C(1), C(2)
+
+        assert c2 > c1
+        assert c2.own_le_called
+
+        c1, c2 = C(1), C(2)
+
+        assert c2 != c1
+        assert c1 == c1
+
+        assert c1.own_eq_called
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_detects_setstate_getstate(self, slots):
+        """
+        __getstate__ and __setstate__ are not overwritten if either is present.
+        """
+
+        @attr.s(slots=slots, auto_detect=True)
+        class C(object):
+            def __getstate__(self):
+                return ("hi",)
+
+        assert None is getattr(C(), "__setstate__", None)
+
+        @attr.s(slots=slots, auto_detect=True)
+        class C(object):
+            called = attr.ib(False)
+
+            def __setstate__(self, state):
+                self.called = True
+
+        i = C()
+
+        assert False is i.called
+
+        i.__setstate__(())
+
+        assert True is i.called
+        assert None is getattr(C(), "__getstate__", None)
diff --git a/tests/test_next_gen.py b/tests/test_next_gen.py
new file mode 100644 (file)
index 0000000..941ec0a
--- /dev/null
@@ -0,0 +1,240 @@
+"""
+Python 3-only integration tests for provisional next generation APIs.
+"""
+
+import re
+
+import pytest
+
+import attr
+
+
+@attr.define
+class C:
+    x: str
+    y: int
+
+
+class TestNextGen:
+    def test_simple(self):
+        """
+        Instantiation works.
+        """
+        C("1", 2)
+
+    def test_no_slots(self):
+        """
+        slots can be deactivated.
+        """
+
+        @attr.define(slots=False)
+        class NoSlots:
+            x: int
+
+        ns = NoSlots(1)
+
+        assert {"x": 1} == getattr(ns, "__dict__")
+
+    def test_validates(self):
+        """
+        Validators at __init__ and __setattr__ work.
+        """
+
+        @attr.define
+        class Validated:
+            x: int = attr.field(validator=attr.validators.instance_of(int))
+
+        v = Validated(1)
+
+        with pytest.raises(TypeError):
+            Validated(None)
+
+        with pytest.raises(TypeError):
+            v.x = "1"
+
+    def test_no_order(self):
+        """
+        Order is off by default but can be added.
+        """
+        with pytest.raises(TypeError):
+            C("1", 2) < C("2", 3)
+
+        @attr.define(order=True)
+        class Ordered:
+            x: int
+
+        assert Ordered(1) < Ordered(2)
+
+    def test_override_auto_attribs_true(self):
+        """
+        Don't guess if auto_attrib is set explicitly.
+
+        Having an unannotated attr.ib/attr.field fails.
+        """
+        with pytest.raises(attr.exceptions.UnannotatedAttributeError):
+
+            @attr.define(auto_attribs=True)
+            class ThisFails:
+                x = attr.field()
+                y: int
+
+    def test_override_auto_attribs_false(self):
+        """
+        Don't guess if auto_attrib is set explicitly.
+
+        Annotated fields that don't carry an attr.ib are ignored.
+        """
+
+        @attr.define(auto_attribs=False)
+        class NoFields:
+            x: int
+            y: int
+
+        assert NoFields() == NoFields()
+
+    def test_auto_attribs_detect(self):
+        """
+        define correctly detects if a class lacks type annotations.
+        """
+
+        @attr.define
+        class OldSchool:
+            x = attr.field()
+
+        assert OldSchool(1) == OldSchool(1)
+
+        # Test with maybe_cls = None
+        @attr.define()
+        class OldSchool2:
+            x = attr.field()
+
+        assert OldSchool2(1) == OldSchool2(1)
+
+    def test_auto_attribs_detect_annotations(self):
+        """
+        define correctly detects if a class has type annotations.
+        """
+
+        @attr.define
+        class NewSchool:
+            x: int
+
+        assert NewSchool(1) == NewSchool(1)
+
+        # Test with maybe_cls = None
+        @attr.define()
+        class NewSchool2:
+            x: int
+
+        assert NewSchool2(1) == NewSchool2(1)
+
+    def test_exception(self):
+        """
+        Exceptions are detected and correctly handled.
+        """
+
+        @attr.define
+        class E(Exception):
+            msg: str
+            other: int
+
+        with pytest.raises(E) as ei:
+            raise E("yolo", 42)
+
+        e = ei.value
+
+        assert ("yolo", 42) == e.args
+        assert "yolo" == e.msg
+        assert 42 == e.other
+
+    def test_frozen(self):
+        """
+        attr.frozen freezes classes.
+        """
+
+        @attr.frozen
+        class F:
+            x: str
+
+        f = F(1)
+
+        with pytest.raises(attr.exceptions.FrozenInstanceError):
+            f.x = 2
+
+    def test_auto_detect_eq(self):
+        """
+        auto_detect=True works for eq.
+
+        Regression test for #670.
+        """
+
+        @attr.define
+        class C:
+            def __eq__(self, o):
+                raise ValueError()
+
+        with pytest.raises(ValueError):
+            C() == C()
+
+    def test_subclass_frozen(self):
+        """
+        It's possible to subclass an `attr.frozen` class and the frozen-ness is
+        inherited.
+        """
+
+        @attr.frozen
+        class A:
+            a: int
+
+        @attr.frozen
+        class B(A):
+            b: int
+
+        @attr.define(on_setattr=attr.setters.NO_OP)
+        class C(B):
+            c: int
+
+        assert B(1, 2) == B(1, 2)
+        assert C(1, 2, 3) == C(1, 2, 3)
+
+        with pytest.raises(attr.exceptions.FrozenInstanceError):
+            A(1).a = 1
+
+        with pytest.raises(attr.exceptions.FrozenInstanceError):
+            B(1, 2).a = 1
+
+        with pytest.raises(attr.exceptions.FrozenInstanceError):
+            B(1, 2).b = 2
+
+        with pytest.raises(attr.exceptions.FrozenInstanceError):
+            C(1, 2, 3).c = 3
+
+    def test_catches_frozen_on_setattr(self):
+        """
+        Passing frozen=True and on_setattr hooks is caught, even if the
+        immutability is inherited.
+        """
+
+        @attr.define(frozen=True)
+        class A:
+            pass
+
+        with pytest.raises(
+            ValueError, match="Frozen classes can't use on_setattr."
+        ):
+
+            @attr.define(frozen=True, on_setattr=attr.setters.validate)
+            class B:
+                pass
+
+        with pytest.raises(
+            ValueError,
+            match=re.escape(
+                "Frozen classes can't use on_setattr "
+                "(frozen-ness was inherited)."
+            ),
+        ):
+
+            @attr.define(on_setattr=attr.setters.validate)
+            class C(A):
+                pass
diff --git a/tests/test_setattr.py b/tests/test_setattr.py
new file mode 100644 (file)
index 0000000..8e55da2
--- /dev/null
@@ -0,0 +1,435 @@
+from __future__ import absolute_import, division, print_function
+
+import pickle
+
+import pytest
+
+import attr
+
+from attr import setters
+from attr._compat import PY2
+from attr.exceptions import FrozenAttributeError
+from attr.validators import instance_of, matches_re
+
+
+@attr.s(frozen=True)
+class Frozen(object):
+    x = attr.ib()
+
+
+@attr.s
+class WithOnSetAttrHook(object):
+    x = attr.ib(on_setattr=lambda *args: None)
+
+
+class TestSetAttr(object):
+    def test_change(self):
+        """
+        The return value of a hook overwrites the value. But they are not run
+        on __init__.
+        """
+
+        def hook(*a, **kw):
+            return "hooked!"
+
+        @attr.s
+        class Hooked(object):
+            x = attr.ib(on_setattr=hook)
+            y = attr.ib()
+
+        h = Hooked("x", "y")
+
+        assert "x" == h.x
+        assert "y" == h.y
+
+        h.x = "xxx"
+        h.y = "yyy"
+
+        assert "yyy" == h.y
+        assert "hooked!" == h.x
+
+    def test_frozen_attribute(self):
+        """
+        Frozen attributes raise FrozenAttributeError, others are not affected.
+        """
+
+        @attr.s
+        class PartiallyFrozen(object):
+            x = attr.ib(on_setattr=setters.frozen)
+            y = attr.ib()
+
+        pf = PartiallyFrozen("x", "y")
+
+        pf.y = "yyy"
+
+        assert "yyy" == pf.y
+
+        with pytest.raises(FrozenAttributeError):
+            pf.x = "xxx"
+
+        assert "x" == pf.x
+
+    @pytest.mark.parametrize(
+        "on_setattr",
+        [setters.validate, [setters.validate], setters.pipe(setters.validate)],
+    )
+    def test_validator(self, on_setattr):
+        """
+        Validators are run and they don't alter the value.
+        """
+
+        @attr.s(on_setattr=on_setattr)
+        class ValidatedAttribute(object):
+            x = attr.ib()
+            y = attr.ib(validator=[instance_of(str), matches_re("foo.*qux")])
+
+        va = ValidatedAttribute(42, "foobarqux")
+
+        with pytest.raises(TypeError) as ei:
+            va.y = 42
+
+        assert "foobarqux" == va.y
+
+        assert ei.value.args[0].startswith("'y' must be <")
+
+        with pytest.raises(ValueError) as ei:
+            va.y = "quxbarfoo"
+
+        assert ei.value.args[0].startswith("'y' must match regex '")
+
+        assert "foobarqux" == va.y
+
+        va.y = "foobazqux"
+
+        assert "foobazqux" == va.y
+
+    def test_pipe(self):
+        """
+        Multiple hooks are possible, in that case the last return value is
+        used. They can be supplied using the pipe functions or by passing a
+        list to on_setattr.
+        """
+
+        s = [setters.convert, lambda _, __, nv: nv + 1]
+
+        @attr.s
+        class Piped(object):
+            x1 = attr.ib(converter=int, on_setattr=setters.pipe(*s))
+            x2 = attr.ib(converter=int, on_setattr=s)
+
+        p = Piped("41", "22")
+
+        assert 41 == p.x1
+        assert 22 == p.x2
+
+        p.x1 = "41"
+        p.x2 = "22"
+
+        assert 42 == p.x1
+        assert 23 == p.x2
+
+    def test_make_class(self):
+        """
+        on_setattr of make_class gets forwarded.
+        """
+        C = attr.make_class("C", {"x": attr.ib()}, on_setattr=setters.frozen)
+
+        c = C(1)
+
+        with pytest.raises(FrozenAttributeError):
+            c.x = 2
+
+    def test_no_validator_no_converter(self):
+        """
+        validate and convert tolerate missing validators and converters.
+        """
+
+        @attr.s(on_setattr=[setters.convert, setters.validate])
+        class C(object):
+            x = attr.ib()
+
+        c = C(1)
+
+        c.x = 2
+
+        assert 2 == c.x
+
+    def test_validate_respects_run_validators_config(self):
+        """
+        If run validators is off, validate doesn't run them.
+        """
+
+        @attr.s(on_setattr=setters.validate)
+        class C(object):
+            x = attr.ib(validator=attr.validators.instance_of(int))
+
+        c = C(1)
+
+        attr.set_run_validators(False)
+
+        c.x = "1"
+
+        assert "1" == c.x
+
+        attr.set_run_validators(True)
+
+        with pytest.raises(TypeError) as ei:
+            c.x = "1"
+
+        assert ei.value.args[0].startswith("'x' must be <")
+
+    def test_frozen_on_setattr_class_is_caught(self):
+        """
+        @attr.s(on_setattr=X, frozen=True) raises an ValueError.
+        """
+        with pytest.raises(ValueError) as ei:
+
+            @attr.s(frozen=True, on_setattr=setters.validate)
+            class C(object):
+                x = attr.ib()
+
+        assert "Frozen classes can't use on_setattr." == ei.value.args[0]
+
+    def test_frozen_on_setattr_attribute_is_caught(self):
+        """
+        attr.ib(on_setattr=X) on a frozen class raises an ValueError.
+        """
+
+        with pytest.raises(ValueError) as ei:
+
+            @attr.s(frozen=True)
+            class C(object):
+                x = attr.ib(on_setattr=setters.validate)
+
+        assert "Frozen classes can't use on_setattr." == ei.value.args[0]
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_reset_if_no_custom_setattr(self, slots):
+        """
+        If a class with an active setattr is subclassed and no new setattr
+        is generated, the __setattr__ is set to object.__setattr__.
+
+        We do the double test because of Python 2.
+        """
+
+        def boom(*args):
+            pytest.fail("Must not be called.")
+
+        @attr.s
+        class Hooked(object):
+            x = attr.ib(on_setattr=boom)
+
+        @attr.s(slots=slots)
+        class NoHook(WithOnSetAttrHook):
+            x = attr.ib()
+
+        if not PY2:
+            assert NoHook.__setattr__ == object.__setattr__
+
+        assert 1 == NoHook(1).x
+        assert Hooked.__attrs_own_setattr__
+        assert not NoHook.__attrs_own_setattr__
+        assert WithOnSetAttrHook.__attrs_own_setattr__
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_inherited_do_not_reset(self, slots):
+        """
+        If we inherit a __setattr__ that has been written by the user, we must
+        not reset it unless necessary.
+        """
+
+        class A(object):
+            """
+            Not an attrs class on purpose to prevent accidental resets that
+            would render the asserts meaningless.
+            """
+
+            def __setattr__(self, *args):
+                pass
+
+        @attr.s(slots=slots)
+        class B(A):
+            pass
+
+        assert B.__setattr__ == A.__setattr__
+
+        @attr.s(slots=slots)
+        class C(B):
+            pass
+
+        assert C.__setattr__ == A.__setattr__
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_pickling_retains_attrs_own(self, slots):
+        """
+        Pickling/Unpickling does not lose ownership information about
+        __setattr__.
+        """
+        i = WithOnSetAttrHook(1)
+
+        assert True is i.__attrs_own_setattr__
+
+        i2 = pickle.loads(pickle.dumps(i))
+
+        assert True is i2.__attrs_own_setattr__
+
+        WOSAH = pickle.loads(pickle.dumps(WithOnSetAttrHook))
+
+        assert True is WOSAH.__attrs_own_setattr__
+
+    def test_slotted_class_can_have_custom_setattr(self):
+        """
+        A slotted class can define a custom setattr and it doesn't get
+        overwritten.
+
+        Regression test for #680.
+        """
+
+        @attr.s(slots=True)
+        class A(object):
+            def __setattr__(self, key, value):
+                raise SystemError
+
+        with pytest.raises(SystemError):
+            A().x = 1
+
+    @pytest.mark.xfail(raises=attr.exceptions.FrozenAttributeError)
+    def test_slotted_confused(self):
+        """
+        If we have a in-between non-attrs class, setattr reset detection
+        should still work, but currently doesn't.
+
+        It works with dict classes because we can look the finished class and
+        patch it.  With slotted classes we have to deduce it ourselves.
+        """
+
+        @attr.s(slots=True)
+        class A(object):
+            x = attr.ib(on_setattr=setters.frozen)
+
+        class B(A):
+            pass
+
+        @attr.s(slots=True)
+        class C(B):
+            x = attr.ib()
+
+        C(1).x = 2
+
+
+@pytest.mark.skipif(PY2, reason="Python 3-only.")
+class TestSetAttrNoPy2(object):
+    """
+    __setattr__ tests for Py3+ to avoid the skip repetition.
+    """
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_auto_detect_if_no_custom_setattr(self, slots):
+        """
+        It's possible to remove the on_setattr hook from an attribute and
+        therefore write a custom __setattr__.
+        """
+        assert 1 == WithOnSetAttrHook(1).x
+
+        @attr.s(auto_detect=True, slots=slots)
+        class RemoveNeedForOurSetAttr(WithOnSetAttrHook):
+            x = attr.ib()
+
+            def __setattr__(self, name, val):
+                object.__setattr__(self, name, val * 2)
+
+        i = RemoveNeedForOurSetAttr(1)
+
+        assert not RemoveNeedForOurSetAttr.__attrs_own_setattr__
+        assert 2 == i.x
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_restore_respects_auto_detect(self, slots):
+        """
+        If __setattr__ should be restored but the user supplied its own and
+        set auto_detect, leave is alone.
+        """
+
+        @attr.s(auto_detect=True, slots=slots)
+        class CustomSetAttr:
+            def __setattr__(self, _, __):
+                pass
+
+        assert CustomSetAttr.__setattr__ != object.__setattr__
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_auto_detect_frozen(self, slots):
+        """
+        frozen=True together with a detected custom __setattr__ are rejected.
+        """
+        with pytest.raises(
+            ValueError, match="Can't freeze a class with a custom __setattr__."
+        ):
+
+            @attr.s(auto_detect=True, slots=slots, frozen=True)
+            class CustomSetAttr(Frozen):
+                def __setattr__(self, _, __):
+                    pass
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_setattr_auto_detect_on_setattr(self, slots):
+        """
+        on_setattr attributes together with a detected custom __setattr__ are
+        rejected.
+        """
+        with pytest.raises(
+            ValueError,
+            match="Can't combine custom __setattr__ with on_setattr hooks.",
+        ):
+
+            @attr.s(auto_detect=True, slots=slots)
+            class HookAndCustomSetAttr(object):
+                x = attr.ib(on_setattr=lambda *args: None)
+
+                def __setattr__(self, _, __):
+                    pass
+
+    @pytest.mark.parametrize("a_slots", [True, False])
+    @pytest.mark.parametrize("b_slots", [True, False])
+    @pytest.mark.parametrize("c_slots", [True, False])
+    def test_setattr_inherited_do_not_reset_intermediate(
+        self, a_slots, b_slots, c_slots
+    ):
+        """
+        A user-provided intermediate __setattr__ is not reset to
+        object.__setattr__.
+
+        This only can work on Python 3+ with auto_detect activated, such that
+        attrs can know that there is a user-provided __setattr__.
+        """
+
+        @attr.s(slots=a_slots)
+        class A(object):
+            x = attr.ib(on_setattr=setters.frozen)
+
+        @attr.s(slots=b_slots, auto_detect=True)
+        class B(A):
+            x = attr.ib(on_setattr=setters.NO_OP)
+
+            def __setattr__(self, key, value):
+                raise SystemError
+
+        @attr.s(slots=c_slots)
+        class C(B):
+            pass
+
+        assert getattr(A, "__attrs_own_setattr__", False) is True
+        assert getattr(B, "__attrs_own_setattr__", False) is False
+        assert getattr(C, "__attrs_own_setattr__", False) is False
+
+        with pytest.raises(SystemError):
+            C(1).x = 3
+
+    def test_docstring(self):
+        """
+        Generated __setattr__ has a useful docstring.
+        """
+        assert (
+            "Method generated by attrs for class WithOnSetAttrHook."
+            == WithOnSetAttrHook.__setattr__.__doc__
+        )
diff --git a/tests/test_slots.py b/tests/test_slots.py
new file mode 100644 (file)
index 0000000..b57fc63
--- /dev/null
@@ -0,0 +1,627 @@
+"""
+Unit tests for slots-related functionality.
+"""
+
+import pickle
+import sys
+import types
+import weakref
+
+import pytest
+
+import attr
+
+from attr._compat import PY2, PYPY, just_warn, make_set_closure_cell
+
+
+# Pympler doesn't work on PyPy.
+try:
+    from pympler.asizeof import asizeof
+
+    has_pympler = True
+except BaseException:  # Won't be an import error.
+    has_pympler = False
+
+
+@attr.s
+class C1(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+    def method(self):
+        return self.x
+
+    @classmethod
+    def classmethod(cls):
+        return "clsmethod"
+
+    @staticmethod
+    def staticmethod():
+        return "staticmethod"
+
+    if not PY2:
+
+        def my_class(self):
+            return __class__
+
+        def my_super(self):
+            """Just to test out the no-arg super."""
+            return super().__repr__()
+
+
+@attr.s(slots=True, hash=True)
+class C1Slots(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+    def method(self):
+        return self.x
+
+    @classmethod
+    def classmethod(cls):
+        return "clsmethod"
+
+    @staticmethod
+    def staticmethod():
+        return "staticmethod"
+
+    if not PY2:
+
+        def my_class(self):
+            return __class__
+
+        def my_super(self):
+            """Just to test out the no-arg super."""
+            return super().__repr__()
+
+
+def test_slots_being_used():
+    """
+    The class is really using __slots__.
+    """
+    non_slot_instance = C1(x=1, y="test")
+    slot_instance = C1Slots(x=1, y="test")
+
+    assert "__dict__" not in dir(slot_instance)
+    assert "__slots__" in dir(slot_instance)
+
+    assert "__dict__" in dir(non_slot_instance)
+    assert "__slots__" not in dir(non_slot_instance)
+
+    assert set(["__weakref__", "x", "y"]) == set(slot_instance.__slots__)
+
+    if has_pympler:
+        assert asizeof(slot_instance) < asizeof(non_slot_instance)
+
+    non_slot_instance.t = "test"
+    with pytest.raises(AttributeError):
+        slot_instance.t = "test"
+
+    assert 1 == non_slot_instance.method()
+    assert 1 == slot_instance.method()
+
+    assert attr.fields(C1Slots) == attr.fields(C1)
+    assert attr.asdict(slot_instance) == attr.asdict(non_slot_instance)
+
+
+def test_basic_attr_funcs():
+    """
+    Comparison, `__eq__`, `__hash__`, `__repr__`, `attrs.asdict` work.
+    """
+    a = C1Slots(x=1, y=2)
+    b = C1Slots(x=1, y=3)
+    a_ = C1Slots(x=1, y=2)
+
+    # Comparison.
+    assert b > a
+
+    assert a_ == a
+
+    # Hashing.
+    hash(b)  # Just to assert it doesn't raise.
+
+    # Repr.
+    assert "C1Slots(x=1, y=2)" == repr(a)
+
+    assert {"x": 1, "y": 2} == attr.asdict(a)
+
+
+def test_inheritance_from_nonslots():
+    """
+    Inheritance from a non-slotted class works.
+
+    Note that a slotted class inheriting from an ordinary class loses most of
+    the benefits of slotted classes, but it should still work.
+    """
+
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    c2.t = "test"  # This will work, using the base class.
+
+    assert "test" == c2.t
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    assert set(["z"]) == set(C2Slots.__slots__)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+
+    assert c3 > c2
+
+    c2_ = C2Slots(x=1, y=2, z="test")
+
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_nonslots_these():
+    """
+    Enhancing a dict class using 'these' works.
+
+    This will actually *replace* the class with another one, using slots.
+    """
+
+    class SimpleOrdinaryClass(object):
+        def __init__(self, x, y, z):
+            self.x = x
+            self.y = y
+            self.z = z
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    C2Slots = attr.s(
+        these={"x": attr.ib(), "y": attr.ib(), "z": attr.ib()},
+        init=False,
+        slots=True,
+        hash=True,
+    )(SimpleOrdinaryClass)
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+    with pytest.raises(AttributeError):
+        c2.t = "test"  # We have slots now.
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    assert set(["__weakref__", "x", "y", "z"]) == set(C2Slots.__slots__)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "SimpleOrdinaryClass(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_inheritance_from_slots():
+    """
+    Inheriting from an attrs slotted class works.
+    """
+
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1Slots):
+        z = attr.ib()
+
+    @attr.s(slots=True, hash=True)
+    class C2(C1):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    assert set(["z"]) == set(C2Slots.__slots__)
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    with pytest.raises(AttributeError):
+        c2.t = "test"
+
+    non_slot_instance = C2(x=1, y=2, z="test")
+    if has_pympler:
+        assert asizeof(c2) < asizeof(non_slot_instance)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_bare_inheritance_from_slots():
+    """
+    Inheriting from a bare attrs slotted class works.
+    """
+
+    @attr.s(
+        init=False, eq=False, order=False, hash=False, repr=False, slots=True
+    )
+    class C1BareSlots(object):
+        x = attr.ib(validator=attr.validators.instance_of(int))
+        y = attr.ib()
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    @attr.s(init=False, eq=False, order=False, hash=False, repr=False)
+    class C1Bare(object):
+        x = attr.ib(validator=attr.validators.instance_of(int))
+        y = attr.ib()
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1BareSlots):
+        z = attr.ib()
+
+    @attr.s(slots=True, hash=True)
+    class C2(C1Bare):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    with pytest.raises(AttributeError):
+        c2.t = "test"
+
+    non_slot_instance = C2(x=1, y=2, z="test")
+    if has_pympler:
+        assert asizeof(c2) < asizeof(non_slot_instance)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.")
+class TestClosureCellRewriting(object):
+    def test_closure_cell_rewriting(self):
+        """
+        Slotted classes support proper closure cell rewriting.
+
+        This affects features like `__class__` and the no-arg super().
+        """
+        non_slot_instance = C1(x=1, y="test")
+        slot_instance = C1Slots(x=1, y="test")
+
+        assert non_slot_instance.my_class() is C1
+        assert slot_instance.my_class() is C1Slots
+
+        # Just assert they return something, and not an exception.
+        assert non_slot_instance.my_super()
+        assert slot_instance.my_super()
+
+    def test_inheritance(self):
+        """
+        Slotted classes support proper closure cell rewriting when inheriting.
+
+        This affects features like `__class__` and the no-arg super().
+        """
+
+        @attr.s
+        class C2(C1):
+            def my_subclass(self):
+                return __class__
+
+        @attr.s
+        class C2Slots(C1Slots):
+            def my_subclass(self):
+                return __class__
+
+        non_slot_instance = C2(x=1, y="test")
+        slot_instance = C2Slots(x=1, y="test")
+
+        assert non_slot_instance.my_class() is C1
+        assert slot_instance.my_class() is C1Slots
+
+        # Just assert they return something, and not an exception.
+        assert non_slot_instance.my_super()
+        assert slot_instance.my_super()
+
+        assert non_slot_instance.my_subclass() is C2
+        assert slot_instance.my_subclass() is C2Slots
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_cls_static(self, slots):
+        """
+        Slotted classes support proper closure cell rewriting for class- and
+        static methods.
+        """
+        # Python can reuse closure cells, so we create new classes just for
+        # this test.
+
+        @attr.s(slots=slots)
+        class C:
+            @classmethod
+            def clsmethod(cls):
+                return __class__
+
+        assert C.clsmethod() is C
+
+        @attr.s(slots=slots)
+        class D:
+            @staticmethod
+            def statmethod():
+                return __class__
+
+        assert D.statmethod() is D
+
+    @pytest.mark.skipif(PYPY, reason="set_closure_cell always works on PyPy")
+    @pytest.mark.skipif(
+        sys.version_info >= (3, 8),
+        reason="can't break CodeType.replace() via monkeypatch",
+    )
+    def test_code_hack_failure(self, monkeypatch):
+        """
+        Keeps working if function/code object introspection doesn't work
+        on this (nonstandard) interpeter.
+
+        A warning is emitted that points to the actual code.
+        """
+        # This is a pretty good approximation of the behavior of
+        # the actual types.CodeType on Brython.
+        monkeypatch.setattr(types, "CodeType", lambda: None)
+        func = make_set_closure_cell()
+
+        with pytest.warns(RuntimeWarning) as wr:
+            func()
+
+        w = wr.pop()
+        assert __file__ == w.filename
+        assert (
+            "Running interpreter doesn't sufficiently support code object "
+            "introspection.  Some features like bare super() or accessing "
+            "__class__ will not work with slotted classes.",
+        ) == w.message.args
+
+        assert just_warn is func
+
+
+@pytest.mark.skipif(PYPY, reason="__slots__ only block weakref on CPython")
+def test_not_weakrefable():
+    """
+    Instance is not weak-referenceable when `weakref_slot=False` in CPython.
+    """
+
+    @attr.s(slots=True, weakref_slot=False)
+    class C(object):
+        pass
+
+    c = C()
+
+    with pytest.raises(TypeError):
+        weakref.ref(c)
+
+
+@pytest.mark.skipif(
+    not PYPY, reason="slots without weakref_slot should only work on PyPy"
+)
+def test_implicitly_weakrefable():
+    """
+    Instance is weak-referenceable even when `weakref_slot=False` in PyPy.
+    """
+
+    @attr.s(slots=True, weakref_slot=False)
+    class C(object):
+        pass
+
+    c = C()
+    w = weakref.ref(c)
+
+    assert c is w()
+
+
+def test_weakrefable():
+    """
+    Instance is weak-referenceable when `weakref_slot=True`.
+    """
+
+    @attr.s(slots=True, weakref_slot=True)
+    class C(object):
+        pass
+
+    c = C()
+    w = weakref.ref(c)
+
+    assert c is w()
+
+
+def test_weakref_does_not_add_a_field():
+    """
+    `weakref_slot=True` does not add a field to the class.
+    """
+
+    @attr.s(slots=True, weakref_slot=True)
+    class C(object):
+        field = attr.ib()
+
+    assert [f.name for f in attr.fields(C)] == ["field"]
+
+
+def tests_weakref_does_not_add_when_inheriting_with_weakref():
+    """
+    `weakref_slot=True` does not add a new __weakref__ slot when inheriting
+    one.
+    """
+
+    @attr.s(slots=True, weakref_slot=True)
+    class C(object):
+        pass
+
+    @attr.s(slots=True, weakref_slot=True)
+    class D(C):
+        pass
+
+    d = D()
+    w = weakref.ref(d)
+
+    assert d is w()
+
+
+def tests_weakref_does_not_add_with_weakref_attribute():
+    """
+    `weakref_slot=True` does not add a new __weakref__ slot when an attribute
+    of that name exists.
+    """
+
+    @attr.s(slots=True, weakref_slot=True)
+    class C(object):
+        __weakref__ = attr.ib(
+            init=False, hash=False, repr=False, eq=False, order=False
+        )
+
+    c = C()
+    w = weakref.ref(c)
+
+    assert c is w()
+
+
+def test_slots_empty_cell():
+    """
+    Tests that no `ValueError: Cell is empty` exception is raised when
+    closure cells are present with no contents in a `slots=True` class.
+    (issue https://github.com/python-attrs/attrs/issues/589)
+
+    On Python 3, if a method mentions `__class__` or uses the no-arg `super()`,
+    the compiler will bake a reference to the class in the method itself as
+    `method.__closure__`. Since `attrs` replaces the class with a clone,
+    `_ClassBuilder._create_slots_class(self)` will rewrite these references so
+    it keeps working. This method was not properly covering the edge case where
+    the closure cell was empty, we fixed it and this is the non-regression
+    test.
+    """
+
+    @attr.s(slots=True)
+    class C(object):
+        field = attr.ib()
+
+        def f(self, a):
+            super(C, self).__init__()
+
+    C(field=1)
+
+
+@attr.s(getstate_setstate=True)
+class C2(object):
+    x = attr.ib()
+
+
+@attr.s(slots=True, getstate_setstate=True)
+class C2Slots(object):
+    x = attr.ib()
+
+
+class TestPickle(object):
+    @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL))
+    def test_pickleable_by_default(self, protocol):
+        """
+        If nothing else is passed, slotted classes can be pickled and
+        unpickled with all supported protocols.
+        """
+        i1 = C1Slots(1, 2)
+        i2 = pickle.loads(pickle.dumps(i1, protocol))
+
+        assert i1 == i2
+        assert i1 is not i2
+
+    def test_no_getstate_setstate_for_dict_classes(self):
+        """
+        As long as getstate_setstate is None, nothing is done to dict
+        classes.
+        """
+        i = C1(1, 2)
+
+        assert None is getattr(i, "__getstate__", None)
+        assert None is getattr(i, "__setstate__", None)
+
+    def test_no_getstate_setstate_if_option_false(self):
+        """
+        Don't add getstate/setstate if getstate_setstate is False.
+        """
+
+        @attr.s(slots=True, getstate_setstate=False)
+        class C(object):
+            x = attr.ib()
+
+        i = C(42)
+
+        assert None is getattr(i, "__getstate__", None)
+        assert None is getattr(i, "__setstate__", None)
+
+    @pytest.mark.parametrize("cls", [C2(1), C2Slots(1)])
+    def test_getstate_set_state_force_true(self, cls):
+        """
+        If getstate_setstate is True, add them unconditionally.
+        """
+        assert None is not getattr(cls, "__getstate__", None)
+        assert None is not getattr(cls, "__setstate__", None)
diff --git a/tests/test_validators.py b/tests/test_validators.py
new file mode 100644 (file)
index 0000000..4aeec99
--- /dev/null
@@ -0,0 +1,711 @@
+"""
+Tests for `attr.validators`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import re
+
+import pytest
+
+import attr
+
+from attr import has
+from attr import validators as validator_module
+from attr._compat import PY2, TYPE
+from attr.validators import (
+    and_,
+    deep_iterable,
+    deep_mapping,
+    in_,
+    instance_of,
+    is_callable,
+    matches_re,
+    optional,
+    provides,
+)
+
+from .utils import simple_attr
+
+
+@pytest.fixture(scope="module")
+def zope_interface():
+    """Provides ``zope.interface`` if available, skipping the test if not."""
+    try:
+        import zope.interface
+    except ImportError:
+        raise pytest.skip(
+            "zope-related tests skipped when zope.interface is not installed"
+        )
+
+    return zope.interface
+
+
+class TestInstanceOf(object):
+    """
+    Tests for `instance_of`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert instance_of.__name__ in validator_module.__all__
+
+    def test_success(self):
+        """
+        Nothing happens if types match.
+        """
+        v = instance_of(int)
+        v(None, simple_attr("test"), 42)
+
+    def test_subclass(self):
+        """
+        Subclasses are accepted too.
+        """
+        v = instance_of(int)
+        # yep, bools are a subclass of int :(
+        v(None, simple_attr("test"), True)
+
+    def test_fail(self):
+        """
+        Raises `TypeError` on wrong types.
+        """
+        v = instance_of(int)
+        a = simple_attr("test")
+        with pytest.raises(TypeError) as e:
+            v(None, a, "42")
+        assert (
+            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            a,
+            int,
+            "42",
+        ) == e.value.args
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = instance_of(int)
+        assert (
+            "<instance_of validator for type <{type} 'int'>>".format(type=TYPE)
+        ) == repr(v)
+
+
+class TestMatchesRe(object):
+    """
+    Tests for `matches_re`.
+    """
+
+    def test_in_all(self):
+        """
+        validator is in ``__all__``.
+        """
+        assert matches_re.__name__ in validator_module.__all__
+
+    def test_match(self):
+        """
+        Silent on matches, raises ValueError on mismatches.
+        """
+
+        @attr.s
+        class ReTester(object):
+            str_match = attr.ib(validator=matches_re("a"))
+
+        ReTester("a")  # shouldn't raise exceptions
+        with pytest.raises(TypeError):
+            ReTester(1)
+        with pytest.raises(ValueError):
+            ReTester("1")
+        with pytest.raises(ValueError):
+            ReTester("a1")
+
+    def test_flags(self):
+        """
+        Flags are propagated to the match function.
+        """
+
+        @attr.s
+        class MatchTester(object):
+            val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match))
+
+        MatchTester("A1")  # test flags and using re.match
+
+    def test_different_func(self):
+        """
+        Changing the match functions works.
+        """
+
+        @attr.s
+        class SearchTester(object):
+            val = attr.ib(validator=matches_re("a", 0, re.search))
+
+        SearchTester("bab")  # re.search will match
+
+    def test_catches_invalid_func(self):
+        """
+        Invalid match functions are caught.
+        """
+        with pytest.raises(ValueError) as ei:
+            matches_re("a", 0, lambda: None)
+
+        if not PY2:
+            assert (
+                "'func' must be one of None, fullmatch, match, search."
+                == ei.value.args[0]
+            )
+        else:
+            assert (
+                "'func' must be one of None, match, search."
+                == ei.value.args[0]
+            )
+
+    @pytest.mark.parametrize(
+        "func", [None, getattr(re, "fullmatch", None), re.match, re.search]
+    )
+    def test_accepts_all_valid_func(self, func):
+        """
+        Every valid match function is accepted.
+        """
+        matches_re("a", func=func)
+
+    def test_repr(self):
+        """
+        __repr__ is meaningful.
+        """
+        assert repr(matches_re("a")).startswith(
+            "<matches_re validator for pattern"
+        )
+
+
+def always_pass(_, __, ___):
+    """
+    Toy validator that always passes.
+    """
+
+
+def always_fail(_, __, ___):
+    """
+    Toy validator that always fails.
+    """
+    0 / 0
+
+
+class TestAnd(object):
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert and_.__name__ in validator_module.__all__
+
+    def test_success(self):
+        """
+        Succeeds if all wrapped validators succeed.
+        """
+        v = and_(instance_of(int), always_pass)
+
+        v(None, simple_attr("test"), 42)
+
+    def test_fail(self):
+        """
+        Fails if any wrapped validator fails.
+        """
+        v = and_(instance_of(int), always_fail)
+
+        with pytest.raises(ZeroDivisionError):
+            v(None, simple_attr("test"), 42)
+
+    def test_sugar(self):
+        """
+        `and_(v1, v2, v3)` and `[v1, v2, v3]` are equivalent.
+        """
+
+        @attr.s
+        class C(object):
+            a1 = attr.ib("a1", validator=and_(instance_of(int)))
+            a2 = attr.ib("a2", validator=[instance_of(int)])
+
+        assert C.__attrs_attrs__[0].validator == C.__attrs_attrs__[1].validator
+
+
+@pytest.fixture(scope="module")
+def ifoo(zope_interface):
+    """Provides a test ``zope.interface.Interface`` in ``zope`` tests."""
+
+    class IFoo(zope_interface.Interface):
+        """
+        An interface.
+        """
+
+        def f():
+            """
+            A function called f.
+            """
+
+    return IFoo
+
+
+class TestProvides(object):
+    """
+    Tests for `provides`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert provides.__name__ in validator_module.__all__
+
+    def test_success(self, zope_interface, ifoo):
+        """
+        Nothing happens if value provides requested interface.
+        """
+
+        @zope_interface.implementer(ifoo)
+        class C(object):
+            def f(self):
+                pass
+
+        v = provides(ifoo)
+        v(None, simple_attr("x"), C())
+
+    def test_fail(self, ifoo):
+        """
+        Raises `TypeError` if interfaces isn't provided by value.
+        """
+        value = object()
+        a = simple_attr("x")
+
+        v = provides(ifoo)
+        with pytest.raises(TypeError) as e:
+            v(None, a, value)
+        assert (
+            "'x' must provide {interface!r} which {value!r} doesn't.".format(
+                interface=ifoo, value=value
+            ),
+            a,
+            ifoo,
+            value,
+        ) == e.value.args
+
+    def test_repr(self, ifoo):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = provides(ifoo)
+        assert (
+            "<provides validator for interface {interface!r}>".format(
+                interface=ifoo
+            )
+        ) == repr(v)
+
+
+@pytest.mark.parametrize(
+    "validator", [instance_of(int), [always_pass, instance_of(int)]]
+)
+class TestOptional(object):
+    """
+    Tests for `optional`.
+    """
+
+    def test_in_all(self, validator):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert optional.__name__ in validator_module.__all__
+
+    def test_success(self, validator):
+        """
+        Nothing happens if validator succeeds.
+        """
+        v = optional(validator)
+        v(None, simple_attr("test"), 42)
+
+    def test_success_with_none(self, validator):
+        """
+        Nothing happens if None.
+        """
+        v = optional(validator)
+        v(None, simple_attr("test"), None)
+
+    def test_fail(self, validator):
+        """
+        Raises `TypeError` on wrong types.
+        """
+        v = optional(validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError) as e:
+            v(None, a, "42")
+        assert (
+            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            a,
+            int,
+            "42",
+        ) == e.value.args
+
+    def test_repr(self, validator):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = optional(validator)
+
+        if isinstance(validator, list):
+            repr_s = (
+                "<optional validator for _AndValidator(_validators=[{func}, "
+                "<instance_of validator for type <{type} 'int'>>]) or None>"
+            ).format(func=repr(always_pass), type=TYPE)
+        else:
+            repr_s = (
+                "<optional validator for <instance_of validator for type "
+                "<{type} 'int'>> or None>"
+            ).format(type=TYPE)
+
+        assert repr_s == repr(v)
+
+
+class TestIn_(object):
+    """
+    Tests for `in_`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert in_.__name__ in validator_module.__all__
+
+    def test_success_with_value(self):
+        """
+        If the value is in our options, nothing happens.
+        """
+        v = in_([1, 2, 3])
+        a = simple_attr("test")
+        v(1, a, 3)
+
+    def test_fail(self):
+        """
+        Raise ValueError if the value is outside our options.
+        """
+        v = in_([1, 2, 3])
+        a = simple_attr("test")
+        with pytest.raises(ValueError) as e:
+            v(None, a, None)
+        assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args
+
+    def test_fail_with_string(self):
+        """
+        Raise ValueError if the value is outside our options when the
+        options are specified as a string and the value is not a string.
+        """
+        v = in_("abc")
+        a = simple_attr("test")
+        with pytest.raises(ValueError) as e:
+            v(None, a, None)
+        assert ("'test' must be in 'abc' (got None)",) == e.value.args
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = in_([3, 4, 5])
+        assert (("<in_ validator with options [3, 4, 5]>")) == repr(v)
+
+
+class TestDeepIterable(object):
+    """
+    Tests for `deep_iterable`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert deep_iterable.__name__ in validator_module.__all__
+
+    def test_success_member_only(self):
+        """
+        If the member validator succeeds and the iterable validator is not set,
+        nothing happens.
+        """
+        member_validator = instance_of(int)
+        v = deep_iterable(member_validator)
+        a = simple_attr("test")
+        v(None, a, [42])
+
+    def test_success_member_and_iterable(self):
+        """
+        If both the member and iterable validators succeed, nothing happens.
+        """
+        member_validator = instance_of(int)
+        iterable_validator = instance_of(list)
+        v = deep_iterable(member_validator, iterable_validator)
+        a = simple_attr("test")
+        v(None, a, [42])
+
+    @pytest.mark.parametrize(
+        "member_validator, iterable_validator",
+        (
+            (instance_of(int), 42),
+            (42, instance_of(list)),
+            (42, 42),
+            (42, None),
+        ),
+    )
+    def test_noncallable_validators(
+        self, member_validator, iterable_validator
+    ):
+        """
+        Raise `TypeError` if any validators are not callable.
+        """
+        with pytest.raises(TypeError) as e:
+            deep_iterable(member_validator, iterable_validator)
+        value = 42
+        message = "must be callable (got {value} that is a {type_}).".format(
+            value=value, type_=value.__class__
+        )
+
+        assert message in e.value.args[0]
+        assert value == e.value.args[1]
+        assert message in e.value.msg
+        assert value == e.value.value
+
+    def test_fail_invalid_member(self):
+        """
+        Raise member validator error if an invalid member is found.
+        """
+        member_validator = instance_of(int)
+        v = deep_iterable(member_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, [42, "42"])
+
+    def test_fail_invalid_iterable(self):
+        """
+        Raise iterable validator error if an invalid iterable is found.
+        """
+        member_validator = instance_of(int)
+        iterable_validator = instance_of(tuple)
+        v = deep_iterable(member_validator, iterable_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, [42])
+
+    def test_fail_invalid_member_and_iterable(self):
+        """
+        Raise iterable validator error if both the iterable
+        and a member are invalid.
+        """
+        member_validator = instance_of(int)
+        iterable_validator = instance_of(tuple)
+        v = deep_iterable(member_validator, iterable_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, [42, "42"])
+
+    def test_repr_member_only(self):
+        """
+        Returned validator has a useful `__repr__`
+        when only member validator is set.
+        """
+        member_validator = instance_of(int)
+        member_repr = "<instance_of validator for type <{type} 'int'>>".format(
+            type=TYPE
+        )
+        v = deep_iterable(member_validator)
+        expected_repr = (
+            "<deep_iterable validator for iterables of {member_repr}>"
+        ).format(member_repr=member_repr)
+        assert ((expected_repr)) == repr(v)
+
+    def test_repr_member_and_iterable(self):
+        """
+        Returned validator has a useful `__repr__` when both member
+        and iterable validators are set.
+        """
+        member_validator = instance_of(int)
+        member_repr = "<instance_of validator for type <{type} 'int'>>".format(
+            type=TYPE
+        )
+        iterable_validator = instance_of(list)
+        iterable_repr = (
+            "<instance_of validator for type <{type} 'list'>>"
+        ).format(type=TYPE)
+        v = deep_iterable(member_validator, iterable_validator)
+        expected_repr = (
+            "<deep_iterable validator for"
+            " {iterable_repr} iterables of {member_repr}>"
+        ).format(iterable_repr=iterable_repr, member_repr=member_repr)
+        assert expected_repr == repr(v)
+
+
+class TestDeepMapping(object):
+    """
+    Tests for `deep_mapping`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert deep_mapping.__name__ in validator_module.__all__
+
+    def test_success(self):
+        """
+        If both the key and value validators succeed, nothing happens.
+        """
+        key_validator = instance_of(str)
+        value_validator = instance_of(int)
+        v = deep_mapping(key_validator, value_validator)
+        a = simple_attr("test")
+        v(None, a, {"a": 6, "b": 7})
+
+    @pytest.mark.parametrize(
+        "key_validator, value_validator, mapping_validator",
+        (
+            (42, instance_of(int), None),
+            (instance_of(str), 42, None),
+            (instance_of(str), instance_of(int), 42),
+            (42, 42, None),
+            (42, 42, 42),
+        ),
+    )
+    def test_noncallable_validators(
+        self, key_validator, value_validator, mapping_validator
+    ):
+        """
+        Raise `TypeError` if any validators are not callable.
+        """
+        with pytest.raises(TypeError) as e:
+            deep_mapping(key_validator, value_validator, mapping_validator)
+
+        value = 42
+        message = "must be callable (got {value} that is a {type_}).".format(
+            value=value, type_=value.__class__
+        )
+
+        assert message in e.value.args[0]
+        assert value == e.value.args[1]
+        assert message in e.value.msg
+        assert value == e.value.value
+
+    def test_fail_invalid_mapping(self):
+        """
+        Raise `TypeError` if mapping validator fails.
+        """
+        key_validator = instance_of(str)
+        value_validator = instance_of(int)
+        mapping_validator = instance_of(dict)
+        v = deep_mapping(key_validator, value_validator, mapping_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, None)
+
+    def test_fail_invalid_key(self):
+        """
+        Raise key validator error if an invalid key is found.
+        """
+        key_validator = instance_of(str)
+        value_validator = instance_of(int)
+        v = deep_mapping(key_validator, value_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, {"a": 6, 42: 7})
+
+    def test_fail_invalid_member(self):
+        """
+        Raise key validator error if an invalid member value is found.
+        """
+        key_validator = instance_of(str)
+        value_validator = instance_of(int)
+        v = deep_mapping(key_validator, value_validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError):
+            v(None, a, {"a": "6", "b": 7})
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        key_validator = instance_of(str)
+        key_repr = "<instance_of validator for type <{type} 'str'>>".format(
+            type=TYPE
+        )
+        value_validator = instance_of(int)
+        value_repr = "<instance_of validator for type <{type} 'int'>>".format(
+            type=TYPE
+        )
+        v = deep_mapping(key_validator, value_validator)
+        expected_repr = (
+            "<deep_mapping validator for objects mapping "
+            "{key_repr} to {value_repr}>"
+        ).format(key_repr=key_repr, value_repr=value_repr)
+        assert expected_repr == repr(v)
+
+
+class TestIsCallable(object):
+    """
+    Tests for `is_callable`.
+    """
+
+    def test_in_all(self):
+        """
+        Verify that this validator is in ``__all__``.
+        """
+        assert is_callable.__name__ in validator_module.__all__
+
+    def test_success(self):
+        """
+        If the value is callable, nothing happens.
+        """
+        v = is_callable()
+        a = simple_attr("test")
+        v(None, a, isinstance)
+
+    def test_fail(self):
+        """
+        Raise TypeError if the value is not callable.
+        """
+        v = is_callable()
+        a = simple_attr("test")
+        with pytest.raises(TypeError) as e:
+            v(None, a, None)
+
+        value = None
+        message = "'test' must be callable (got {value} that is a {type_})."
+        expected_message = message.format(value=value, type_=value.__class__)
+
+        assert expected_message == e.value.args[0]
+        assert value == e.value.args[1]
+        assert expected_message == e.value.msg
+        assert value == e.value.value
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = is_callable()
+        assert "<is_callable validator>" == repr(v)
+
+    def test_exception_repr(self):
+        """
+        Verify that NotCallableError exception has a useful `__str__`.
+        """
+        from attr.exceptions import NotCallableError
+
+        instance = NotCallableError(msg="Some Message", value=42)
+        assert "Some Message" == str(instance)
+
+
+def test_hashability():
+    """
+    Validator classes are hashable.
+    """
+    for obj_name in dir(validator_module):
+        obj = getattr(validator_module, obj_name)
+        if not has(obj):
+            continue
+        hash_func = getattr(obj, "__hash__", None)
+        assert hash_func is not None
+        assert hash_func is not object.__hash__
diff --git a/tests/test_version_info.py b/tests/test_version_info.py
new file mode 100644 (file)
index 0000000..db4053f
--- /dev/null
@@ -0,0 +1,60 @@
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from attr import VersionInfo
+from attr._compat import PY2
+
+
+@pytest.fixture(name="vi")
+def fixture_vi():
+    return VersionInfo(19, 2, 0, "final")
+
+
+class TestVersionInfo:
+    def test_from_string_no_releaselevel(self, vi):
+        """
+        If there is no suffix, the releaselevel becomes "final" by default.
+        """
+        assert vi == VersionInfo._from_version_string("19.2.0")
+
+    def test_suffix_is_preserved(self):
+        """
+        If there is a suffix, it's preserved.
+        """
+        assert (
+            "dev0"
+            == VersionInfo._from_version_string("19.2.0.dev0").releaselevel
+        )
+
+    @pytest.mark.skipif(
+        PY2, reason="Python 2 is too YOLO to care about comparability."
+    )
+    @pytest.mark.parametrize("other", [(), (19, 2, 0, "final", "garbage")])
+    def test_wrong_len(self, vi, other):
+        """
+        Comparing with a tuple that has the wrong length raises an error.
+        """
+        assert vi != other
+
+        with pytest.raises(TypeError):
+            vi < other
+
+    @pytest.mark.parametrize("other", [[19, 2, 0, "final"]])
+    def test_wrong_type(self, vi, other):
+        """
+        Only compare to other VersionInfos or tuples.
+        """
+        assert vi != other
+
+    def test_order(self, vi):
+        """
+        Ordering works as expected.
+        """
+        assert vi < (20,)
+        assert vi < (19, 2, 1)
+        assert vi > (0,)
+        assert vi <= (19, 2)
+        assert vi >= (19, 2)
+        assert vi > (19, 2, 0, "dev0")
+        assert vi < (19, 2, 0, "post1")
diff --git a/tests/typing_example.py b/tests/typing_example.py
new file mode 100644 (file)
index 0000000..878837b
--- /dev/null
@@ -0,0 +1,236 @@
+import re
+
+from typing import Any, Dict, List, Tuple, Union
+
+import attr
+
+
+# Typing via "type" Argument ---
+
+
+@attr.s
+class C:
+    a = attr.ib(type=int)
+
+
+c = C(1)
+C(a=1)
+
+
+@attr.s
+class D:
+    x = attr.ib(type=List[int])
+
+
+@attr.s
+class E:
+    y = attr.ib(type="List[int]")
+
+
+@attr.s
+class F:
+    z = attr.ib(type=Any)
+
+
+# Typing via Annotations ---
+
+
+@attr.s
+class CC:
+    a: int = attr.ib()
+
+
+cc = CC(1)
+CC(a=1)
+
+
+@attr.s
+class DD:
+    x: List[int] = attr.ib()
+
+
+@attr.s
+class EE:
+    y: "List[int]" = attr.ib()
+
+
+@attr.s
+class FF:
+    z: Any = attr.ib()
+
+
+# Inheritance --
+
+
+@attr.s
+class GG(DD):
+    y: str = attr.ib()
+
+
+GG(x=[1], y="foo")
+
+
+@attr.s
+class HH(DD, EE):
+    z: float = attr.ib()
+
+
+HH(x=[1], y=[], z=1.1)
+
+
+# same class
+c == cc
+
+
+# Exceptions
+@attr.s(auto_exc=True)
+class Error(Exception):
+    x = attr.ib()
+
+
+try:
+    raise Error(1)
+except Error as e:
+    e.x
+    e.args
+    str(e)
+
+
+# Converters
+# XXX: Currently converters can only be functions so none of this works
+# although the stubs should be correct.
+
+# @attr.s
+# class ConvCOptional:
+#     x: Optional[int] = attr.ib(converter=attr.converters.optional(int))
+
+
+# ConvCOptional(1)
+# ConvCOptional(None)
+
+
+# @attr.s
+# class ConvCDefaultIfNone:
+#     x: int = attr.ib(converter=attr.converters.default_if_none(42))
+
+
+# ConvCDefaultIfNone(1)
+# ConvCDefaultIfNone(None)
+
+
+# Validators
+@attr.s
+class Validated:
+    a = attr.ib(
+        type=List[C],
+        validator=attr.validators.deep_iterable(
+            attr.validators.instance_of(C), attr.validators.instance_of(list)
+        ),
+    )
+    a = attr.ib(
+        type=Tuple[C],
+        validator=attr.validators.deep_iterable(
+            attr.validators.instance_of(C), attr.validators.instance_of(tuple)
+        ),
+    )
+    b = attr.ib(
+        type=List[C],
+        validator=attr.validators.deep_iterable(
+            attr.validators.instance_of(C)
+        ),
+    )
+    c = attr.ib(
+        type=Dict[C, D],
+        validator=attr.validators.deep_mapping(
+            attr.validators.instance_of(C),
+            attr.validators.instance_of(D),
+            attr.validators.instance_of(dict),
+        ),
+    )
+    d = attr.ib(
+        type=Dict[C, D],
+        validator=attr.validators.deep_mapping(
+            attr.validators.instance_of(C), attr.validators.instance_of(D)
+        ),
+    )
+    e = attr.ib(validator=attr.validators.matches_re(r"foo"))
+    f = attr.ib(
+        validator=attr.validators.matches_re(r"foo", flags=42, func=re.search)
+    )
+
+    # Test different forms of instance_of
+    g: int = attr.ib(validator=attr.validators.instance_of(int))
+    h: int = attr.ib(validator=attr.validators.instance_of((int,)))
+    j: Union[int, str] = attr.ib(
+        validator=attr.validators.instance_of((int, str))
+    )
+    k: Union[int, str, C] = attr.ib(
+        validator=attr.validators.instance_of((int, C, str))
+    )
+
+
+# Custom repr()
+@attr.s
+class WithCustomRepr:
+    a = attr.ib(repr=True)
+    b = attr.ib(repr=False)
+    c = attr.ib(repr=lambda value: "c is for cookie")
+    d = attr.ib(repr=str)
+
+
+# Check some of our own types
+@attr.s(eq=True, order=False)
+class OrderFlags:
+    a = attr.ib(eq=False, order=False)
+    b = attr.ib(eq=True, order=True)
+
+
+# on_setattr hooks
+@attr.s(on_setattr=attr.setters.validate)
+class ValidatedSetter:
+    a = attr.ib()
+    b = attr.ib(on_setattr=attr.setters.NO_OP)
+    c = attr.ib(on_setattr=attr.setters.frozen)
+    d = attr.ib(on_setattr=[attr.setters.convert, attr.setters.validate])
+    d = attr.ib(
+        on_setattr=attr.setters.pipe(
+            attr.setters.convert, attr.setters.validate
+        )
+    )
+
+
+# Auto-detect
+# XXX: needs support in mypy
+# @attr.s(auto_detect=True)
+# class AutoDetect:
+#     x: int
+
+#     def __init__(self, x: int):
+#         self.x = x
+
+# Provisional APIs
+@attr.define(order=True)
+class NGClass:
+    x: int = attr.field(default=42)
+
+
+# XXX: needs support in mypy
+# ngc = NGClass(1)
+
+
+@attr.mutable(slots=False)
+class NGClass2:
+    x: int
+
+
+# XXX: needs support in mypy
+# ngc2 = NGClass2(1)
+
+
+@attr.frozen(str=True)
+class NGFrozen:
+    x: int
+
+
+# XXX: needs support in mypy
+# ngf = NGFrozen(1)
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644 (file)
index 0000000..ad3fb57
--- /dev/null
@@ -0,0 +1,84 @@
+"""
+Common helper functions for tests.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from attr import Attribute
+from attr._make import NOTHING, make_class
+
+
+def simple_class(
+    eq=False,
+    order=False,
+    repr=False,
+    hash=False,
+    str=False,
+    slots=False,
+    frozen=False,
+    cache_hash=False,
+):
+    """
+    Return a new simple class.
+    """
+    return make_class(
+        "C",
+        ["a", "b"],
+        eq=eq or order,
+        order=order,
+        repr=repr,
+        hash=hash,
+        init=True,
+        slots=slots,
+        str=str,
+        frozen=frozen,
+        cache_hash=cache_hash,
+    )
+
+
+def simple_attr(
+    name,
+    default=NOTHING,
+    validator=None,
+    repr=True,
+    eq=True,
+    hash=None,
+    init=True,
+    converter=None,
+    kw_only=False,
+    inherited=False,
+):
+    """
+    Return an attribute with a name and no other bells and whistles.
+    """
+    return Attribute(
+        name=name,
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=None,
+        eq=eq,
+        hash=hash,
+        init=init,
+        converter=converter,
+        kw_only=kw_only,
+        inherited=inherited,
+    )
+
+
+class TestSimpleClass(object):
+    """
+    Tests for the testing helper function `make_class`.
+    """
+
+    def test_returns_class(self):
+        """
+        Returns a class object.
+        """
+        assert type is simple_class().__class__
+
+    def returns_distinct_classes(self):
+        """
+        Each call returns a completely new class.
+        """
+        assert simple_class() is not simple_class()
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..0508fda
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,122 @@
+[pytest]
+addopts = -ra
+testpaths = tests
+xfail_strict = true
+filterwarnings =
+    once::Warning
+    ignore:::pympler[.*]
+
+
+# Keep docs in sync with docs env and .readthedocs.yml.
+[gh-actions]
+python =
+    2.7: py27
+    3.5: py35
+    3.6: py36
+    3.7: py37, docs
+    3.8: py38, lint, manifest, typing, changelog
+    3.9: py39
+    pypy2: pypy2
+    pypy3: pypy3
+
+
+[tox]
+envlist = typing,lint,py27,py35,py36,py37,py38,py39,pypy,pypy3,manifest,docs,pypi-description,changelog,coverage-report
+isolated_build = True
+
+
+[testenv]
+# Prevent random setuptools/pip breakages like
+# https://github.com/pypa/setuptools/issues/1042 from breaking our builds.
+setenv =
+    VIRTUALENV_NO_DOWNLOAD=1
+extras = {env:TOX_AP_TEST_EXTRAS:tests}
+commands = python -m pytest {posargs}
+
+
+[testenv:py27]
+extras = {env:TOX_AP_TEST_EXTRAS:tests}
+commands = coverage run -m pytest {posargs}
+
+
+[testenv:py37]
+# 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 = {env:TOX_AP_TEST_EXTRAS:tests}
+commands = coverage run -m pytest {posargs}
+
+
+[testenv:py38]
+# 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.
+basepython = python3.8
+install_command = pip install --no-compile {opts} {packages}
+setenv =
+    PYTHONWARNINGS=d
+extras = {env:TOX_AP_TEST_EXTRAS:tests}
+commands = coverage run -m pytest {posargs}
+
+
+[testenv:coverage-report]
+basepython = python3.7
+skip_install = true
+deps = coverage[toml]>=5.0.2
+commands =
+    coverage combine
+    coverage report
+
+
+[testenv:lint]
+basepython = python3.8
+skip_install = true
+deps =
+    pre-commit
+passenv = HOMEPATH  # needed on Windows
+commands =
+    pre-commit run --all-files
+
+
+[testenv:docs]
+# Keep basepython in sync with gh-actions and .readthedocs.yml.
+basepython = python3.7
+extras = docs
+commands =
+    sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
+    sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
+    python -m doctest README.rst
+
+
+[testenv:manifest]
+basepython = python3.8
+deps = check-manifest
+skip_install = true
+commands = check-manifest
+
+
+[testenv:pypi-description]
+basepython = python3.8
+skip_install = true
+deps =
+    twine
+    pip >= 18.0.0
+commands =
+    pip wheel -w {envtmpdir}/build --no-deps .
+    twine check {envtmpdir}/build/*
+
+
+[testenv:changelog]
+basepython = python3.8
+deps = towncrier
+skip_install = true
+commands = towncrier --draft
+
+
+[testenv:typing]
+basepython = python3.8
+deps = mypy
+commands =
+    mypy src/attr/__init__.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi
+    mypy tests/typing_example.py