From a584b92803b1f088fee0b134d2ea18cb67d35d0e Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Wed, 21 Aug 2024 11:21:36 +0100 Subject: [PATCH] Switch from attr to attrs namespace. --- .github/workflows/python_ci.yml | 20 +- .github/workflows/python_ci_linux.yml | 20 +- .github/workflows/python_ci_macos.yml | 20 +- attr_utils/annotations.py | 40 +- attr_utils/autoattrs.py | 10 +- attr_utils/mypy_plugin.py | 10 +- attr_utils/pprinter.py | 10 +- attr_utils/serialise.py | 26 +- demo.py | 26 +- repo_helper.yml | 2 +- requirements.txt | 2 +- tests/test_annotations.py | 12 +- tests/test_docstrings.py | 10 +- tests/test_mypy_plugin.yml | 6 +- .../doc-test/test-root/annotations.rst | 1 + .../doc-test/test-root/docstrings.rst | 4 +- .../test_html_output_docstrings_html_.html | 433 +++++++++--------- tests/test_pprinter.py | 12 +- tests/test_serialise.py | 18 +- tox.ini | 59 ++- 20 files changed, 385 insertions(+), 356 deletions(-) diff --git a/.github/workflows/python_ci.yml b/.github/workflows/python_ci.yml index ae68171..2e95f54 100644 --- a/.github/workflows/python_ci.yml +++ b/.github/workflows/python_ci.yml @@ -28,16 +28,16 @@ jobs: fail-fast: False matrix: config: - - {python-version: "3.7", testenvs: "py37-attrs{21.2,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} - - {python-version: "3.8", testenvs: "py38-attrs{21.2,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.9", testenvs: "py39-attrs{21.2,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.10", testenvs: "py310-attrs{21.2,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.11", testenvs: "py311-attrs{21.2,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.12", testenvs: "py312-attrs{21.2,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} - - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} - - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} - - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "pypy-3.9-v7.3.15", testenvs: "pypy39-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.7", testenvs: "py37-attrs{21.3,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} + - {python-version: "3.8", testenvs: "py38-attrs{21.3,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.9", testenvs: "py39-attrs{21.3,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.10", testenvs: "py310-attrs{21.3,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.11", testenvs: "py311-attrs{21.3,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.12", testenvs: "py312-attrs{21.3,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} + - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "pypy-3.9-v7.3.15", testenvs: "pypy39-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} steps: - name: Checkout πŸ›ŽοΈ diff --git a/.github/workflows/python_ci_linux.yml b/.github/workflows/python_ci_linux.yml index 028a4ee..86fad04 100644 --- a/.github/workflows/python_ci_linux.yml +++ b/.github/workflows/python_ci_linux.yml @@ -29,16 +29,16 @@ jobs: fail-fast: False matrix: config: - - {python-version: "3.7", testenvs: "py37-attrs{21.2,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} - - {python-version: "3.8", testenvs: "py38-attrs{21.2,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.9", testenvs: "py39-attrs{21.2,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.10", testenvs: "py310-attrs{21.2,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.11", testenvs: "py311-attrs{21.2,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.12", testenvs: "py312-attrs{21.2,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} - - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} - - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} - - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "pypy-3.9", testenvs: "pypy39-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.7", testenvs: "py37-attrs{21.3,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} + - {python-version: "3.8", testenvs: "py38-attrs{21.3,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.9", testenvs: "py39-attrs{21.3,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.10", testenvs: "py310-attrs{21.3,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.11", testenvs: "py311-attrs{21.3,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.12", testenvs: "py312-attrs{21.3,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} + - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "pypy-3.9", testenvs: "pypy39-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} steps: - name: Checkout πŸ›ŽοΈ diff --git a/.github/workflows/python_ci_macos.yml b/.github/workflows/python_ci_macos.yml index e19b990..791c4b1 100644 --- a/.github/workflows/python_ci_macos.yml +++ b/.github/workflows/python_ci_macos.yml @@ -28,16 +28,16 @@ jobs: fail-fast: False matrix: config: - - {python-version: "3.7", testenvs: "py37-attrs{21.2,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} - - {python-version: "3.8", testenvs: "py38-attrs{21.2,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.9", testenvs: "py39-attrs{21.2,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.10", testenvs: "py310-attrs{21.2,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.11", testenvs: "py311-attrs{21.2,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "3.12", testenvs: "py312-attrs{21.2,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} - - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} - - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} - - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} - - {python-version: "pypy-3.9", testenvs: "pypy39-attrs{21.2,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.7", testenvs: "py37-attrs{21.3,21.4,22.2,23.1,23.2,latest},py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1},py37-sphinx{4,5},build", experimental: False} + - {python-version: "3.8", testenvs: "py38-attrs{21.3,21.4,22.2,23.1,23.2,latest},py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.9", testenvs: "py39-attrs{21.3,21.4,22.2,23.1,23.2,latest},py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py39-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.10", testenvs: "py310-attrs{21.3,21.4,22.2,23.1,23.2,latest},py310-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py310-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.11", testenvs: "py311-attrs{21.3,21.4,22.2,23.1,23.2,latest},py311-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest},py311-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "3.12", testenvs: "py312-attrs{21.3,21.4,22.2,23.1,23.2,latest},py312-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py312-sphinx{4,5,6,7},build", experimental: True} + - {python-version: "3.13.0-beta.4", testenvs: "py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest},py313-dev-mypy{1.0.1,1.2.0,1.4.1,1.6.1,latest},py313-dev-sphinx{6,7},build", experimental: True} + - {python-version: "pypy-3.7", testenvs: "pypy37-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy37-sphinx{4,5},build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy38-sphinx{4,5,6,7},build", experimental: False} + - {python-version: "pypy-3.9", testenvs: "pypy39-attrs{21.3,21.4,22.2,23.1,23.2,latest},pypy39-sphinx{4,5,6,7},build", experimental: True} steps: - name: Checkout πŸ›ŽοΈ diff --git a/attr_utils/annotations.py b/attr_utils/annotations.py index f36193b..9da4843 100644 --- a/attr_utils/annotations.py +++ b/attr_utils/annotations.py @@ -22,7 +22,7 @@ The annotation can also be provided via the ``'annotation'`` key in the `metadata dict `_. If you prefer you can instead provide this as a keyword argument to :func:`~.attrib` -which will construct the metadata dict and call :func:`attr.ib` for you. +which will construct the metadata dict and call :func:`attrs.field` for you. .. _attrs: https://www.attrs.org/en/stable/ @@ -52,12 +52,12 @@ def untyped_converter(arg): return arg - @attr.s + @attrs.define class SomeClass: - a_string: str = attr.ib(converter=str) - custom_converter: Any = attr.ib(converter=my_converter) - untyped: Tuple[str, int, float] = attr.ib(converter=untyped_converter) - annotated: List[str] = attr.ib( + a_string: str = attrs.field(converter=str) + custom_converter: Any = attrs.field(converter=my_converter) + untyped: Tuple[str, int, float] = attrs.field(converter=untyped_converter) + annotated: List[str] = attrs.field( converter=list, metadata={"annotation": Sequence[str]}, ) @@ -123,7 +123,7 @@ class SomeClass: from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Type, TypeVar, Union, cast # 3rd party -import attr +import attrs # this package import attr_utils @@ -153,7 +153,7 @@ def add_init_annotations(obj: _C) -> _C: .. _attrs: https://www.attrs.org/en/stable/ """ - if not attr.has(obj): # type: ignore + if not attrs.has(obj): # type: ignore return obj if hasattr(obj, "__attrs_init__"): @@ -161,9 +161,9 @@ def add_init_annotations(obj: _C) -> _C: annotations: Dict[str, Optional[Type]] = {"return": None} - attrs = attr.fields(obj) + fields = attrs.fields(obj) - for a in attrs: + for a in fields: arg_name = a.name.lstrip('_') if a.init is True and a.type is not None: @@ -197,13 +197,13 @@ def add_init_annotations(obj: _C) -> _C: def attrib( - default=attr.NOTHING, + default=attrs.NOTHING, validator=None, repr: bool = True, # noqa: A002 # pylint: disable=redefined-builtin hash=None, # noqa: A002 # pylint: disable=redefined-builtin init=True, metadata=None, - annotation: Union[Type, object] = attr.NOTHING, + annotation: Union[Type, object] = attrs.NOTHING, converter=None, factory=None, kw_only: bool = False, @@ -212,7 +212,7 @@ def attrib( **kwargs, ): r""" - Wrapper around :func:`attr.ib` which supports the ``annotation`` + Wrapper around :func:`attrs.field` which supports the ``annotation`` keyword argument for use by :func:`~.add_init_annotations`. .. versionadded:: 0.2.0 @@ -231,16 +231,16 @@ def attrib( :param eq: :param order: - See the documentation for :func:`attr.ib` for descriptions of the other arguments. + See the documentation for :func:`attrs.field` for descriptions of the other arguments. """ # noqa: D400 - if annotation is not attr.NOTHING: + if annotation is not attrs.NOTHING: if metadata is None: metadata = {} metadata["annotation"] = annotation - return attr.ib( + return attrs.field( default=default, validator=validator, repr=repr, @@ -309,7 +309,7 @@ def parse_occupations(occupations: Iterable[str]) -> Iterable[str]: # pragma: n return [str(x) for x in occupations] -@attr.s +@attrs.define class AttrsClass: """ Example of using :func:`~.add_init_annotations` for attrs_ classes with Sphinx documentation. @@ -321,6 +321,6 @@ class AttrsClass: :param occupations: The occupation(s) of the person. """ - name: str = attr.ib(converter=str) - age: int = attr.ib(converter=int) - occupations: List[str] = attr.ib(converter=parse_occupations) + name: str = attrs.field(converter=str) + age: int = attrs.field(converter=int) + occupations: List[str] = attrs.field(converter=parse_occupations) diff --git a/attr_utils/autoattrs.py b/attr_utils/autoattrs.py index 32ced05..487f1f9 100644 --- a/attr_utils/autoattrs.py +++ b/attr_utils/autoattrs.py @@ -101,7 +101,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, MutableMapping, Optional, Tuple, Type # 3rd party -import attr +import attrs from sphinx.application import Sphinx # nodep from sphinx.ext.autodoc import ClassDocumenter, Documenter # nodep from sphinx.pycode import ModuleAnalyzer # nodep @@ -181,7 +181,7 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: .. latex:clearpage:: """ - return attr.has(member) and isinstance(member, type) + return attrs.has(member) and isinstance(member, type) def add_content(self, more_content: Any, no_docstring: bool = False) -> None: # type: ignore """ @@ -244,7 +244,7 @@ def import_object(self, raiseerror: bool = False) -> bool: ret = super().import_object(raiseerror) - if attr.has(self.object): + if attrs.has(self.object): self.object = add_attrs_doc(self.object) return ret @@ -281,7 +281,7 @@ def sort_members( params, pre_output, post_output = self._get_docstring() all_docs = {} - for field in (a.name for a in attr.fields(self.object) if a.init): + for field in (a.name for a in attrs.fields(self.object) if a.init): doc: List[str] = [''] # Prefer doc from class docstring @@ -344,7 +344,7 @@ def filter_members( :param want_all: """ # noqa: D400 - attrib_names = (a.name for a in attr.fields(self.object) if a.init) + attrib_names = (a.name for a in attrs.fields(self.object) if a.init) no_init_attribs = self.options.get("no-init-attribs", False) diff --git a/attr_utils/mypy_plugin.py b/attr_utils/mypy_plugin.py index 5f4f76b..98c0d7e 100644 --- a/attr_utils/mypy_plugin.py +++ b/attr_utils/mypy_plugin.py @@ -141,7 +141,7 @@ def attr_utils_serialise_serde(cls_def_ctx: ClassDefContext) -> None: # # def attr__make_attrs(cls_def_ctx: ClassDefContext): # """ -# Handles :func:attr.ib`. +# Handles :func:attrs.field`. # # :param cls_def_ctx: # """ @@ -149,7 +149,7 @@ def attr_utils_serialise_serde(cls_def_ctx: ClassDefContext) -> None: # info = cls_def_ctx.cls.info # # list_ = cls_def_ctx.api.lookup_fully_qualified_or_none('typing.List') -# attribute = cls_def_ctx.api.lookup_fully_qualified_or_none('attr.Attribute') +# attribute = cls_def_ctx.api.lookup_fully_qualified_or_none('attrs.Attribute') # # # # if "__attrs_attrs__" not in info.names: # # info.names["__attrs_attrs__"] = SymbolTableNode( @@ -159,9 +159,9 @@ def attr_utils_serialise_serde(cls_def_ctx: ClassDefContext) -> None: # # ) decorator_registry["attr_utils.serialise.serde"] = attr_utils_serialise_serde -# decorator_registry["attr._make.attrs"] = attr__make_attrs -# decorator_registry["attr.s"] = attr__make_attrs -# decorator_registry["attr.attrs"] = attr__make_attrs +# decorator_registry["attrs._make.attrs"] = attr__make_attrs +# decorator_registry["attrs.field"] = attr__make_attrs +# decorator_registry["attrs.attrs"] = attr__make_attrs class AttrUtilsPlugin(Plugin): diff --git a/attr_utils/pprinter.py b/attr_utils/pprinter.py index d391599..37632aa 100644 --- a/attr_utils/pprinter.py +++ b/attr_utils/pprinter.py @@ -56,7 +56,7 @@ from typing import Any, Callable, Optional, Type, TypeVar, Union # 3rd party -import attr +import attrs from typing_extensions import Protocol, runtime_checkable try: @@ -175,13 +175,13 @@ def pretty_repr(obj: Type): .. code-block:: python - >>> import attr + >>> import attrs >>> from attr_utils.pprinter import pretty_repr >>> @pretty_repr - ... @attr.s + ... @attrs.define ... class Person(object): - ... name = attr.ib() + ... name = attrs.field() >>> repr(Person(name="Bob")) Person(name='Bob') @@ -189,7 +189,7 @@ def pretty_repr(obj: Type): :param obj: """ - if attr.has(obj): + if attrs.has(obj): def __repr__(self) -> str: return prettyprinter.pformat(self) diff --git a/attr_utils/serialise.py b/attr_utils/serialise.py index ce4a79c..f7c1f01 100644 --- a/attr_utils/serialise.py +++ b/attr_utils/serialise.py @@ -13,7 +13,7 @@ .. code-block:: python - >>> import attr + >>> import attrs >>> from attr_utils.serialise import serde >>> person_dict = {"contact": {"personal": {"name": "John"}, "phone": "555-112233"}} @@ -22,10 +22,10 @@ >>> phone_path = ["contact", "phone"] >>> @serde - ... @attr.s + ... @attrs.define ... class Person(object): - ... name = attr.ib(metadata={"to": name_path, "from": name_path}) - ... phone = attr.ib(metadata={"to": phone_path, "from": phone_path}) + ... name = attrs.field(metadata={"to": name_path, "from": name_path}) + ... phone = attrs.field(metadata={"to": phone_path, "from": phone_path}) >>> p = Person.from_dict(person_dict) Person(name=John phone=555-112233) @@ -108,18 +108,18 @@ def serde( Decorator to add serialisation and deserialisation capabilities to attrs classes. The keys used in the dictionary output, and used when creating the class from a dictionary, - can be controlled using the ``metadata`` argument in :func:`attr.ib`: + can be controlled using the ``metadata`` argument in :func:`attrs.field`: .. code-block:: from attr_utils.serialize import serde - import attr + import attrs @serde - @attr.s + @attrs.define class Person(object): - name = attr.ib(metadata={"to": name_path, "from": name_path}) - phone = attr.ib(metadata={"to": phone_path, "from": phone_path}) + name = attrs.field(metadata={"to": name_path, "from": name_path}) + phone = attrs.field(metadata={"to": phone_path, "from": phone_path}) The names of the keys given in the ``metadata`` argument can be controlled with the ``from_key`` and ``to_key`` arguments: @@ -127,13 +127,13 @@ class Person(object): .. code-block:: from attr_utils.serialize import serde - import attr + import attrs @serde(from_key="get", to_key="set") - @attr.s + @attrs.define class Person(object): - name = attr.ib(metadata={"get": name_path, "set": name_path}) - phone = attr.ib(metadata={"get": phone_path, "set": phone_path}) + name = attrs.field(metadata={"get": name_path, "set": name_path}) + phone = attrs.field(metadata={"get": phone_path, "set": phone_path}) This may be required when using other extensions to attrs. diff --git a/demo.py b/demo.py index 1725d03..ef67c68 100644 --- a/demo.py +++ b/demo.py @@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, overload # 3rd party -import attr +import attrs from domdf_python_tools.utils import strtobool # this package @@ -17,37 +17,37 @@ @pretty_repr @serde -@attr.s(slots=True) +@attrs.define(slots=True, order=True) class Device: """ Represents a device in an :class:`~.AcqMethod`. """ #: The ID of the device - device_id: str = attr.ib(converter=str) + device_id: str = attrs.field(converter=str) #: The display name for the device. - display_name: str = attr.ib(converter=str) + display_name: str = attrs.field(converter=str) - rc_device: bool = attr.ib(converter=strtobool) + rc_device: bool = attrs.field(converter=strtobool) """ Flag to indicate the device is an RC Device. If :py:obj:`False` the device is an SCIC. """ #: List of key: value mappings for configuration options. - configuration: List[Dict[str, Any]] = attr.ib(converter=list, factory=list) + configuration: List[Dict[str, Any]] = attrs.field(converter=list, factory=list) #: Alternative form of ``configuration``. - configuration2: Tuple[Dict[str, Any]] = attr.ib( + configuration2: Tuple[Dict[str, Any]] = attrs.field( converter=tuple, - default=attr.Factory(tuple), + default=attrs.Factory(tuple), ) #: Alternative form of ``configuration``. - configuration3: List[Dict[str, Any]] = attr.ib( + configuration3: List[Dict[str, Any]] = attrs.field( converter=list, - default=attr.Factory(list), + default=attrs.Factory(list), metadata={"annotation": Sequence[Dict[str, Any]]}, ) @@ -76,7 +76,7 @@ def __getitem__(self, item: Union[int, slice]) -> Union[str, List[str]]: """ -@attr.s(init=False) +@attrs.define(init=False, order=True) class Connector: """ Represents an electrical connector. @@ -87,10 +87,10 @@ class Connector: """ #: The name of the connector - name: str = attr.ib(converter=str) + name: str = attrs.field(converter=str) #: The number of pins - n_pins: int = attr.ib(converter=int) + n_pins: int = attrs.field(converter=int) def __init__(self, name: str, n_pins: Optional[int] = None, right_angle: bool = False): if name == "DA-15": diff --git a/repo_helper.yml b/repo_helper.yml index 53790c5..1e73a61 100644 --- a/repo_helper.yml +++ b/repo_helper.yml @@ -123,7 +123,7 @@ keywords: third_party_version_matrix: attrs: - - 21.2 + - 21.3 - 21.4 - 22.2 - 23.1 diff --git a/requirements.txt b/requirements.txt index d00de9f..31eb9ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -attrs>=21.2.0 +attrs>=21.3.0 domdf-python-tools>=3.6.1 toolz>=0.11.1 typing-extensions>=4.0.0 diff --git a/tests/test_annotations.py b/tests/test_annotations.py index 167bf2d..78e428c 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Tuple, get_type_hints # 3rd party -import attr +import attrs from coincidence import PEP_563 # this package @@ -19,12 +19,12 @@ def untyped_converter(arg): return arg -@attr.s +@attrs.define class SomeClass: - a_string: str = attr.ib(converter=str) - custom_converter: Any = attr.ib(converter=my_converter) - untyped: Tuple[str, int, float] = attr.ib(converter=untyped_converter) - no_converter: Callable[[str], None] = attr.ib() + a_string: str = attrs.field(converter=str) + custom_converter: Any = attrs.field(converter=my_converter) + untyped: Tuple[str, int, float] = attrs.field(converter=untyped_converter) + no_converter: Callable[[str], None] = attrs.field() def test_add_init_annotations(): diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index db5031e..e036ccc 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -2,7 +2,7 @@ from typing import Any, Dict, List, Tuple # 3rd party -import attr +import attrs from domdf_python_tools.compat import importlib_metadata # this package @@ -17,11 +17,11 @@ def untyped_converter(arg): return arg -@attr.s +@attrs.define(order=True) class SomeClass: - a_string: str = attr.ib(converter=str) - custom_converter: Any = attr.ib(converter=my_converter) - untyped: Tuple[str, int, float] = attr.ib(converter=untyped_converter) + a_string: str = attrs.field(converter=str) + custom_converter: Any = attrs.field(converter=my_converter) + untyped: Tuple[str, int, float] = attrs.field(converter=untyped_converter) def test_add_attrs_doc(): diff --git a/tests/test_mypy_plugin.yml b/tests/test_mypy_plugin.yml index 572c33e..29914db 100644 --- a/tests/test_mypy_plugin.yml +++ b/tests/test_mypy_plugin.yml @@ -1,12 +1,12 @@ - case: mypy_path_from_env main: | from attr_utils.serialise import serde - import attr + import attrs @serde - @attr.s + @attrs.define class F: - foo: int = attr.ib() + foo: int = attrs.field() for key, value in F(42).to_dict().items(): print(key, value) diff --git a/tests/test_output/doc-test/test-root/annotations.rst b/tests/test_output/doc-test/test-root/annotations.rst index 2f7b9f6..47dbac6 100644 --- a/tests/test_output/doc-test/test-root/annotations.rst +++ b/tests/test_output/doc-test/test-root/annotations.rst @@ -7,3 +7,4 @@ The Sphinx output looks like: .. autoclass:: attr_utils.annotations.AttrsClass :members: :no-special-members: + :exclude-members: name,age,occupations diff --git a/tests/test_output/doc-test/test-root/docstrings.rst b/tests/test_output/doc-test/test-root/docstrings.rst index 2264973..3410194 100644 --- a/tests/test_output/doc-test/test-root/docstrings.rst +++ b/tests/test_output/doc-test/test-root/docstrings.rst @@ -6,10 +6,10 @@ The Sphinx output looks like: .. automodule:: attr_utils.annotations :members: AttrsClass - :exclude-members: __init__,__weakref__,__lt__,__le__,__hash__ + :exclude-members: __init__,__weakref__,__lt__,__le__,__hash__,__getstate__,__setattr__,__setstate__ :special-members: .. autoattrs:: attr_utils.annotations.AttrsClass :members: - :exclude-members: __init__,__weakref__,__lt__,__le__,__hash__ + :exclude-members: __init__,__weakref__,__lt__,__le__,__hash__,__getstate__,__setattr__,__setstate__ :special-members: diff --git a/tests/test_output/test_output_/test_html_output_docstrings_html_.html b/tests/test_output/test_output_/test_html_output_docstrings_html_.html index 9eb1e9e..1a5921b 100644 --- a/tests/test_output/test_output_/test_html_output_docstrings_html_.html +++ b/tests/test_output/test_output_/test_html_output_docstrings_html_.html @@ -166,7 +166,7 @@

which will construct the metadata dict and call - attr.ib() + attrs.field() for you. @@ -223,12 +223,12 @@

6 return arg 7 8 - 9@attr.s + 9@attrs.define 10class SomeClass: -11 a_string: str = attr.ib(converter=str) -12 custom_converter: Any = attr.ib(converter=my_converter) -13 untyped: Tuple[str, int, float] = attr.ib(converter=untyped_converter) -14 annotated: List[str] = attr.ib( +11 a_string: str = attrs.field(converter=str) +12 custom_converter: Any = attrs.field(converter=my_converter) +13 untyped: Tuple[str, int, float] = attrs.field(converter=untyped_converter) +14 annotated: List[str] = attrs.field( 15 converter=list, 16 metadata={"annotation": Sequence[str]}, 17 ) @@ -374,6 +374,80 @@

+
+ + + age + + + + : + + + int + + + + ΒΆ + + +
+

+ The age of the person. +

+
+
+
+
+ + name + + + + : + + + str + + + + ΒΆ + +
+
+

+ The name of the person. +

+
+
+
+
+ + occupations + + + + : + + + typing.List + + [ + + str + + ] + + + ΒΆ + +
+
+

+ The occupation(s) of the person. +

+
+
@@ -548,9 +622,9 @@

- + <{{ sig_prename_tag }} class="sig-name descname"> - __ge__ + __ne__ ( @@ -563,7 +637,7 @@

) - + ΒΆ @@ -575,7 +649,7 @@

self - >= + != other @@ -600,39 +674,30 @@

- + <{{ sig_prename_tag }} class="sig-name descname"> - __gt__ + __repr__ ( - - - other - - ) - + ΒΆ

- Return - - - self - - - > - - - other - - + Return a string representation of the + + + + AttrsClass + + + .

@@ -643,7 +708,7 @@

- bool + str

@@ -651,106 +716,85 @@

-
- - <{{ sig_prename_tag }} class="sig-name descname"> - __ne__ - - - ( +
+ + + age - - - other + + + : + + int + - - ) - - + ΒΆ

- Return - - - self - - - != - - - other - - - . + The age of the person.

-
-
- Return type{{ d18_colon(' ') }} -
-
-

- - - bool - - -

-
-
-
- - <{{ sig_prename_tag }} class="sig-name descname"> - __repr__ - - - ( +
+ + + name - - ) + + + : + + + str + + + + ΒΆ + + +
+

+ The name of the person. +

+
+
+
+ + + occupations - + + + : + + + typing.List + + [ + + str + + ] + + ΒΆ

- Return a string representation of the - - - - AttrsClass - - - - . + The occupation(s) of the person.

-
-
- Return type{{ d18_colon(' ') }} -
-
-

- - - str - - -

-
-
- + class @@ -783,7 +827,7 @@

) - + ΒΆ @@ -859,7 +903,7 @@

- + <{{ sig_prename_tag }} class="sig-name descname"> __eq__ @@ -874,7 +918,7 @@

) - + ΒΆ @@ -911,9 +955,9 @@

- + <{{ sig_prename_tag }} class="sig-name descname"> - __ge__ + __ne__ ( @@ -926,7 +970,7 @@

) - + ΒΆ @@ -938,7 +982,7 @@

self - >= + != other @@ -963,50 +1007,44 @@

- + <{{ sig_prename_tag }} class="sig-name descname"> - __gt__ + __repr__ ( - - - other - - ) - + ΒΆ

- Return - - - self - - - > - - - other - - + Return a string representation of the + + + + AttrsClass + + + .

- Return type{{ d18_colon(' ') }} + Return type + + : +

- bool + str

@@ -1014,99 +1052,78 @@

-
- +
+ <{{ sig_prename_tag }} class="sig-name descname"> - __ne__ + age - - ( - - - - other + + + : + + int + - - ) - - + ΒΆ

- Return - - - self - - - != - - - other - - - . + The age of the person.

-
-
- Return type{{ d18_colon(' ') }} -
-
-

- - - bool - - -

-
-
-
- - <{{ sig_prename_tag }} class="sig-name descname"> - __repr__ - - - ( +
+ + + name - - ) + + + : + + + str + + + + ΒΆ + + +
+

+ The name of the person. +

+
+
+
+ + + occupations - + + + : + + + typing.List + + [ + + str + + ] + + ΒΆ

- Return a string representation of the - - - - AttrsClass - - - - . + The occupation(s) of the person.

-
-
- Return type{{ d18_colon(' ') }} -
-
-

- - - str - - -

-
-
diff --git a/tests/test_pprinter.py b/tests/test_pprinter.py index cf5b702..d0ee567 100644 --- a/tests/test_pprinter.py +++ b/tests/test_pprinter.py @@ -8,7 +8,7 @@ from typing import Any, Dict, get_type_hints # 3rd party -import attr +import attrs from coincidence import PEP_563 # this package @@ -29,23 +29,23 @@ class Port(IntEnum): @pretty_repr -@attr.s(slots=True) +@attrs.define(slots=True) class Device: """ Represents a device in an :class:`~.AcqMethod`. """ #: The ID of the device - device_id: int = attr.ib(converter=int) + device_id: int = attrs.field(converter=int) #: The display name for the device. - display_name: str = attr.ib(converter=str) + display_name: str = attrs.field(converter=str) #: The type of device. - device_type: DeviceType = attr.ib(converter=DeviceType) + device_type: DeviceType = attrs.field(converter=DeviceType) #: Key: value mapping of configuration options. - configuration: Dict[str, Any] = attr.ib(default=attr.Factory(dict)) + configuration: Dict[str, Any] = attrs.field(default=attrs.Factory(dict)) def test_device(): diff --git a/tests/test_serialise.py b/tests/test_serialise.py index 1d14f35..40c2159 100644 --- a/tests/test_serialise.py +++ b/tests/test_serialise.py @@ -6,7 +6,7 @@ from typing import Any, Dict, Mapping, MutableMapping, get_type_hints, no_type_check # 3rd party -import attr +import attrs import sdjson from coincidence import PEP_563 from sdjson import register_encoder @@ -30,32 +30,32 @@ class Port(IntEnum): @serde -@attr.s(slots=True) +@attrs.define(slots=True) class Device: """ Represents a device in an :class:`~.AcqMethod`. """ #: The ID of the device - device_id: int = attr.ib(converter=int) + device_id: int = attrs.field(converter=int) #: The display name for the device. - display_name: str = attr.ib(converter=str) + display_name: str = attrs.field(converter=str) #: The type of device. - device_type: DeviceType = attr.ib(converter=DeviceType) + device_type: DeviceType = attrs.field(converter=DeviceType) #: Key: value mapping of configuration options. - configuration: MutableMapping[str, Any] = attr.ib(default=attr.Factory(dict)) + configuration: MutableMapping[str, Any] = attrs.field(default=attrs.Factory(dict)) -@attr.s(slots=True) +@attrs.define(slots=True) class EnhancedDevice(Device): """ A :class:`~.Device` with more features! """ - warp_drive: Literal["Engaged!"] = attr.ib(default="Engaged!") + warp_drive: Literal["Engaged!"] = attrs.field(default="Engaged!") class MagicMapping(dict): @@ -128,7 +128,7 @@ def test_enhanced_device(): }, ) - assert attr.asdict(e) == { + assert attrs.asdict(e) == { "device_id": 1000, "display_name": "Television", "device_type": DeviceType.RC, diff --git a/tox.ini b/tox.ini index e6fb674..ce75e12 100644 --- a/tox.ini +++ b/tox.ini @@ -21,16 +21,16 @@ [tox] envlist = - py37-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py38-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py39-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py310-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py311-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py312-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy37-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy38-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy39-attrs{21.2,21.4,22.2,23.1,23.2,latest} + py37-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py38-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py39-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py310-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py311-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py312-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy37-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy38-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy39-attrs{21.3,21.4,22.2,23.1,23.2,latest} py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1} py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest} py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest} @@ -60,16 +60,16 @@ requires = [envlists] test = - py37-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py38-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py39-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py310-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py311-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py312-attrs{21.2,21.4,22.2,23.1,23.2,latest} - py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy37-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy38-attrs{21.2,21.4,22.2,23.1,23.2,latest} - pypy39-attrs{21.2,21.4,22.2,23.1,23.2,latest} + py37-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py38-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py39-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py310-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py311-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py312-attrs{21.3,21.4,22.2,23.1,23.2,latest} + py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy37-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy38-attrs{21.3,21.4,22.2,23.1,23.2,latest} + pypy39-attrs{21.3,21.4,22.2,23.1,23.2,latest} py37-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1} py38-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest} py39-mypy{0.900,0.910,0.921,0.931,1.0.1,1.2.0,1.4.1,1.6.1,latest} @@ -88,7 +88,7 @@ test = pypy38-sphinx{4,5,6,7} pypy39-sphinx{4,5,6,7} qa = mypy, lint -cov = py38-attrs21.2, coverage +cov = py38-attrs21.3, coverage [testenv] setenv = @@ -97,7 +97,7 @@ setenv = SETUPTOOLS_USE_DISTUTILS=stdlib deps = -r{toxinidir}/tests/requirements.txt - attrs21.2: attrs~=21.2.0 + attrs21.3: attrs~=21.3.0 attrs21.4: attrs~=21.4.0 attrs22.2: attrs~=22.2.0 attrs23.1: attrs~=23.1.0 @@ -286,7 +286,7 @@ ignore = W002 toplevel = attr_utils package = attr_utils -[testenv:py312-attrs{21.2,21.4,22.2,23.1,23.2,latest}] +[testenv:py312-attrs{21.3,21.4,22.2,23.1,23.2,latest}] setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 @@ -301,7 +301,7 @@ setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 -[testenv:py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest}] +[testenv:py313-dev-attrs{21.3,21.4,22.2,23.1,23.2,latest}] setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 @@ -319,6 +319,17 @@ setenv = PIP_DISABLE_PIP_VERSION_CHECK=1 UNSAFE_PYO3_SKIP_VERSION_CHECK=1 +[testenv:py312-attrs{21.2,21.4,22.2,23.1,23.2,latest}] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + +[testenv:py313-dev-attrs{21.2,21.4,22.2,23.1,23.2,latest}] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + UNSAFE_PYO3_SKIP_VERSION_CHECK=1 + [testenv:py{37,38,39,310,311}-mypy{0.900,0.910,0.921,0.931}] setenv = PYTHONDEVMODE=1