Skip to content

Commit

Permalink
Use MyST-native doctest blocks in all MD
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Dec 20, 2022
1 parent 403adab commit 519423d
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 192 deletions.
22 changes: 10 additions & 12 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,18 +408,16 @@ Therefore if you use `@validator`, it is *not* enough to annotate said attribute

*attrs* ships with a bunch of validators, make sure to [check them out](api-validators) before writing your own:

```{eval-rst}
.. doctest::
>>> @define
... class C:
... x: int = field(validator=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')
```{doctest}
>>> @define
... class C:
... x: int = field(validator=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')
```

Please note that if you use {func}`attr.s` (and **not** {func}`attrs.define`) to define your class, validators only run on initialization by default -- not when you set an attribute.
Expand Down
81 changes: 38 additions & 43 deletions docs/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Here are some tips for effective use of metadata:

- To avoid metadata key collisions, consider exposing your metadata keys from your modules.:

```
```python
from mylib import MY_METADATA_KEY

@define
Expand All @@ -173,31 +173,28 @@ Here are some tips for effective use of metadata:
- Expose `field` wrappers for your specific metadata.
This is a more graceful approach if your users don't require metadata from other libraries.

```{eval-rst}
.. doctest::
>>> from attr import fields, NOTHING
>>> MY_TYPE_METADATA = '__my_type_metadata'
>>>
>>> def typed(
... cls, default=NOTHING, validator=None, repr=True,
... eq=True, order=None, hash=None, init=True, metadata=None,
... converter=None
... ):
... metadata = metadata or {}
... metadata[MY_TYPE_METADATA] = cls
... return field(
... default=default, validator=validator, repr=repr,
... eq=eq, order=order, hash=hash, init=init,
... metadata=metadata, converter=converter
... )
>>>
>>> @define
... class C:
... x: int = typed(int, default=1, init=False)
>>> fields(C).x.metadata[MY_TYPE_METADATA]
<class 'int'>
```{doctest}
>>> from attrs import fields, NOTHING
>>> MY_TYPE_METADATA = '__my_type_metadata'
>>>
>>> def typed(
... cls, default=NOTHING, validator=None, repr=True,
... eq=True, order=None, hash=None, init=True, metadata=None,
... converter=None
... ):
... metadata = metadata or {}
... metadata[MY_TYPE_METADATA] = cls
... return field(
... default=default, validator=validator, repr=repr,
... eq=eq, order=order, hash=hash, init=init,
... metadata=metadata, converter=converter
... )
>>>
>>> @define
... class C:
... x: int = typed(int, default=1, init=False)
>>> fields(C).x.metadata[MY_TYPE_METADATA]
<class 'int'>
```

(transform-fields)=
Expand Down Expand Up @@ -293,24 +290,22 @@ Data(public=42, _private='spam', explicit='yes')
*attrs* allows you to serialize instances of *attrs* classes to dicts using the {func}`attrs.asdict` function.
However, the result can not always be serialized since most data types will remain as they are:

```{eval-rst}
.. doctest::
>>> import json
>>> import datetime
>>> from attrs import asdict
>>>
>>> @frozen
... class Data:
... dt: datetime.datetime
```{doctest}
>>> import json
>>> import datetime
>>> from attrs import asdict
>>>
>>> @frozen
... class Data:
... dt: datetime.datetime
...
>>> data = asdict(Data(datetime.datetime(2020, 5, 4, 13, 37)))
>>> data
{'dt': datetime.datetime(2020, 5, 4, 13, 37)}
>>> json.dumps(data)
Traceback (most recent call last):
...
>>> data = asdict(Data(datetime.datetime(2020, 5, 4, 13, 37)))
>>> data
{'dt': datetime.datetime(2020, 5, 4, 13, 37)}
>>> json.dumps(data)
Traceback (most recent call last):
...
TypeError: Object of type datetime is not JSON serializable
TypeError: Object of type datetime is not JSON serializable
```

To help you with this, {func}`~attrs.asdict` allows you to pass a *value_serializer* hook.
Expand Down
26 changes: 12 additions & 14 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,18 @@ slotted classes

- Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies `__slots__`:

```{eval-rst}
.. doctest::
>>> from attr import define
>>> @define
... class Coordinates:
... x: int
... y: int
...
>>> c = Coordinates(x=1, y=2)
>>> c.z = 3
Traceback (most recent call last):
...
AttributeError: 'Coordinates' object has no attribute 'z'
```{doctest}
>>> from attr import define
>>> @define
... class Coordinates:
... x: int
... y: int
...
>>> 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.
Expand Down
22 changes: 10 additions & 12 deletions docs/how-does-it-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@ While creating new classes is more elegant, we've run into several edge cases su

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:

```{eval-rst}
.. doctest::
>>> import inspect
>>> from attr import define
>>> @define
... class C:
... x: int
>>> print(inspect.getsource(C.__init__))
def __init__(self, x):
self.x = x
<BLANKLINE>
```{doctest}
>>> import inspect
>>> from attrs import define
>>> @define
... class C:
... x: int
>>> print(inspect.getsource(C.__init__))
def __init__(self, x):
self.x = x
<BLANKLINE>
```

No magic, no meta programming, no expensive introspection at runtime.
Expand Down
20 changes: 9 additions & 11 deletions docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@

However they will forever remain *optional*, therefore the example from the README could also be written as:

```{eval-rst}
.. doctest::
```{doctest}
>>> from attrs import define, field
>>> from attrs import define, field
>>> @define
... class SomeClass:
... a_number = field(default=42)
... list_of_numbers = field(factory=list)
>>> @define
... class SomeClass:
... a_number = field(default=42)
... list_of_numbers = field(factory=list)
>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
```

You can choose freely between the approaches, but please remember that if you choose to use type annotations, you **must** annotate **all** attributes!
Expand Down
Loading

0 comments on commit 519423d

Please sign in to comment.