Repository: python-attrs/attrs
Pull request #1090 (2023-01-19 18:10:02)

Add AttrsInstance protocol to forgotten attrs.asdict and attrs.astuple
# Summary

Fixes https://github.com/python-attrs/attrs/issues/1089

`AttrsInstance` protocol was added in https://github.com/python-attrs/attrs/pull/890 but two functions in the `attrs` namespace were apparently forgotten and still accept Any as the instance. This PR changes the signatures to accept `AttrsInstance` instead to match what's in the `attr` namespace.


# 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://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) 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.
-->

- [x] Added **tests** for changed code.
  Our CI fails if coverage is not 100%.
- [x] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py).
- [x] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``).
    - [x] ...and used in the stub test file `tests/typing_example.py`.
    - [x] If they've been added to `attr/__init__.pyi`, they've *also* been re-imported in `attrs/__init__.pyi`.
- [x] Updated **documentation** for changed code.
    - [x] New functions/classes have to be added to `docs/api.rst` by hand.
    - [x] Changes to the signature of `@attr.s()` have to be added by hand too.
    - [x] 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/main/src/attr/__init__.py) file.
- [x] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
- [x] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d).
- [x] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you.




hynek: @tsx ah sorry can you add a news fragment describing the change in one sentence? I'm traveling rn otherwise I would do it myself. Thanks!
tsx: @hynek done. I tried to make it short and to the point.

Patch:
diff --git a/changelog.d/1090.change.md b/changelog.d/1090.change.md
new file mode 100644
index 0000000..5452d16
--- /dev/null
+++ b/changelog.d/1090.change.md
@@ -0,0 +1,1 @@
+`attrs.asdict()`'s and `attrs.astuple()`'s type stubs now accept the `attrs.AttrsInstance` protocol.
diff --git a/src/attrs/__init__.pyi b/src/attrs/__init__.pyi
index 4ea64d8..9372cfe 100644
--- a/src/attrs/__init__.pyi
+++ b/src/attrs/__init__.pyi
@@ -46,7 +46,7 @@ from attr import validators as validators
 
 # TODO: see definition of attr.asdict/astuple
 def asdict(
-    inst: Any,
+    inst: AttrsInstance,
     recurse: bool = ...,
     filter: Optional[_FilterType[Any]] = ...,
     dict_factory: Type[Mapping[Any, Any]] = ...,
@@ -59,7 +59,7 @@ def asdict(
 
 # TODO: add support for returning NamedTuple from the mypy plugin
 def astuple(
-    inst: Any,
+    inst: AttrsInstance,
     recurse: bool = ...,
     filter: Optional[_FilterType[Any]] = ...,
     tuple_factory: Type[Sequence[Any]] = ...,


Test patch:
diff --git a/tests/test_mypy.yml b/tests/test_mypy.yml
index f0603a6..37b2ead 100644
--- a/tests/test_mypy.yml
+++ b/tests/test_mypy.yml
@@ -1372,6 +1372,25 @@
 
     fields(A)  # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected "Type[AttrsInstance]"  [arg-type]
 
+- case: testAsDict
+  main: |
+    from attrs import asdict, define
+
+    @define
+    class A:
+      a: int
+
+    asdict(A(1))
+
+- case: testAsDictError
+  main: |
+    from attrs import asdict
+
+    class A:
+      a: int
+
+    asdict(A())  # E: Argument 1 to "asdict" has incompatible type "A"; expected "AttrsInstance"  [arg-type]
+
 - case: testHasTypeGuard
   main: |
     from attrs import define, has