Skip to content

Commit

Permalink
Merge pull request #130 from moshez/127-doc-assert
Browse files Browse the repository at this point in the history
Fix #127: Add explanation for how to use assert to pacify mypy
  • Loading branch information
glyph committed Apr 11, 2021
2 parents c8e76be + cec0c02 commit 4bfa4af
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Expand Up @@ -55,3 +55,4 @@ You can read more about state machines and their advantages for Python programme
visualize
api
debugging
typing
95 changes: 95 additions & 0 deletions docs/typing.rst
@@ -0,0 +1,95 @@
Static Typing
--------------

When writing an output for a given state,
you can assume the finite state machine will be in that state.
This might mean that specific object attributes will have values
of speciifc types.

This comment has been minimized.

Copy link
@graingert

graingert Apr 12, 2021

 of specific types.
Those attributes might,
in general,
be of some :code:`Union` type:
frequently,
an :code:`Option` type
(which is a :code:`Union[T, None]`).

It is an *anti-pattern* to check for these things inside the output.
The reason for a state machine is for the outputs to avoid checking.
However,
if the output is type annotated,
often :code:`mypy`
will complain that it cannot validate the types.
The recommended solution is to
:code:`assert`
the types inside the code.
This aligns
the assumptions
:code:`mypy`
makes
with the assumptions
:code:`automat`
makes.

For example,
consider the following:

.. code::
import attr
import automat
from typing import Optional
@attr.s(auto_attribs=True)
class MaybeValue:
_machine = automat.MethodicalMachine()
_value: Optional[float] = attr.ib(default=None)
@_machine.input()
def set_value(self, value: float) -> None:
"The value has been measured"
@_machine.input()
def get_value(self) -> float:
"Return the value if it has been measured"
@_machine.output()
def _set_value_when_unset(self, value: float) -> None:
self._value = value
@_machine.output()
def _get_value_when_set(self) -> float:
"""mypy will complain here:
Incompatible return value type
(got "Optional[float]", expected "float")
"""
return self._value
@_machine.state()
def value_is_set(self):
"The value is set"
@_machine.state(initial=True)
def value_is_unset(self):
"The value is not set"
value_is_unset.upon(
set_value,
enter=value_is_set,
outputs=[_set_value_when_unset],
collector=lambda x: None,
)
value_is_set.upon(
get_value,
enter=value_is_set,
outputs=[_get_value_when_set],
collector=lambda x: next(iter(x)),
)
In this case
starting
:code:`_get_value_when_set`
with a line
:code:`assert self._value is not None`
will satisfy
:code:`mypy`.

10 changes: 10 additions & 0 deletions tox.ini
Expand Up @@ -38,3 +38,13 @@ commands = {[testenv:benchmark]commands}
[testenv:pypy3-benchmark]
deps = {[testenv:benchmark]deps}
commands = {[testenv:benchmark]commands}

[testenv:docs]
usedevelop = True
changedir = docs
deps =
sphinx
sphinx_rtd_theme
commands =
sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
basepython = python3.8

0 comments on commit 4bfa4af

Please sign in to comment.