From a518d9ddfe525eb59e5e640009be73b172a6059e Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Mon, 15 Jul 2024 11:27:08 +0200 Subject: [PATCH] docs: make more examplar --- docs/extending.md | 28 ++++++++++++++++------------ docs/types.md | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/extending.md b/docs/extending.md index 65d46883..c3a475ae 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -6,7 +6,7 @@ It's a tuple of {class}`attrs.Attribute` carrying metadata about each attribute. So it is fairly simple to build your own decorators on top of *attrs*: ```{doctest} ->>> from attr import define +>>> from attrs import define >>> def print_attrs(cls): ... print(cls.__attrs_attrs__) ... return cls @@ -50,8 +50,8 @@ Another common use case is to overwrite *attrs*'s defaults. ### Mypy -Unfortunately, decorator wrapping 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: +Unfortunately, decorator wrapping 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: ```python from mypy.plugin import Plugin @@ -79,7 +79,7 @@ def plugin(version): return MyPlugin ``` -Then tell *Mypy* about your plugin using your project's `mypy.ini`: +Then tell Mypy about your plugin using your project's `mypy.ini`: ```ini [mypy] @@ -87,21 +87,21 @@ plugins= ``` :::{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. +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. ::: ### Pyright -Generic decorator wrapping is supported in [*Pyright*](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`681`. +Generic decorator wrapping is supported in [Pyright](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`681`. For a custom wrapping of the form: ``` -@typing.dataclass_transform(field_specifiers=(attr.attrib, attr.field)) +@typing.dataclass_transform(field_specifiers=(attr.attrib, attrs.field)) def custom_define(f): - return attr.define(f) + return attrs.define(f) ``` ## Types @@ -109,16 +109,16 @@ def custom_define(f): *attrs* offers two ways of attaching type information to attributes: - {pep}`526` annotations, -- and the *type* argument to {func}`attr.ib`. +- and the *type* argument to {func}`attr.ib` / {func}`attrs.field`. This information is available to you: ```{doctest} ->>> from attr import attrib, define, field, fields +>>> from attrs import define, field, fields >>> @define ... class C: ... x: int = field() -... y = attrib(type=str) +... y = field(type=str) >>> fields(C).x.type >>> fields(C).y.type @@ -127,6 +127,10 @@ This information is available to you: 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! +Originally, we didn't add the *type* argument to the new {func}`attrs.field` API, because type annotations are the preferred way. +But we reintroduced it later, so `field` can be used with the {func}`attrs.make_class` function. +We strongly discourage the use of the *type* parameter outside of {func}`attrs.make_class`. + (extending-metadata)= ## Metadata diff --git a/docs/types.md b/docs/types.md index 5ab7146f..2d19548b 100644 --- a/docs/types.md +++ b/docs/types.md @@ -2,7 +2,7 @@ *attrs* comes with first-class support for type annotations for both {pep}`526` and legacy syntax. -However they will forever remain *optional*, therefore the example from the README could also be written as: +However, they will remain *optional* forever, therefore the example from the README could also be written as: ```{doctest} >>> from attrs import define, field @@ -32,7 +32,7 @@ SomeClass(a_number=42) ``` ::: -Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features though. +Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features, though. 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. @@ -46,20 +46,18 @@ Please note that types -- regardless how added -- are *only metadata* that can b 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* ({pep}`526`). -You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`) as of Python 3.7. +You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`). In this case *attrs* simply puts these string literals into the `type` attributes. If you need to resolve these to real types, you can call {func}`attrs.resolve_types` which will update the attribute in place. -In practice though, types show their biggest usefulness in combination with tools like [*Mypy*], [*pytype*], or [*Pyright*] that have dedicated support for *attrs* classes. +In practice though, types show their biggest usefulness in combination with tools like [Mypy], [*pytype*], or [Pyright] that have dedicated support for *attrs* classes. The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you write *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 -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. +While having a nice syntax for type metadata is great, it's even greater that [Mypy] 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: @@ -71,8 +69,8 @@ t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected 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: +And it also works with *both* legacy annotation styles. +To Mypy, this code is equivalent to the one above: ```python @attr.s @@ -81,31 +79,33 @@ class SomeClass: list_of_numbers = attr.ib(factory=list, type=list[int]) ``` +The approach used for `list_of_numbers` one is only a available in our [old-style API](names.md) which is why the example still uses it. + ## Pyright -*attrs* provides support for [*Pyright*] through the `dataclass_transform` / {pep}`681` specification. +*attrs* provides support for [Pyright] through the `dataclass_transform` / {pep}`681` specification. This provides static type inference for a subset of *attrs* equivalent to standard-library {mod}`dataclasses`, and requires explicit type annotations using the {func}`attrs.define` or `@attr.s(auto_attribs=True)` API. -Given the following definition, *Pyright* will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods: +Given the following definition, Pyright will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods: ``` -@attr.define +@attrs.define class SomeClass: a_number: int = 42 list_of_numbers: list[int] = attr.field(factory=list) ``` :::{warning} -The *Pyright* inferred types are a tiny subset of those supported by *Mypy*, including: +The Pyright inferred types are a tiny subset of those supported by Mypy, including: - The `attrs.frozen` decorator is not typed with frozen attributes, which are properly typed via `attrs.define(frozen=True)`. Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782). -Generally speaking, the decision on improving *attrs* support in *Pyright* is entirely Microsoft's prerogative, though. +Generally speaking, the decision on improving *attrs* support in Pyright is entirely Microsoft's prerogative and they unequivocally indicated that they'll only add support for features that go through the PEP process, though. ::: -[*Mypy*]: http://mypy-lang.org -[*Pyright*]: https://github.com/microsoft/pyright +[Mypy]: http://mypy-lang.org +[Pyright]: https://github.com/microsoft/pyright [*pytype*]: https://google.github.io/pytype/