Repository: engnadeau/pybotics (language: python)

Issue:
Mypy linting failing with latest mypy 0.971
## Description

https://github.com/engnadeau/pybotics/runs/7933150413?check_suite_focus=true



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
index 5d8971e9..2fbeb422 100644
--- a/.flake8
+++ b/.flake8
@@ -13,4 +13,4 @@ max-complexity = 10
 import-order-style = pep8
 application-import-names = pybotics
 max-line-length = 88
-ignore = E741,W503
+extend-ignore = E203,E741
diff --git a/Makefile b/Makefile
index ce0076ac..f03361de 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ check-package:
 
 .PHONY: check-typing
 check-typing:
-	poetry run mypy --strict .
+	poetry run mypy --strict --show-error-codes .
 
 .PHONY: check-format
 check-format:
@@ -22,6 +22,11 @@ lint:
 .PHONY: check
 check: check-format check-package check-typing lint
 
+.PHONY: debug
+debug:
+	@echo "Git version: $(shell git describe --tags)"
+	poetry debug
+
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # formatting
 
diff --git a/README.md b/README.md
index 2903b3d1..d4877943 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ Social | ![Twitter Follow](https://img.shields.io/twitter/follow/engnadeau?style
   - [Citing](#citing)
   - [Development and Community Guidelines](#development-and-community-guidelines)
     - [Local Development](#local-development)
+    - [Docker Development](#docker-development)
     - [Dependency Management](#dependency-management)
     - [Submit an Issue](#submit-an-issue)
     - [Contributing](#contributing)
@@ -135,6 +136,23 @@ Please cite the following articles if you use `pybotics` in your research:
 poetry install
 ```
 
+### Docker Development
+
+- Docker is a great tool to test the package in an isolated environment
+- It is especially useful for debugging issues between python versions
+
+```bash
+# launch container attached to current directory
+docker run -v $(pwd):/$(basename $(pwd)) -w /$(basename $(pwd)) -it python:3 bash
+
+# install deps
+pip install poetry
+poetry install
+
+# run tests
+make test
+```
+
 ### Dependency Management
 
 ```bash
diff --git a/examples/basic_usage.py b/examples/basic_usage.py
index 6b238f47..ae020065 100644
--- a/examples/basic_usage.py
+++ b/examples/basic_usage.py
@@ -1,11 +1,13 @@
 """Basic usage of the pybotics package."""
+import numpy as np
+
 from pybotics.geometry import vector_2_matrix
 from pybotics.predefined_models import ur10
 from pybotics.robot import Robot
 from pybotics.tool import Tool
 
 
-def main():
+def main() -> None:
     """
     Demonstrate pybotics usage.
 
@@ -16,11 +18,11 @@ def main():
 
     # add tool
     tool = Tool()
-    tool.position = [1, 2, 3]
+    tool.position = np.array([1, 2, 3])
     robot.tool = tool
 
     # set world frame
-    world_frame = vector_2_matrix([100, 200, 300, 0, 0, 0])
+    world_frame = vector_2_matrix(np.array([100, 200, 300, 0, 0, 0]))
     robot.world_frame = world_frame
 
     print(f"Robot: {robot}")
diff --git a/poetry.lock b/poetry.lock
index 337d0b2c..6e3952d5 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -6,14 +6,6 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "appdirs"
-version = "1.4.4"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
-optional = false
-python-versions = "*"
-
 [[package]]
 name = "appnope"
 version = "0.1.0"
@@ -90,23 +82,28 @@ python-versions = "*"
 
 [[package]]
 name = "black"
-version = "19.10b0"
+version = "22.6.0"
 description = "The uncompromising code formatter."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.6.2"
 
 [package.dependencies]
-appdirs = "*"
-attrs = ">=18.1.0"
-click = ">=6.5"
-pathspec = ">=0.6,<1"
-regex = "*"
-toml = ">=0.9.4"
-typed-ast = ">=1.4.0"
+click = ">=8.0.0"
+ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""}
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""}
+tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
-d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+uvloop = ["uvloop (>=0.15.2)"]
+jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"]
+d = ["aiohttp (>=3.7.4)"]
+colorama = ["colorama (>=0.4.3)"]
 
 [[package]]
 name = "bleach"
@@ -153,11 +150,15 @@ unicode_backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
-version = "7.1.2"
+version = "8.1.3"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "colorama"
@@ -169,14 +170,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "coverage"
-version = "5.2.1"
+version = "6.4.4"
 description = "Code coverage measurement for Python"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+python-versions = ">=3.7"
 
 [package.extras]
-toml = ["toml"]
+toml = ["tomli"]
 
 [[package]]
 name = "cycler"
@@ -221,19 +222,30 @@ category = "dev"
 optional = false
 python-versions = ">=2.7"
 
+[[package]]
+name = "exceptiongroup"
+version = "1.0.0rc8"
+description = "Backport of PEP 654 (exception groups)"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)"]
+
 [[package]]
 name = "flake8"
-version = "3.8.3"
+version = "4.0.1"
 description = "the modular source code checker: pep8 pyflakes and co"
 category = "dev"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+python-versions = ">=3.6"
 
 [package.dependencies]
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""}
 mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.6.0a1,<2.7.0"
-pyflakes = ">=2.2.0,<2.3.0"
+pycodestyle = ">=2.8.0,<2.9.0"
+pyflakes = ">=2.4.0,<2.5.0"
 
 [[package]]
 name = "flake8-bugbear"
@@ -247,29 +259,6 @@ python-versions = ">=3.6"
 attrs = ">=19.2.0"
 flake8 = ">=3.0.0"
 
-[[package]]
-name = "flake8-docstrings"
-version = "1.5.0"
-description = "Extension for flake8 which uses pydocstyle to check docstrings"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-flake8 = ">=3"
-pydocstyle = ">=2.1"
-
-[[package]]
-name = "flake8-import-order"
-version = "0.18.1"
-description = "Flake8 and pylama plugin that checks the ordering of import statements."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-pycodestyle = "*"
-
 [[package]]
 name = "flake8-polyfill"
 version = "1.0.2"
@@ -283,28 +272,32 @@ flake8 = "*"
 
 [[package]]
 name = "hypothesis"
-version = "5.26.0"
+version = "6.54.4"
 description = "A library for property-based testing"
 category = "dev"
 optional = false
-python-versions = ">=3.5.2"
+python-versions = ">=3.7"
 
 [package.dependencies]
 attrs = ">=19.2.0"
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
 sortedcontainers = ">=2.1.0,<3.0.0"
 
 [package.extras]
-all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "numpy (>=1.9.0)", "pandas (>=0.19)", "pytest (>=4.3)", "python-dateutil (>=1.4)", "pytz (>=2014.1)"]
-cli = ["click (>=7.0)", "black (>=19.10b0)"]
+all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=1.0)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"]
+cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"]
+codemods = ["libcst (>=0.3.16)"]
 dateutil = ["python-dateutil (>=1.4)"]
-django = ["pytz (>=2014.1)", "django (>=2.2)"]
+django = ["django (>=3.2)"]
 dpcontracts = ["dpcontracts (>=0.4)"]
 ghostwriter = ["black (>=19.10b0)"]
 lark = ["lark-parser (>=0.6.5)"]
 numpy = ["numpy (>=1.9.0)"]
-pandas = ["pandas (>=0.19)"]
-pytest = ["pytest (>=4.3)"]
+pandas = ["pandas (>=1.0)"]
+pytest = ["pytest (>=4.6)"]
 pytz = ["pytz (>=2014.1)"]
+redis = ["redis (>=3.0.0)"]
+zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"]
 
 [[package]]
 name = "idna"
@@ -334,8 +327,8 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
+testing = ["importlib-resources (>=1.3)", "pep517", "packaging"]
+docs = ["rst.linker", "sphinx"]
 
 [[package]]
 name = "iniconfig"
@@ -404,16 +397,17 @@ python-versions = "*"
 
 [[package]]
 name = "isort"
-version = "5.4.2"
+version = "5.10.1"
 description = "A Python utility / library to sort Python imports."
 category = "dev"
 optional = false
-python-versions = ">=3.6,<4.0"
+python-versions = ">=3.6.1,<4.0"
 
 [package.extras]
-pipfile_deprecated_finder = ["pipreqs", "requirementslib", "tomlkit (>=0.5.3)"]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
 requirements_deprecated_finder = ["pipreqs", "pip-api"]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
 
 [[package]]
 name = "jedi"
@@ -488,48 +482,6 @@ traitlets = "*"
 [package.extras]
 test = ["async-generator", "ipykernel", "ipython", "mock", "pytest", "pytest-asyncio", "pytest-timeout"]
 
-[[package]]
-name = "jupyter-contrib-core"
-version = "0.3.3"
-description = "Common utilities for jupyter-contrib projects."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-jupyter-core = "*"
-notebook = ">=4.0"
-tornado = "*"
-traitlets = "*"
-
-[package.extras]
-testing_utils = ["nose", "mock"]
-
-[[package]]
-name = "jupyter-contrib-nbextensions"
-version = "0.5.1"
-description = "A collection of Jupyter nbextensions."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-ipython-genutils = "*"
-jupyter-contrib-core = ">=0.3.3"
-jupyter-core = "*"
-jupyter-highlight-selected-word = ">=0.1.1"
-jupyter-latex-envs = ">=1.3.8"
-jupyter-nbextensions-configurator = ">=0.4.0"
-lxml = "*"
-nbconvert = ">=4.2"
-notebook = ">=4.0"
-pyyaml = "*"
-tornado = "*"
-traitlets = ">=4.1"
-
-[package.extras]
-test = ["nbformat", "nose", "pip", "requests", "mock"]
-
 [[package]]
 name = "jupyter-core"
 version = "4.6.3"
@@ -542,48 +494,6 @@ python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7"
 pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""}
 traitlets = "*"
 
-[[package]]
-name = "jupyter-highlight-selected-word"
-version = "0.2.0"
-description = "Jupyter notebook extension that enables highlighting every instance of the current word in the notebook."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "jupyter-latex-envs"
-version = "1.4.6"
-description = "Jupyter notebook extension which supports (some) LaTeX environments within markdown cells. Also provides support for labels and crossreferences, document wide numbering, bibliography, and more..."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-ipython = "*"
-jupyter_core = "*"
-nbconvert = "*"
-notebook = ">=4.0"
-traitlets = ">=4.1"
-
-[[package]]
-name = "jupyter-nbextensions-configurator"
-version = "0.4.1"
-description = "jupyter serverextension providing configuration interfaces for nbextensions."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-jupyter_contrib_core = ">=0.3.3"
-jupyter_core = "*"
-notebook = ">=4.0"
-pyyaml = "*"
-tornado = "*"
-traitlets = "*"
-
-[package.extras]
-test = ["jupyter-contrib-core", "nose", "requests", "selenium", "mock"]
-
 [[package]]
 name = "kiwisolver"
 version = "1.2.0"
@@ -604,20 +514,6 @@ python-versions = "*"
 six = "*"
 tornado = {version = "*", markers = "python_version > \"2.7\""}
 
-[[package]]
-name = "lxml"
-version = "4.9.1"
-description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
-
-[package.extras]
-cssselect = ["cssselect (>=0.7)"]
-html5 = ["html5lib"]
-htmlsoup = ["beautifulsoup4"]
-source = ["Cython (>=0.29.7)"]
-
 [[package]]
 name = "markupsafe"
 version = "1.1.1"
@@ -659,28 +555,23 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "more-itertools"
-version = "8.4.0"
-description = "More routines for operating on iterables, beyond itertools"
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-
 [[package]]
 name = "mypy"
-version = "0.782"
+version = "0.971"
 description = "Optional static typing for Python"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.dependencies]
-mypy-extensions = ">=0.4.3,<0.5.0"
-typed-ast = ">=1.4.0,<1.5.0"
-typing-extensions = ">=3.7.4"
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=3.10"
 
 [package.extras]
+reports = ["lxml"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
 dmypy = ["psutil (>=4.0)"]
 
 [[package]]
@@ -776,11 +667,11 @@ test = ["pytest", "coverage", "requests", "nbval", "selenium", "pytest-cov", "re
 
 [[package]]
 name = "numpy"
-version = "1.19.1"
+version = "1.21.1"
 description = "NumPy is the fundamental package for array computing with Python."
 category = "main"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "packaging"
@@ -808,7 +699,7 @@ python-dateutil = ">=2.7.3"
 pytz = ">=2017.2"
 
 [package.extras]
-test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"]
+test = ["hypothesis (>=3.58)", "pytest-xdist", "pytest (>=4.0.2)"]
 
 [[package]]
 name = "pandocfilters"
@@ -827,15 +718,15 @@ optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [package.extras]
-testing = ["docopt", "pytest (>=3.0.7)"]
+testing = ["pytest (>=3.0.7)", "docopt"]
 
 [[package]]
 name = "pathspec"
-version = "0.8.0"
+version = "0.9.0"
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
 name = "pathtools"
@@ -891,6 +782,18 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "platformdirs"
+version = "2.5.2"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)", "pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "appdirs (==1.4.4)"]
+docs = ["sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)"]
+
 [[package]]
 name = "pluggy"
 version = "0.13.1"
@@ -953,11 +856,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pycodestyle"
-version = "2.6.0"
+version = "2.8.0"
 description = "Python style guide checker"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pycparser"
@@ -969,18 +872,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pydocstyle"
-version = "5.0.2"
+version = "6.1.1"
 description = "Python docstring style checker"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.dependencies]
 snowballstemmer = "*"
 
+[package.extras]
+toml = ["toml"]
+
 [[package]]
 name = "pyflakes"
-version = "2.2.0"
+version = "2.4.0"
 description = "passive checker of Python programs"
 category = "dev"
 optional = false
@@ -1015,27 +921,25 @@ six = "*"
 
 [[package]]
 name = "pytest"
-version = "6.0.1"
+version = "7.1.2"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
 
 [package.dependencies]
 atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
-attrs = ">=17.4.0"
+attrs = ">=19.2.0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 iniconfig = "*"
-more-itertools = ">=4.0.0"
 packaging = "*"
-pluggy = ">=0.12,<1.0"
+pluggy = ">=0.12,<2.0"
 py = ">=1.8.2"
-toml = "*"
+tomli = ">=1.0.0"
 
 [package.extras]
-checkqa_mypy = ["mypy (==0.780)"]
-testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+testing = ["xmlschema", "requests", "pygments (>=2.7.2)", "nose", "mock", "hypothesis (>=3.56)", "argcomplete"]
 
 [[package]]
 name = "pytest-cov"
@@ -1127,14 +1031,6 @@ category = "dev"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
 
-[[package]]
-name = "regex"
-version = "2020.7.14"
-description = "Alternative regular expression module, to replace re."
-category = "dev"
-optional = false
-python-versions = "*"
-
 [[package]]
 name = "requests"
 version = "2.26.0"
@@ -1155,14 +1051,14 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 
 [[package]]
 name = "rope"
-version = "0.17.0"
+version = "1.1.1"
 description = "a python refactoring library..."
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3"
 
 [package.extras]
-dev = ["pytest"]
+dev = ["pytest-timeout", "pytest", "build"]
 
 [[package]]
 name = "scikit-learn"
@@ -1289,7 +1185,7 @@ python-versions = "*"
 sphinx = "*"
 
 [package.extras]
-dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
+dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"]
 
 [[package]]
 name = "sphinxcontrib-apidoc"
@@ -1312,8 +1208,8 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
+lint = ["docutils-stubs", "mypy", "flake8"]
 
 [[package]]
 name = "sphinxcontrib-devhelp"
@@ -1324,8 +1220,8 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
+lint = ["docutils-stubs", "mypy", "flake8"]
 
 [[package]]
 name = "sphinxcontrib-htmlhelp"
@@ -1336,8 +1232,8 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-lint = ["flake8", "mypy", "docutils-stubs"]
-test = ["pytest", "html5lib"]
+test = ["html5lib", "pytest"]
+lint = ["docutils-stubs", "mypy", "flake8"]
 
 [[package]]
 name = "sphinxcontrib-jsmath"
@@ -1348,7 +1244,7 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-test = ["pytest", "flake8", "mypy"]
+test = ["mypy", "flake8", "pytest"]
 
 [[package]]
 name = "sphinxcontrib-qthelp"
@@ -1359,8 +1255,8 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
+lint = ["docutils-stubs", "mypy", "flake8"]
 
 [[package]]
 name = "sphinxcontrib-serializinghtml"
@@ -1371,8 +1267,8 @@ optional = false
 python-versions = ">=3.5"
 
 [package.extras]
-lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
+lint = ["docutils-stubs", "mypy", "flake8"]
 
 [[package]]
 name = "terminado"
@@ -1407,12 +1303,20 @@ optional = false
 python-versions = ">=3.5"
 
 [[package]]
-name = "toml"
-version = "0.10.1"
-description = "Python Library for Tom's Obvious, Minimal Language"
+name = "tokenize-rt"
+version = "4.2.1"
+description = "A wrapper around the stdlib `tokenize` which roundtrips."
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6.1"
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
 
 [[package]]
 name = "tornado"
@@ -1440,19 +1344,19 @@ test = ["pytest", "mock"]
 
 [[package]]
 name = "typed-ast"
-version = "1.4.1"
+version = "1.5.4"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "typing-extensions"
-version = "3.7.4.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
-category = "dev"
+version = "4.3.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.7"
 
 [[package]]
 name = "urllib3"
@@ -1514,23 +1418,19 @@ optional = false
 python-versions = ">=3.6"
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["jaraco.itertools", "func-timeout"]
+testing = ["func-timeout", "jaraco.itertools"]
+docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=3.2)", "sphinx"]
 
 [metadata]
 lock-version = "1.1"
-python-versions = "^3.6.1"
-content-hash = "97a42af9b83f92b4f460425e0afb8ab9f1a2afb684b40f2059c3338baecf7c6d"
+python-versions = "^3.7"
+content-hash = "843fc9b6b16378437356bd7e739b22526e2f8a3c50b31df53c8886cee8b44771"
 
 [metadata.files]
 alabaster = [
     {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
     {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
 ]
-appdirs = [
-    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
-    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
-]
 appnope = [
     {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
     {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
@@ -1580,8 +1480,29 @@ backcall = [
     {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
 ]
 black = [
-    {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
-    {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
+    {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
+    {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
+    {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
+    {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
+    {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
+    {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
+    {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
+    {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
+    {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
+    {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
+    {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
+    {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
+    {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
+    {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
+    {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
+    {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
+    {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
+    {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
+    {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
+    {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
+    {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
+    {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
+    {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
 ]
 bleach = [
     {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"},
@@ -1626,48 +1547,64 @@ charset-normalizer = [
     {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"},
 ]
 click = [
-    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
-    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+    {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+    {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
 ]
 colorama = [
     {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
     {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
 ]
 coverage = [
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
-    {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
-    {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
-    {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
-    {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
-    {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
-    {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
-    {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
-    {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
-    {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
-    {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
-    {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
-    {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
-    {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
-    {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
-    {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
-    {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
-    {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
-    {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
+    {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
+    {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
+    {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"},
+    {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"},
+    {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"},
+    {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"},
+    {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"},
+    {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"},
+    {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"},
+    {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"},
+    {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"},
+    {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"},
+    {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"},
+    {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"},
+    {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"},
+    {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"},
+    {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"},
+    {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"},
+    {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"},
+    {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"},
+    {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"},
+    {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"},
+    {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"},
+    {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"},
+    {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"},
+    {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"},
+    {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"},
+    {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"},
+    {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"},
+    {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"},
+    {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"},
+    {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"},
+    {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"},
+    {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"},
+    {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"},
+    {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"},
+    {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"},
+    {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"},
+    {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"},
+    {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"},
+    {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"},
+    {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"},
+    {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"},
+    {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"},
+    {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"},
+    {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"},
+    {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"},
+    {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"},
+    {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"},
+    {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"},
 ]
 cycler = [
     {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
@@ -1689,29 +1626,25 @@ entrypoints = [
     {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
     {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
 ]
+exceptiongroup = [
+    {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"},
+    {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"},
+]
 flake8 = [
-    {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
-    {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
+    {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
+    {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
 ]
 flake8-bugbear = [
     {file = "flake8-bugbear-20.1.4.tar.gz", hash = "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162"},
     {file = "flake8_bugbear-20.1.4-py36.py37.py38-none-any.whl", hash = "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63"},
 ]
-flake8-docstrings = [
-    {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"},
-    {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"},
-]
-flake8-import-order = [
-    {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"},
-    {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"},
-]
 flake8-polyfill = [
     {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
     {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
 ]
 hypothesis = [
-    {file = "hypothesis-5.26.0-py3-none-any.whl", hash = "sha256:88260b37437f4ae311822589bdb798a15c3def70db1839d86fa0a3ff4b63d564"},
-    {file = "hypothesis-5.26.0.tar.gz", hash = "sha256:54f3ce8d7146cb59eef419a26d5edfdf4e8518615b9df8c7e1f5392a2c5c9c36"},
+    {file = "hypothesis-6.54.4-py3-none-any.whl", hash = "sha256:469125235b4fdd6b6a4b6a6fa13e528d3e8de4dbd3bd9236b03323fefa3a9b32"},
+    {file = "hypothesis-6.54.4.tar.gz", hash = "sha256:ee42fe4d2ff96c49910085780d6b8f34cbcf4c44427616e22833869d451116bb"},
 ]
 idna = [
     {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
@@ -1742,8 +1675,8 @@ ipython-genutils = [
     {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
 ]
 isort = [
-    {file = "isort-5.4.2-py3-none-any.whl", hash = "sha256:60a1b97e33f61243d12647aaaa3e6cc6778f5eb9f42997650f1cc975b6008750"},
-    {file = "isort-5.4.2.tar.gz", hash = "sha256:d488ba1c5a2db721669cc180180d5acf84ebdc5af7827f7aaeaa75f73cf0e2b8"},
+    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
 ]
 jedi = [
     {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"},
@@ -1765,28 +1698,10 @@ jupyter-client = [
     {file = "jupyter_client-6.1.6-py3-none-any.whl", hash = "sha256:7ad9aa91505786420d77edc5f9fb170d51050c007338ba8d196f603223fd3b3a"},
     {file = "jupyter_client-6.1.6.tar.gz", hash = "sha256:b360f8d4638bc577a4656e93f86298db755f915098dc763f6fc05da0c5d7a595"},
 ]
-jupyter-contrib-core = [
-    {file = "jupyter_contrib_core-0.3.3-py2.py3-none-any.whl", hash = "sha256:1ec81e275a8f5858d56b0c4c6cd85335aa8e915001b8657fe51c620c3cdde50f"},
-    {file = "jupyter_contrib_core-0.3.3.tar.gz", hash = "sha256:e65bc0e932ff31801003cef160a4665f2812efe26a53801925a634735e9a5794"},
-]
-jupyter-contrib-nbextensions = [
-    {file = "jupyter_contrib_nbextensions-0.5.1-py2.py3-none-any.whl", hash = "sha256:2c071f0aa208c569666f656bdc0f66906ca493cf9f06f46db6350db11030ff40"},
-    {file = "jupyter_contrib_nbextensions-0.5.1.tar.gz", hash = "sha256:eecd28ecc2fc410226c0a3d4932ed2fac4860ccf8d9e9b1b29548835a35b22ab"},
-]
 jupyter-core = [
     {file = "jupyter_core-4.6.3-py2.py3-none-any.whl", hash = "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"},
     {file = "jupyter_core-4.6.3.tar.gz", hash = "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e"},
 ]
-jupyter-highlight-selected-word = [
-    {file = "jupyter_highlight_selected_word-0.2.0-py2.py3-none-any.whl", hash = "sha256:9545dfa9cb057eebe3a5795604dcd3a5294ea18637e553f61a0b67c1b5903c58"},
-    {file = "jupyter_highlight_selected_word-0.2.0.tar.gz", hash = "sha256:9fa740424859a807950ca08d2bfd28a35154cd32dd6d50ac4e0950022adc0e7b"},
-]
-jupyter-latex-envs = [
-    {file = "jupyter_latex_envs-1.4.6.tar.gz", hash = "sha256:070a31eb2dc488bba983915879a7c2939247bf5c3b669b398bdb36a9b5343872"},
-]
-jupyter-nbextensions-configurator = [
-    {file = "jupyter_nbextensions_configurator-0.4.1.tar.gz", hash = "sha256:e5e86b5d9d898e1ffb30ebb08e4ad8696999f798fef3ff3262d7b999076e4e83"},
-]
 kiwisolver = [
     {file = "kiwisolver-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74"},
     {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8"},
@@ -1811,78 +1726,6 @@ kiwisolver = [
 livereload = [
     {file = "livereload-2.6.2.tar.gz", hash = "sha256:d1eddcb5c5eb8d2ca1fa1f750e580da624c0f7fcb734aa5780dc81b7dcbd89be"},
 ]
-lxml = [
-    {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"},
-    {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"},
-    {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
-    {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"},
-    {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"},
-    {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"},
-    {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"},
-    {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"},
-    {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"},
-    {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"},
-    {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"},
-    {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"},
-    {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"},
-    {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"},
-    {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"},
-    {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"},
-    {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"},
-    {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"},
-    {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"},
-    {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"},
-    {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"},
-    {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"},
-    {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"},
-    {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"},
-    {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"},
-    {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"},
-    {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"},
-    {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"},
-    {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"},
-    {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"},
-    {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"},
-    {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"},
-    {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"},
-    {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"},
-    {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"},
-    {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"},
-    {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"},
-    {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"},
-    {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"},
-    {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"},
-    {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"},
-    {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"},
-    {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"},
-    {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"},
-    {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"},
-    {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"},
-    {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"},
-    {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"},
-    {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"},
-    {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"},
-    {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"},
-    {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"},
-    {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"},
-    {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"},
-    {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"},
-    {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"},
-    {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"},
-    {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"},
-    {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"},
-    {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"},
-    {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"},
-    {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"},
-    {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"},
-    {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"},
-    {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"},
-    {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"},
-    {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"},
-    {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"},
-    {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"},
-    {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
-]
 markupsafe = [
     {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
     {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
@@ -1974,25 +1817,30 @@ mistune = [
     {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
     {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
 ]
-more-itertools = [
-    {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
-    {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
-]
 mypy = [
-    {file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"},
-    {file = "mypy-0.782-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e"},
-    {file = "mypy-0.782-cp35-cp35m-win_amd64.whl", hash = "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d"},
-    {file = "mypy-0.782-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd"},
-    {file = "mypy-0.782-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"},
-    {file = "mypy-0.782-cp36-cp36m-win_amd64.whl", hash = "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406"},
-    {file = "mypy-0.782-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86"},
-    {file = "mypy-0.782-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707"},
-    {file = "mypy-0.782-cp37-cp37m-win_amd64.whl", hash = "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308"},
-    {file = "mypy-0.782-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc"},
-    {file = "mypy-0.782-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea"},
-    {file = "mypy-0.782-cp38-cp38-win_amd64.whl", hash = "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b"},
-    {file = "mypy-0.782-py3-none-any.whl", hash = "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d"},
-    {file = "mypy-0.782.tar.gz", hash = "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c"},
+    {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"},
+    {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"},
+    {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"},
+    {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"},
+    {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"},
+    {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"},
+    {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"},
+    {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"},
+    {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"},
+    {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"},
+    {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"},
+    {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"},
+    {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"},
+    {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"},
+    {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"},
+    {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"},
+    {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"},
+    {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"},
+    {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"},
+    {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"},
+    {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"},
+    {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"},
+    {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"},
 ]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
@@ -2015,32 +1863,34 @@ notebook = [
     {file = "notebook-6.4.10.tar.gz", hash = "sha256:2408a76bc6289283a8eecfca67e298ec83c67db51a4c2e1b713dd180bb39e90e"},
 ]
 numpy = [
-    {file = "numpy-1.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69"},
-    {file = "numpy-1.19.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72"},
-    {file = "numpy-1.19.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7"},
-    {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e"},
-    {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132"},
-    {file = "numpy-1.19.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff"},
-    {file = "numpy-1.19.1-cp36-cp36m-win32.whl", hash = "sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624"},
-    {file = "numpy-1.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983"},
-    {file = "numpy-1.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e"},
-    {file = "numpy-1.19.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a"},
-    {file = "numpy-1.19.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065"},
-    {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93"},
-    {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"},
-    {file = "numpy-1.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954"},
-    {file = "numpy-1.19.1-cp37-cp37m-win32.whl", hash = "sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b"},
-    {file = "numpy-1.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055"},
-    {file = "numpy-1.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a"},
-    {file = "numpy-1.19.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7"},
-    {file = "numpy-1.19.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129"},
-    {file = "numpy-1.19.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7"},
-    {file = "numpy-1.19.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968"},
-    {file = "numpy-1.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd"},
-    {file = "numpy-1.19.1-cp38-cp38-win32.whl", hash = "sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae"},
-    {file = "numpy-1.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc"},
-    {file = "numpy-1.19.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1"},
-    {file = "numpy-1.19.1.zip", hash = "sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491"},
+    {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"},
+    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"},
+    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"},
+    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"},
+    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"},
+    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"},
+    {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"},
+    {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"},
+    {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"},
+    {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"},
+    {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"},
+    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"},
+    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"},
+    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"},
+    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"},
+    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"},
+    {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"},
+    {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"},
+    {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"},
+    {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"},
+    {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"},
+    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"},
+    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"},
+    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"},
+    {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"},
+    {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"},
+    {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"},
+    {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
 ]
 packaging = [
     {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
@@ -2072,8 +1922,8 @@ parso = [
     {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"},
 ]
 pathspec = [
-    {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
-    {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
+    {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
+    {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
 ]
 pathtools = [
     {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
@@ -2149,6 +1999,10 @@ pillow = [
     {file = "Pillow-8.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96"},
     {file = "Pillow-8.3.2.tar.gz", hash = "sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c"},
 ]
+platformdirs = [
+    {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
+    {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+]
 pluggy = [
     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
@@ -2173,20 +2027,20 @@ py = [
     {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
 pycodestyle = [
-    {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
-    {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
+    {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
+    {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
 ]
 pycparser = [
     {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
     {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
 ]
 pydocstyle = [
-    {file = "pydocstyle-5.0.2-py3-none-any.whl", hash = "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586"},
-    {file = "pydocstyle-5.0.2.tar.gz", hash = "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"},
+    {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
+    {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
 ]
 pyflakes = [
-    {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
-    {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
+    {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
+    {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
 ]
 pygments = [
     {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"},
@@ -2200,8 +2054,8 @@ pyrsistent = [
     {file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"},
 ]
 pytest = [
-    {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"},
-    {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"},
+    {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
+    {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
 ]
 pytest-cov = [
     {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
@@ -2304,35 +2158,13 @@ pyzmq = [
     {file = "pyzmq-19.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7e7f930039ee0c4c26e4dfee015f20bd6919cd8b97c9cd7afbde2923a5167b6"},
     {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"},
 ]
-regex = [
-    {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
-    {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"},
-    {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"},
-    {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"},
-    {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"},
-    {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"},
-    {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"},
-    {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"},
-    {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"},
-]
 requests = [
     {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
     {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
 ]
 rope = [
-    {file = "rope-0.17.0.tar.gz", hash = "sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1"},
+    {file = "rope-1.1.1-py3-none-any.whl", hash = "sha256:216390b6342bde8ef1f27a6663554de0ea0b1fdad7c421deb3d5a3f6c5fc01e7"},
+    {file = "rope-1.1.1.tar.gz", hash = "sha256:13048ff0245a0fa187f282697a55add46e4ccf083e7670f868bdf54ee9e47f2e"},
 ]
 scikit-learn = [
     {file = "scikit-learn-0.23.2.tar.gz", hash = "sha256:20766f515e6cd6f954554387dfae705d93c7b544ec0e6c6a5d8e006f6f7ef480"},
@@ -2438,9 +2270,13 @@ threadpoolctl = [
     {file = "threadpoolctl-2.1.0-py3-none-any.whl", hash = "sha256:38b74ca20ff3bb42caca8b00055111d74159ee95c4370882bbff2b93d24da725"},
     {file = "threadpoolctl-2.1.0.tar.gz", hash = "sha256:ddc57c96a38beb63db45d6c159b5ab07b6bced12c45a1f07b2b92f272aebfa6b"},
 ]
-toml = [
-    {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
-    {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+tokenize-rt = [
+    {file = "tokenize_rt-4.2.1-py2.py3-none-any.whl", hash = "sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8"},
+    {file = "tokenize_rt-4.2.1.tar.gz", hash = "sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94"},
+]
+tomli = [
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
 ]
 tornado = [
     {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
@@ -2490,41 +2326,34 @@ traitlets = [
     {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
 ]
 typed-ast = [
-    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
-    {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"},
-    {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
-    {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
-    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
-    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
-    {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
-    {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
-    {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
+    {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
+    {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
+    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
+    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
+    {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
+    {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
+    {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
+    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
+    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
+    {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
+    {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
+    {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
+    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
+    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
+    {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
+    {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
-    {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
-    {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
+    {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
+    {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
 ]
 urllib3 = [
     {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"},
diff --git a/pybotics/geometry.py b/pybotics/geometry.py
index eedf6ec3..2b833273 100644
--- a/pybotics/geometry.py
+++ b/pybotics/geometry.py
@@ -1,8 +1,9 @@
 """Geometry functions and utilities."""
 from enum import Enum
-from typing import Sequence, Union
+from typing import Union
 
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 
 from pybotics.errors import PyboticsError
 
@@ -38,9 +39,9 @@ class OrientationConvention(Enum):
 
 
 def vector_2_matrix(
-    vector: Sequence[float],
+    vector: npt.NDArray[np.float64],
     convention: Union[OrientationConvention, str] = OrientationConvention.EULER_ZYX,
-) -> np.ndarray:
+) -> npt.NDArray[np.float64]:
     """
     Calculate the pose from the position and euler angles.
 
@@ -63,9 +64,9 @@ def vector_2_matrix(
     # iterate through rotation order
     # build rotation matrix
     transform_matrix = np.eye(4)
-    for axis, value in zip(convention, rotation_component):  # type: ignore
+    for axis, value in zip(convention, rotation_component):
         current_rotation = globals()[f"rotation_matrix_{axis}"](value)
-        transform_matrix = np.dot(transform_matrix, current_rotation)
+        transform_matrix = np.dot(transform_matrix, current_rotation)  # type: ignore
 
     # add translation component
     transform_matrix[:-1, -1] = translation_component
@@ -73,24 +74,28 @@ def vector_2_matrix(
     return transform_matrix
 
 
-def position_from_matrix(matrix: np.ndarray) -> np.ndarray:
+def position_from_matrix(matrix: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
     """Get the position values from a 4x4 transform matrix."""
-    return matrix[:-1, -1]
+    position = matrix[:-1, -1]  # type: npt.NDArray[np.float64]
+    return position
 
 
 def matrix_2_vector(
-    matrix: np.ndarray,
+    matrix: npt.NDArray[np.float64],
     convention: OrientationConvention = OrientationConvention.EULER_ZYX,
-) -> np.ndarray:
+) -> npt.NDArray[np.float64]:
     """Convert 4x4 matrix to a vector."""
     # call function
     try:
-        return globals()[f"_matrix_2_{convention.name.lower()}"](matrix)
+        vector = globals()[f"_matrix_2_{convention.name.lower()}"](
+            matrix
+        )  # type: npt.NDArray[np.float64]
+        return vector
     except KeyError:  # pragma: no cover
         raise NotImplementedError
 
 
-def _matrix_2_euler_zyx(matrix: np.ndarray) -> np.ndarray:
+def _matrix_2_euler_zyx(matrix: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
     """
     Calculate the equivalent position and euler angles of the given pose.
 
@@ -136,7 +141,7 @@ def wrap_2_pi(angle: float) -> float:
     return result
 
 
-def rotation_matrix_x(angle: float) -> np.ndarray:
+def rotation_matrix_x(angle: float) -> npt.NDArray[np.float64]:
     """Generate a basic 4x4 rotation matrix about the X axis."""
     s = np.sin(angle)
     c = np.cos(angle)
@@ -146,7 +151,7 @@ def rotation_matrix_x(angle: float) -> np.ndarray:
     return matrix
 
 
-def rotation_matrix_y(angle: float) -> np.ndarray:
+def rotation_matrix_y(angle: float) -> npt.NDArray[np.float64]:
     """Generate a basic 4x4 rotation matrix about the Y axis."""
     s = np.sin(angle)
     c = np.cos(angle)
@@ -156,7 +161,7 @@ def rotation_matrix_y(angle: float) -> np.ndarray:
     return matrix
 
 
-def rotation_matrix_z(angle: float) -> np.ndarray:
+def rotation_matrix_z(angle: float) -> npt.NDArray[np.float64]:
     """Generate a basic 4x4 rotation matrix about the Z axis."""
     s = np.sin(angle)
     c = np.cos(angle)
@@ -166,7 +171,7 @@ def rotation_matrix_z(angle: float) -> np.ndarray:
     return matrix
 
 
-def translation_matrix(xyz: Sequence[float]) -> np.ndarray:
+def translation_matrix(xyz: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
     """Generate a basic 4x4 translation matrix."""
     # validate
     if len(xyz) != 3:
diff --git a/pybotics/json_encoder.py b/pybotics/json_encoder.py
index d5a01c29..da92d8a5 100644
--- a/pybotics/json_encoder.py
+++ b/pybotics/json_encoder.py
@@ -2,7 +2,7 @@
 import json
 from typing import Any
 
-import numpy as np  # type: ignore
+import numpy as np
 
 
 class JSONEncoder(json.JSONEncoder):
diff --git a/pybotics/kinematic_chain.py b/pybotics/kinematic_chain.py
index 5c83c4fb..771ce8d5 100644
--- a/pybotics/kinematic_chain.py
+++ b/pybotics/kinematic_chain.py
@@ -4,7 +4,8 @@
 from typing import Any, Optional, Sequence, Sized, Union
 
 import attr
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 
 from pybotics.errors import PyboticsError
 from pybotics.json_encoder import JSONEncoder
@@ -30,7 +31,7 @@ def to_json(self) -> str:
 
     @property  # type: ignore
     @abstractmethod
-    def matrix(self) -> np.ndarray:
+    def matrix(self) -> npt.NDArray[np.float64]:
         """
         Convert chain to matrix of link parameters.
 
@@ -41,7 +42,7 @@ def matrix(self) -> np.ndarray:
 
     @matrix.setter  # type: ignore
     @abstractmethod
-    def matrix(self, value: np.ndarray) -> None:
+    def matrix(self, value: npt.NDArray[np.float64]) -> None:
         """
         Set to matrix of link parameters.
 
@@ -72,7 +73,9 @@ def num_parameters(self) -> int:
         raise NotImplementedError
 
     @abstractmethod
-    def transforms(self, q: Optional[Sequence[float]] = None) -> Sequence[np.ndarray]:
+    def transforms(
+        self, q: Optional[npt.NDArray[np.float64]] = None
+    ) -> Sequence[npt.NDArray[np.float64]]:
         """
         Generate a sequence of spatial transforms.
 
@@ -84,7 +87,7 @@ def transforms(self, q: Optional[Sequence[float]] = None) -> Sequence[np.ndarray
 
     @property
     @abstractmethod
-    def vector(self) -> np.ndarray:
+    def vector(self) -> npt.NDArray[np.float64]:
         """
         Get the vector representation of the kinematic chain.
 
@@ -93,12 +96,14 @@ def vector(self) -> np.ndarray:
         raise NotImplementedError
 
     @vector.setter
-    def vector(self, value: Sequence[float]) -> None:
+    def vector(self, value: npt.NDArray[np.float64]) -> None:
         """Set parameters of all links."""
         raise NotImplementedError
 
 
-def _validate_links(value: Union[Sequence[MDHLink], np.ndarray]) -> Sequence[MDHLink]:
+def _validate_links(
+    value: Union[Sequence[MDHLink], npt.NDArray[np.float64]]
+) -> Sequence[MDHLink]:
     if isinstance(value, np.ndarray):
         try:
             value = value.reshape((-1, MDHLink._size))
@@ -115,20 +120,20 @@ def _validate_links(value: Union[Sequence[MDHLink], np.ndarray]) -> Sequence[MDH
 class MDHKinematicChain(KinematicChain):
     """Kinematic Chain of MDH links."""
 
-    _links = attr.ib(type=Union[Sequence[MDHLink], np.ndarray])
+    _links = attr.ib(type=Sequence[MDHLink])
 
     def __attrs_post_init__(self) -> None:
         """Post-attrs initialization."""
         self._links = _validate_links(self._links)
 
     @classmethod
-    def from_parameters(cls: Any, parameters: Sequence[float]) -> Any:
+    def from_parameters(cls: Any, parameters: npt.NDArray[np.float64]) -> Any:
         """Construct Kinematic Chain from parameters."""
         kc = cls(parameters)
         return kc
 
     @property
-    def matrix(self) -> np.ndarray:
+    def matrix(self) -> npt.NDArray[np.float64]:
         """
         Convert chain to matrix of link parameters.
 
@@ -138,7 +143,7 @@ def matrix(self) -> np.ndarray:
         return np.array([l.vector for l in self._links])
 
     @matrix.setter
-    def matrix(self, value: np.ndarray) -> None:
+    def matrix(self, value: npt.NDArray[np.float64]) -> None:
         """
         Set matrix of link parameters.
 
@@ -155,7 +160,7 @@ def links(self) -> Sequence[MDHLink]:
         return x
 
     @links.setter
-    def links(self, value: Union[Sequence[MDHLink], np.ndarray]) -> None:
+    def links(self, value: Union[Sequence[MDHLink], npt.NDArray[np.float64]]) -> None:
         """Set links."""
         self._links = _validate_links(value)
 
@@ -169,20 +174,22 @@ def num_parameters(self) -> int:
         # noinspection PyProtectedMember
         return len(self) * MDHLink._size
 
-    def transforms(self, q: Optional[Sequence[float]] = None) -> Sequence[np.ndarray]:
+    def transforms(
+        self, q: Optional[npt.NDArray[np.float64]] = None
+    ) -> Sequence[npt.NDArray[np.float64]]:
         """Get sequence of 4x4 transforms."""
         q = np.zeros(len(self)) if q is None else q
         transforms = [link.transform(p) for link, p in zip(self._links, q)]
         return transforms
 
     @property
-    def vector(self) -> np.ndarray:
+    def vector(self) -> npt.NDArray[np.float64]:
         """Get parameters of all links."""
         return self.matrix.ravel()
 
     # noinspection PyMethodOverriding
     @vector.setter
-    def vector(self, value: Sequence[float]) -> None:
+    def vector(self, value: npt.NDArray[np.float64]) -> None:
         """Set parameters of all links."""
         # noinspection PyProtectedMember
         value = np.array(value).reshape((-1, MDHLink._size))
diff --git a/pybotics/link.py b/pybotics/link.py
index c3ec8af7..c137c5d0 100644
--- a/pybotics/link.py
+++ b/pybotics/link.py
@@ -1,10 +1,10 @@
 """Link module."""
 from abc import abstractmethod
 from collections.abc import Sized
-from typing import Sequence, Union
 
 import attr
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 
 from pybotics.json_encoder import JSONEncoder
 
@@ -23,7 +23,7 @@ def __len__(self) -> int:
         return self.size
 
     @abstractmethod
-    def displace(self, q: float) -> Union[Sequence[float], np.ndarray]:
+    def displace(self, q: float) -> npt.NDArray[np.float64]:
         """
         Generate a vector of the new link state given a displacement.
 
@@ -34,7 +34,7 @@ def displace(self, q: float) -> Union[Sequence[float], np.ndarray]:
         raise NotImplementedError
 
     @abstractmethod
-    def transform(self, q: float = 0) -> np.ndarray:
+    def transform(self, q: float = 0) -> npt.NDArray[np.float64]:
         """
         Generate a 4x4 transform matrix given a displacement.
 
@@ -46,7 +46,7 @@ def transform(self, q: float = 0) -> np.ndarray:
 
     @property
     @abstractmethod
-    def vector(self) -> np.ndarray:
+    def vector(self) -> npt.NDArray[np.float64]:
         """
         Return the vector representation of the link.
 
@@ -80,7 +80,7 @@ def size(self) -> int:
         """Get number of parameters."""
         return self._size
 
-    def transform(self, q: float = 0) -> np.ndarray:
+    def transform(self, q: float = 0) -> npt.NDArray[np.float64]:
         """
         Generate a 4x4 transform matrix with a displacement.
 
@@ -113,7 +113,7 @@ def transform(self, q: float = 0) -> np.ndarray:
         return transform
 
     @property
-    def vector(self) -> np.ndarray:
+    def vector(self) -> npt.NDArray[np.float64]:
         """
         Return the vector representation of the link.
 
@@ -123,7 +123,7 @@ def vector(self) -> np.ndarray:
 
     # noinspection PyMethodOverriding
     @vector.setter
-    def vector(self, value: Sequence[float]) -> None:
+    def vector(self, value: npt.NDArray[np.float64]) -> None:
         """Set parameters."""
         self.alpha = value[0]
         self.a = value[1]
@@ -139,7 +139,7 @@ class RevoluteMDHLink(MDHLink):
     https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters
     """
 
-    def displace(self, q: float = 0) -> Union[Sequence[float], np.ndarray]:
+    def displace(self, q: float = 0) -> npt.NDArray[np.float64]:
         """
         Generate a vector of the new link state given a displacement.
 
@@ -147,7 +147,7 @@ def displace(self, q: float = 0) -> Union[Sequence[float], np.ndarray]:
 
         :return vector of new displacement state
         """
-        v = np.copy(self.vector)
+        v = self.vector.copy()
         v[2] += q
         return v
 
@@ -160,7 +160,7 @@ class PrismaticMDHLink(MDHLink):
     https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters
     """
 
-    def displace(self, q: float = 0) -> Union[Sequence[float], np.ndarray]:
+    def displace(self, q: float = 0) -> npt.NDArray[np.float64]:
         """
         Generate a vector of the new link state given a displacement.
 
@@ -168,6 +168,6 @@ def displace(self, q: float = 0) -> Union[Sequence[float], np.ndarray]:
 
         :return vector of new displacement state
         """
-        v = np.copy(self.vector)
+        v = self.vector.copy()
         v[3] += q
         return v
diff --git a/pybotics/optimization.py b/pybotics/optimization.py
index e4d52d93..840c0737 100644
--- a/pybotics/optimization.py
+++ b/pybotics/optimization.py
@@ -1,22 +1,20 @@
-"""Optimization module.
-
-isort:skip_file
-"""
+"""Optimization module."""
 from copy import deepcopy
 from itertools import repeat
-from typing import Sequence, Union
+from typing import MutableSequence, Sequence
 
 import attr
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 
-from pybotics.robot import Robot
 from pybotics.errors import PyboticsError
 from pybotics.geometry import matrix_2_vector, position_from_matrix, vector_2_matrix
+from pybotics.robot import Robot
 
 
 def _validate_transform_mask(
-    mask: Union[bool, Sequence[bool]], name: str, size: int
-) -> Sequence[bool]:
+    mask: MutableSequence[bool], name: str, size: int
+) -> MutableSequence[bool]:
     """Validate mask arguments."""
     # validate input
     if isinstance(mask, bool):
@@ -32,9 +30,9 @@ class OptimizationHandler:
     """Handler for optimization tasks."""
 
     robot = attr.ib(type=Robot)
-    kinematic_chain_mask = attr.ib(False, type=Union[bool, Sequence[bool]])
-    tool_mask = attr.ib(False, type=Union[bool, Sequence[bool]])
-    world_mask = attr.ib(False, type=Union[bool, Sequence[bool]])
+    kinematic_chain_mask = attr.ib(False, type=MutableSequence[bool])
+    tool_mask = attr.ib(False, type=MutableSequence[bool])
+    world_mask = attr.ib(False, type=MutableSequence[bool])
 
     def __attrs_post_init__(self) -> None:
         """Post-init handler."""
@@ -51,14 +49,14 @@ def __attrs_post_init__(self) -> None:
             size=self.robot.kinematic_chain.num_parameters,
         )
 
-    def apply_optimization_vector(self, vector: np.ndarray) -> None:
+    def apply_optimization_vector(self, vector: npt.NDArray[np.float64]) -> None:
         """Apply vector."""
         # get number of parameters
         num_kc_parameters = np.sum(self.kinematic_chain_mask)
         num_tool_parameters = np.sum(self.tool_mask)
 
         # extract vector segments
-        segments = np.split(
+        segments = np.split(  # type: ignore
             vector, [num_kc_parameters, num_kc_parameters + num_tool_parameters]
         )
         kc_segment = segments[0]
@@ -78,7 +76,7 @@ def apply_optimization_vector(self, vector: np.ndarray) -> None:
         world_vector[self.world_mask] = world_segment
         self.robot.world_frame = vector_2_matrix(world_vector)
 
-    def generate_optimization_vector(self) -> np.ndarray:
+    def generate_optimization_vector(self) -> npt.NDArray[np.float64]:
         """Generate vector."""
         kc_vector = np.compress(
             self.kinematic_chain_mask, self.robot.kinematic_chain.vector
@@ -91,28 +89,33 @@ def generate_optimization_vector(self) -> np.ndarray:
 
 
 def optimize_accuracy(
-    optimization_vector: np.ndarray,
+    optimization_vector: npt.NDArray[np.float64],
     handler: OptimizationHandler,
-    qs: Sequence[Sequence[float]],
-    positions: Sequence[Sequence[float]],
-) -> np.ndarray:
+    qs: Sequence[npt.NDArray[np.float64]],
+    positions: Sequence[npt.NDArray[np.float64]],
+) -> npt.NDArray[np.float64]:
     """Fitness function for accuracy optimization."""
     handler.apply_optimization_vector(optimization_vector)
     errors = compute_absolute_errors(qs=qs, positions=positions, robot=handler.robot)
     return errors
 
 
-def compute_absolute_error(q: np.ndarray, position: np.ndarray, robot: Robot) -> float:
+def compute_absolute_error(
+    q: npt.NDArray[np.float64], position: npt.NDArray[np.float64], robot: Robot
+) -> float:
     """Compute the absolute error of a given position."""
     pose = robot.fk(q)
     actual_position = position_from_matrix(pose)
-    error = position - actual_position
-    return float(np.linalg.norm(error))
+    error = position - actual_position  # type: npt.NDArray[np.float64]
+    result = float(np.linalg.norm(error))  # type: ignore
+    return result
 
 
 def compute_absolute_errors(
-    qs: np.ndarray, positions: np.ndarray, robot: Robot
-) -> np.ndarray:
+    qs: Sequence[npt.NDArray[np.float64]],
+    positions: Sequence[npt.NDArray[np.float64]],
+    robot: Robot,
+) -> npt.NDArray[np.float64]:
     """
     Compute the absolute errors of a given set of positions.
 
@@ -120,11 +123,16 @@ def compute_absolute_errors(
     :param positions: Array of Cartesian positions, shape=(n-poses, 3)
     :param robot: Robot model
     """
-    return list(map(compute_absolute_error, qs, positions, repeat(robot)))
+    return np.fromiter(  # type: ignore
+        map(compute_absolute_error, qs, positions, repeat(robot)), dtype=np.float64
+    )
 
 
 def compute_relative_error(
-    q_a: np.ndarray, q_b: np.ndarray, distance: float, robot: Robot
+    q_a: npt.NDArray[np.float64],
+    q_b: npt.NDArray[np.float64],
+    distance: float,
+    robot: Robot,
 ) -> float:
     """Compute the relative error of a given position combination."""
     pose_a = robot.fk(q_a)
@@ -133,16 +141,22 @@ def compute_relative_error(
     actual_position_a = position_from_matrix(pose_a)
     actual_position_b = position_from_matrix(pose_b)
 
-    actual_distance = actual_position_a - actual_position_b
-    actual_distance = np.linalg.norm(actual_distance)
+    actual_distance = actual_position_a - actual_position_b  # type: float
+    actual_distance = np.linalg.norm(actual_distance)  # type: ignore
 
-    error = float(np.linalg.norm(distance - actual_distance))
+    error = float(np.linalg.norm(distance - actual_distance))  # type: ignore
 
     return error
 
 
 def compute_relative_errors(
-    qs_a: np.ndarray, qs_b: np.ndarray, distances: np.ndarray, robot: Robot
-) -> np.array:
+    qs_a: npt.NDArray[np.float64],
+    qs_b: npt.NDArray[np.float64],
+    distances: npt.NDArray[np.float64],
+    robot: Robot,
+) -> npt.NDArray[np.float64]:
     """Compute the relative errors of a given set of position combinations."""
-    return list(map(compute_relative_error, qs_a, qs_b, distances, repeat(robot)))
+    return np.fromiter(  # type: ignore
+        map(compute_relative_error, qs_a, qs_b, distances, repeat(robot)),
+        dtype=np.float64,
+    )
diff --git a/pybotics/predefined_models.py b/pybotics/predefined_models.py
index 6bd906b8..7cbc7e32 100644
--- a/pybotics/predefined_models.py
+++ b/pybotics/predefined_models.py
@@ -3,10 +3,11 @@
 These models correspond to the Modified Denavit–Hartenberg parameters:
 https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters
 """
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 
 
-def kuka_lbr_iiwa_7() -> np.ndarray:  # pragma: no cover
+def kuka_lbr_iiwa_7() -> npt.NDArray[np.float64]:  # pragma: no cover
     """Get KUKA LBR iiwa 7 MDH model."""
     return np.array(
         [
@@ -21,7 +22,7 @@ def kuka_lbr_iiwa_7() -> np.ndarray:  # pragma: no cover
     )
 
 
-def mecademic_meca500() -> np.ndarray:  # pragma: no cover
+def mecademic_meca500() -> npt.NDArray[np.float64]:  # pragma: no cover
     """Get Meca500 MDH model."""
     return np.array(
         [
@@ -35,7 +36,7 @@ def mecademic_meca500() -> np.ndarray:  # pragma: no cover
     )
 
 
-def puma560() -> np.ndarray:  # pragma: no cover
+def puma560() -> npt.NDArray[np.float64]:  # pragma: no cover
     """Get PUMA560 MDH model."""
     return np.array(
         [
@@ -49,7 +50,7 @@ def puma560() -> np.ndarray:  # pragma: no cover
     )
 
 
-def ur10() -> np.ndarray:  # pragma: no cover
+def ur10() -> npt.NDArray[np.float64]:  # pragma: no cover
     """Get UR10 MDH model."""
     return np.array(
         [
@@ -63,7 +64,7 @@ def ur10() -> np.ndarray:  # pragma: no cover
     )
 
 
-def abb_irb120() -> np.ndarray:  # pragma: no cover
+def abb_irb120() -> npt.NDArray[np.float64]:  # pragma: no cover
     """Get ABB irb120 MDH model."""
     return np.array(
         [
diff --git a/pybotics/robot.py b/pybotics/robot.py
index 53069678..e9e2ea6b 100644
--- a/pybotics/robot.py
+++ b/pybotics/robot.py
@@ -1,8 +1,12 @@
 """Robot module."""
-from typing import Any, Optional, Sequence, Sized, Union
+from __future__ import annotations
+
+import typing
+from typing import Any, Optional, Sized
 
 import attr
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
 import scipy.optimize  # type: ignore
 
 from pybotics.errors import PyboticsError
@@ -11,11 +15,11 @@
 from pybotics.tool import Tool
 
 
-def _ndof_zeros_factory(robot: Any) -> np.ndarray:
+def _ndof_zeros_factory(robot: Any) -> npt.NDArray[np.float64]:
     return np.zeros(len(robot.kinematic_chain))
 
 
-def _joint_limits_factory(robot: Any) -> np.ndarray:
+def _joint_limits_factory(robot: Any) -> npt.NDArray[np.float64]:
     return np.repeat((-np.pi, np.pi), len(robot.kinematic_chain)).reshape((2, -1))
 
 
@@ -25,22 +29,22 @@ class Robot(Sized):
 
     kinematic_chain = attr.ib(type=KinematicChain)
     tool = attr.ib(factory=lambda: Tool(), type=Tool)
-    world_frame = attr.ib(factory=lambda: np.eye(4), type=np.ndarray)  # type: ignore
+    world_frame = attr.ib(factory=lambda: np.eye(4), type=npt.NDArray[np.float64])
     random_state = attr.ib(
-        factory=lambda: np.random.RandomState(),  # type: ignore
+        factory=lambda: np.random.RandomState(),
         type=np.random.RandomState,
     )
     home_position = attr.ib(
         default=attr.Factory(factory=_ndof_zeros_factory, takes_self=True),
-        type=np.ndarray,
+        type=npt.NDArray[np.float64],
     )
     _joints = attr.ib(
         default=attr.Factory(factory=_ndof_zeros_factory, takes_self=True),
-        type=np.ndarray,
+        type=npt.NDArray[np.float64],
     )
     _joint_limits = attr.ib(
         default=attr.Factory(factory=_joint_limits_factory, takes_self=True),
-        type=np.ndarray,
+        type=npt.NDArray[np.float64],
     )
 
     def __len__(self) -> int:
@@ -56,7 +60,9 @@ def to_json(self) -> str:
         encoder = JSONEncoder(sort_keys=True)
         return encoder.encode(self)
 
-    def fk(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
+    def fk(
+        self, q: Optional[npt.NDArray[np.float64]] = None
+    ) -> npt.NDArray[np.float64]:
         """
         Compute the forward kinematics of a given position.
 
@@ -77,24 +83,30 @@ def fk(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
         # matrix multiply through transforms
         pose = np.eye(4, dtype=float)
         for t in transforms:
-            pose = np.dot(pose, t)
+            pose = np.dot(pose, t)  # type: ignore
 
         return pose
 
     def ik(
-        self, pose: np.ndarray, q: Optional[Sequence[float]] = None
-    ) -> Optional[np.ndarray]:
+        self, pose: npt.NDArray[np.float64], q: Optional[npt.NDArray[np.float64]] = None
+    ) -> Optional[npt.NDArray[np.float64]]:
         """Solve the inverse kinematics."""
+        # set initial value
         x0 = self.joints if q is None else q
-        result = scipy.optimize.least_squares(
+
+        # solve optimization
+        optimization_result = scipy.optimize.least_squares(
             fun=_ik_cost_function, x0=x0, bounds=self.joint_limits, args=(pose, self)
         )  # type: scipy.optimize.OptimizeResult
 
-        if result.success:  # pragma: no cover
-            actual_pose = self.fk(result.x)
+        # set return value
+        result = None  # type: Optional[npt.NDArray[np.float64]]
+        if optimization_result.success:  # pragma: no cover
+            actual_pose = self.fk(optimization_result.x)
             if np.allclose(actual_pose, pose, atol=1e-3):
-                return result.x
-        return None
+                result = optimization_result.x
+
+        return result
 
     @property
     def ndof(self) -> int:
@@ -106,7 +118,7 @@ def ndof(self) -> int:
         return len(self)
 
     @property
-    def joints(self) -> Union[Sequence[float], np.ndarray]:
+    def joints(self) -> npt.NDArray[np.float64]:
         """
         Get the robot configuration (e.g., joint positions for serial robot).
 
@@ -115,14 +127,14 @@ def joints(self) -> Union[Sequence[float], np.ndarray]:
         return self._joints
 
     @joints.setter
-    def joints(self, value: np.ndarray) -> None:
+    def joints(self, value: npt.NDArray[np.float64]) -> None:
         """Set joints."""
         if np.any(value < self.joint_limits[0]) or np.any(value > self.joint_limits[1]):
             raise PyboticsError("Joint limits exceeded.")
         self._joints = value
 
     @property
-    def joint_limits(self) -> np.ndarray:
+    def joint_limits(self) -> npt.NDArray[np.float64]:
         """
         Limits of the robot position (e.g., joint limits).
 
@@ -131,13 +143,15 @@ def joint_limits(self) -> np.ndarray:
         return self._joint_limits
 
     @joint_limits.setter
-    def joint_limits(self, value: np.ndarray) -> None:
+    def joint_limits(self, value: npt.NDArray[np.float64]) -> None:
         """Set joint limits."""
         if value.shape[0] != 2 or value.shape[1] != len(self):
             raise PyboticsError(f"position_limits must have shape=(2,{len(self)})")
         self._joint_limits = value
 
-    def jacobian_world(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
+    def jacobian_world(
+        self, q: Optional[npt.NDArray[np.float64]] = None
+    ) -> npt.NDArray[np.float64]:
         """Calculate the Jacobian wrt the world frame."""
         q = self.joints if q is None else q
         j_fl = self.jacobian_flange(q)
@@ -146,11 +160,13 @@ def jacobian_world(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
         j_tr = np.zeros((6, 6), dtype=float)
         j_tr[:3, :3] = rotation
         j_tr[3:, 3:] = rotation
-        j_w = np.dot(j_tr, j_fl)
+        j_w = np.dot(j_tr, j_fl)  # type: ignore
 
-        return j_w
+        return typing.cast(npt.NDArray[np.float64], j_w)
 
-    def jacobian_flange(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
+    def jacobian_flange(
+        self, q: Optional[npt.NDArray[np.float64]] = None
+    ) -> npt.NDArray[np.float64]:
         """Calculate the Jacobian wrt the flange frame."""
         q = self.joints if q is None else q
 
@@ -176,13 +192,17 @@ def jacobian_flange(self, q: Optional[Sequence[float]] = None) -> np.ndarray:
             current_link = self.kinematic_chain.links[i]
             p = q[i]
             current_link_transform = current_link.transform(p)
-            current_transform = np.dot(current_link_transform, current_transform)
+            current_transform = np.dot(  # type: ignore
+                current_link_transform, current_transform
+            )
 
         return jacobian_flange
 
     def compute_joint_torques(
-        self, wrench: Sequence[float], q: Optional[Sequence[float]] = None
-    ) -> np.ndarray:
+        self,
+        wrench: npt.NDArray[np.float64],
+        q: Optional[npt.NDArray[np.float64]] = None,
+    ) -> npt.NDArray[np.float64]:
         """
         Calculate the joint torques due to external flange force.
 
@@ -215,11 +235,11 @@ def compute_joint_torques(
 
             # calculate force applied to current link
             rotation = transform[:3, :3]
-            force = np.dot(rotation, force)
+            force = np.dot(rotation, force)  # type: ignore
 
             # calculate moment applied to current link
             q = transform[:3, -1]
-            moment = np.dot(rotation, moment) + np.cross(q, force)
+            moment = np.dot(rotation, moment) + np.cross(q, force)  # type: ignore
 
             # append z-component as joint torque
             joint_torques.append(moment[-1])
@@ -227,31 +247,41 @@ def compute_joint_torques(
         # reverse torques into correct order
         return np.array(list(reversed(joint_torques)), dtype=float)
 
-    def clamp_joints(self, q: Sequence[float]) -> Optional[np.ndarray]:
+    def clamp_joints(self, q: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
         """Limit joints to joint limits."""
-        return np.clip(q, self.joint_limits[0], self.joint_limits[1])
-
-    def random_joints(self, in_place: bool = False) -> Optional[np.ndarray]:
+        result = np.clip(
+            q, self.joint_limits[0], self.joint_limits[1]
+        )  # type: npt.NDArray[np.float64]
+        return result
+
+    def random_joints(
+        self, in_place: bool = False
+    ) -> Optional[npt.NDArray[np.float64]]:
         """Generate random joints within limits."""
         q = self.random_state.uniform(
             low=self.joint_limits[0], high=self.joint_limits[1]
         )
 
+        result = None
         if in_place:
             self.joints = q
-            return None
         else:
-            return q
+            result = q
+
+        return result
 
     @classmethod
-    def from_parameters(cls, parameters: Sequence[float]) -> Sized:
+    def from_parameters(cls, parameters: npt.NDArray[np.float64]) -> Robot:
         """Construct Robot from Kinematic Chain parameters."""
         # FIXME: assumes MDH revolute robot
         kc = MDHKinematicChain.from_parameters(parameters)
         return cls(kinematic_chain=kc)
 
 
-def _ik_cost_function(q: np.ndarray, pose: np.ndarray, robot: Robot) -> np.ndarray:
+def _ik_cost_function(
+    q: npt.NDArray[np.float64], pose: npt.NDArray[np.float64], robot: Robot
+) -> npt.NDArray[np.float64]:
     actual_pose = robot.fk(q)
     diff = np.abs(actual_pose - pose)
-    return diff.ravel()
+    result = diff.ravel()  # type: npt.NDArray[np.float64]
+    return result
diff --git a/pybotics/tool.py b/pybotics/tool.py
index 8cfc9c52..92cf777d 100644
--- a/pybotics/tool.py
+++ b/pybotics/tool.py
@@ -2,10 +2,11 @@
 
 isort:skip_file
 """
-from typing import Sequence, Union
 
 import attr
-import numpy as np  # type: ignore
+import numpy as np
+import numpy.typing as npt
+
 
 from pybotics.geometry import matrix_2_vector, position_from_matrix, vector_2_matrix
 
@@ -14,12 +15,12 @@
 class Tool:
     """Tool class."""
 
-    matrix = attr.ib(factory=lambda: np.eye(4), type=np.ndarray)  # type: ignore
+    matrix = attr.ib(factory=lambda: np.eye(4), type=npt.NDArray[np.float64])
     mass = attr.ib(0, type=float)
-    cg = attr.ib(factory=lambda: np.zeros(3), type=np.ndarray)  # type: ignore
+    cg = attr.ib(factory=lambda: np.zeros(3), type=npt.NDArray[np.float64])
 
     @property
-    def position(self) -> Union[Sequence[float], np.ndarray]:
+    def position(self) -> npt.NDArray[np.float64]:
         """
         Get the position XYZ of the frame.
 
@@ -28,11 +29,11 @@ def position(self) -> Union[Sequence[float], np.ndarray]:
         return position_from_matrix(self.matrix)
 
     @position.setter
-    def position(self, value: Sequence[float]) -> None:
+    def position(self, value: npt.NDArray[np.float64]) -> None:
         self.matrix[:-1, -1] = value
 
     @property
-    def vector(self) -> np.ndarray:
+    def vector(self) -> npt.NDArray[np.float64]:
         """
         Return the vector representation of the frame as EULER ZYX.
 
@@ -41,5 +42,5 @@ def vector(self) -> np.ndarray:
         return matrix_2_vector(self.matrix)
 
     @vector.setter
-    def vector(self, value: Sequence[float]) -> None:
+    def vector(self, value: npt.NDArray[np.float64]) -> None:
         self.matrix = vector_2_matrix(value)
diff --git a/pyproject.toml b/pyproject.toml
index 5eb59d84..8836288d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -56,9 +56,10 @@ python = "^3.7"
 attrs = ">=19.0"
 numpy = "^1.20"
 scipy = "^1"
+typing-extensions = { version = "^4", python = "~3.7" }
 
 [tool.poetry.dev-dependencies]
-black = ">=22.1"
+black = {extras = ["jupyter"], version = "^22.6.0"}
 coverage = ">=6"
 flake8 = ">=4"
 flake8-bugbear = "*"
@@ -88,7 +89,6 @@ line-length = 88
 target_version = ['py37']
 include = '\.pyi?$'
 exclude = '''
-
 (
   /(
       \.eggs         # exclude a few common directories in the
@@ -107,6 +107,9 @@ exclude = '''
 )
 '''
 
+[tool.isort]
+profile = "black"
+
 [build-system]
 requires = ["poetry_core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
deleted file mode 100644
index dee7382c..00000000
--- a/tests/conftest.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""Pytest config."""
-from pathlib import Path
-
-import numpy as np
-from pytest import fixture
-
-from pybotics.kinematic_chain import MDHKinematicChain
-from pybotics.robot import Robot
-
-
-@fixture()
-def planar_robot():
-    """Generate planar robot."""
-    return Robot(
-        MDHKinematicChain(np.array([[0, 0, 0, 0], [0, 10, 0, 0], [0, 20, 0, 0]]))
-    )
-
-
-@fixture()
-def resources_path():
-    """Get resources path."""
-    return (Path(__file__).parent / "resources").resolve()
-
-
-@fixture()
-def vector_transforms(resources_path: Path):
-    """Get resource data."""
-    data = np.genfromtxt(
-        fname=resources_path / "vector-transforms.csv", delimiter=",", dtype=str
-    )
-    return [
-        {
-            "vector": d[:6].astype(float),
-            "transform": d[6:-1].astype(float),
-            "order": d[-1],
-        }
-        for d in data
-    ]
diff --git a/tests/resources/rotx-transforms.csv b/tests/resources/rotx-transforms.csv
deleted file mode 100644
index d6d561d1..00000000
--- a/tests/resources/rotx-transforms.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-# angle[0], transform[1:]
-0.17453292519943295,1.0,0.0,0.0,0.0,0.0,0.9848077,-0.1736482,0.0,0.0,0.1736482,0.9848077,0.0,0.0,0.0,0.0,1.0
-
-
diff --git a/tests/resources/roty-transforms.csv b/tests/resources/roty-transforms.csv
deleted file mode 100644
index a68debe8..00000000
--- a/tests/resources/roty-transforms.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-# angle[0], transform[1:]
-0.17453292519943295,0.9848077,0.0,0.1736482,0.0,0.0,1.0,0.0,0.0,-0.1736482,0.0,0.9848077,0.0,0.0,0.0,0.0,1.0
-
-
diff --git a/tests/resources/rotz-transforms.csv b/tests/resources/rotz-transforms.csv
deleted file mode 100644
index b8ca5560..00000000
--- a/tests/resources/rotz-transforms.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-# angle[0], transform[1:]
-0.17453292519943295,0.9848077,-0.1736482,0.0,0.0,0.1736482,0.9848077,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0
-
-
diff --git a/tests/test_errors.py b/tests/test_errors.py
index 4f4c41d4..beb2c4c2 100644
--- a/tests/test_errors.py
+++ b/tests/test_errors.py
@@ -5,7 +5,7 @@
 from pybotics.errors import PyboticsError
 
 
-def test_errors():
+def test_errors() -> None:
     """
     Test error.
 
diff --git a/tests/test_geometry.py b/tests/test_geometry.py
index 30c74762..d1f021dd 100644
--- a/tests/test_geometry.py
+++ b/tests/test_geometry.py
@@ -1,21 +1,48 @@
 """Test geometry."""
+import typing
 from collections import Counter
 from pathlib import Path
-from typing import Sequence
+from typing import Dict, List, Sequence, Union
 
 import hypothesis.strategies as st
 import numpy as np
-from hypothesis import given, settings
+import numpy.typing as npt
+from hypothesis import given
 from hypothesis.extra.numpy import arrays
-from pytest import raises
+from pytest import fixture, raises
+from scipy.spatial.transform import Rotation  # type: ignore
 
 import pybotics.geometry
 from pybotics.errors import PyboticsError
 from pybotics.geometry import OrientationConvention, matrix_2_vector
 
+MIN_FLOAT = -1e9
+MAX_FLOAT = 1e9
+
+
+@fixture()
+def vector_transforms() -> List[Dict[str, Union[npt.NDArray[np.float64], str]]]:
+    """Get resource data."""
+
+    # load test data
+    data_path = (
+        Path(__file__).parent / "resources"
+    ).resolve() / "vector-transforms.csv"
+    data = np.genfromtxt(fname=data_path, delimiter=",", dtype=str)  # type: ignore
+
+    result = [
+        {
+            "vector": d[:6].astype(float),
+            "transform": d[6:-1].astype(float),
+            "order": d[-1],
+        }
+        for d in data
+    ]
+    return result
+
 
 @given(st.floats(allow_nan=False, allow_infinity=False))
-def test_wrap_2_pi(angle):
+def test_wrap_2_pi(angle: float) -> None:
     """
     Test angle wrapping.
 
@@ -35,63 +62,126 @@ def test_wrap_2_pi(angle):
     actual_angles = np.array(list(map(pybotics.geometry.wrap_2_pi, test_angles)))
     assert len(test_angles) == len(expected_angles)
     assert len(actual_angles) == len(expected_angles)
-    np.testing.assert_allclose(actual_angles, expected_angles)
+    np.testing.assert_allclose(actual_angles, expected_angles)  # type: ignore
 
     # test single elements
     for i, _ in enumerate(expected_angles):
         actual_angle = pybotics.geometry.wrap_2_pi(test_angles[i])
-        np.testing.assert_allclose([actual_angle], expected_angles[i])
+        np.testing.assert_allclose([actual_angle], expected_angles[i])  # type: ignore
 
 
-@given(angle=st.floats(allow_nan=False, allow_infinity=False))
-@settings(deadline=None)
-def test_rotation_matrix_xyz(angle, resources_path: Path):
-    """Test."""
-    # define functions to test
-    rotation_functions = {
-        "x": pybotics.geometry.rotation_matrix_x,
-        "y": pybotics.geometry.rotation_matrix_y,
-        "z": pybotics.geometry.rotation_matrix_z,
-    }
-
-    # iterate through rotation axes
-    for i, axis in enumerate("xyz"):
-        # get resource file
-        path = resources_path / f"rot{axis}-transforms.csv"
-        data = np.loadtxt(str(path.resolve()), delimiter=",")
-        if data.ndim == 1:
-            data = np.expand_dims(data, axis=0)
-
-        # test resource transforms
-        for d in data:
-            actual_matrix = rotation_functions[axis](d[0])
-            np.testing.assert_allclose(
-                actual=actual_matrix, desired=d[1:].reshape((4, 4)), atol=1e-6
-            )
+@given(
+    angle=st.floats(
+        allow_nan=False, allow_infinity=False, min_value=MIN_FLOAT, max_value=MAX_FLOAT
+    )
+)
+def test_rotation_matrix_x(angle: float) -> None:
+    # set axis-specific parameters
+    axis_idx = 0
+    axis_vector = np.zeros(3)
+    axis_vector[axis_idx] = 1
 
-        # test hypothesis transforms
-        actual_matrix = rotation_functions[axis](angle)
+    # test randomized hypothesis transforms
+    actual_matrix = pybotics.geometry.rotation_matrix_x(angle)
 
-        # check orthogonality
-        for row in actual_matrix:
-            # noinspection PyTypeChecker
-            np.testing.assert_allclose(np.linalg.norm(row), 1)
+    # check orthogonality
+    for row in actual_matrix:
+        np.testing.assert_allclose(np.linalg.norm(row), 1)  # type: ignore
 
-        for column in actual_matrix.T:
-            # noinspection PyTypeChecker
-            np.testing.assert_allclose(np.linalg.norm(column), 1)
+    for column in actual_matrix.T:
+        np.testing.assert_allclose(np.linalg.norm(column), 1)  # type: ignore
 
-        # check no translation
-        # noinspection PyTypeChecker
-        np.testing.assert_allclose(actual_matrix[:-1, -1], 0)
+    # check no translation
+    np.testing.assert_allclose(actual_matrix[:-1, -1], 0)  # type: ignore
 
-        # check homogeneous matrix
-        # noinspection PyTypeChecker
-        np.testing.assert_allclose(actual_matrix[-1, :-1], 0)
+    # check homogeneous matrix
+    np.testing.assert_allclose(actual_matrix[-1, :-1], 0)  # type: ignore
 
-        # check unit vector location
-        # noinspection PyTypeChecker
-        np.testing.assert_allclose(actual_matrix[i, i], 1)
+    # check unit vector location
+    np.testing.assert_allclose(actual_matrix[axis_idx, axis_idx], 1)  # type: ignore
+
+    # check 3x3 rotation component
+    actual_rotation = actual_matrix[:3, :3]
+    expected_rotation = Rotation.from_rotvec(angle * axis_vector).as_matrix()
+    np.testing.assert_allclose(  # type: ignore
+        actual=actual_rotation, desired=expected_rotation, rtol=1e-05, atol=1e-08
+    )
+
+
+@given(
+    angle=st.floats(
+        allow_nan=False, allow_infinity=False, min_value=MIN_FLOAT, max_value=MAX_FLOAT
+    )
+)
+def test_rotation_matrix_y(angle: float) -> None:
+    # set axis-specific parameters
+    axis_idx = 1
+    axis_vector = np.zeros(3)
+    axis_vector[axis_idx] = 1
+
+    # test randomized hypothesis transforms
+    actual_matrix = pybotics.geometry.rotation_matrix_y(angle)
+
+    # check orthogonality
+    for row in actual_matrix:
+        np.testing.assert_allclose(np.linalg.norm(row), 1)  # type: ignore
+
+    for column in actual_matrix.T:
+        np.testing.assert_allclose(np.linalg.norm(column), 1)  # type: ignore
+
+    # check no translation
+    np.testing.assert_allclose(actual_matrix[:-1, -1], 0)  # type: ignore
+
+    # check homogeneous matrix
+    np.testing.assert_allclose(actual_matrix[-1, :-1], 0)  # type: ignore
+
+    # check unit vector location
+    np.testing.assert_allclose(actual_matrix[axis_idx, axis_idx], 1)  # type: ignore
+
+    # check 3x3 rotation component
+    actual_rotation = actual_matrix[:3, :3]
+    expected_rotation = Rotation.from_rotvec(angle * axis_vector).as_matrix()
+    np.testing.assert_allclose(  # type: ignore
+        actual=actual_rotation, desired=expected_rotation, rtol=1e-05, atol=1e-08
+    )
+
+
+@given(
+    angle=st.floats(
+        allow_nan=False, allow_infinity=False, min_value=MIN_FLOAT, max_value=MAX_FLOAT
+    )
+)
+def test_rotation_matrix_z(angle: float) -> None:
+    # set axis-specific parameters
+    axis_idx = 2
+    axis_vector = np.zeros(3)
+    axis_vector[axis_idx] = 1
+
+    # test randomized hypothesis transforms
+    actual_matrix = pybotics.geometry.rotation_matrix_z(angle)
+
+    # check orthogonality
+    for row in actual_matrix:
+        np.testing.assert_allclose(np.linalg.norm(row), 1)  # type: ignore
+
+    for column in actual_matrix.T:
+        np.testing.assert_allclose(np.linalg.norm(column), 1)  # type: ignore
+
+    # check no translation
+    np.testing.assert_allclose(actual_matrix[:-1, -1], 0)  # type: ignore
+
+    # check homogeneous matrix
+    np.testing.assert_allclose(actual_matrix[-1, :-1], 0)  # type: ignore
+
+    # check unit vector location
+    np.testing.assert_allclose(actual_matrix[axis_idx, axis_idx], 1)  # type: ignore
+
+    # check 3x3 rotation component
+    actual_rotation = actual_matrix[:3, :3]
+    expected_rotation = Rotation.from_rotvec(angle * axis_vector).as_matrix()
+    np.testing.assert_allclose(  # type: ignore
+        actual=actual_rotation, desired=expected_rotation, rtol=1e-05, atol=1e-08
+    )
 
 
 @given(
@@ -101,39 +191,43 @@ def test_rotation_matrix_xyz(angle, resources_path: Path):
         elements=st.floats(allow_nan=False, allow_infinity=False),
     )
 )
-def test_translation_matrix(xyz):
+def test_translation_matrix(xyz: npt.NDArray[np.float64]) -> None:
     """Test."""
     matrix = pybotics.geometry.translation_matrix(xyz)
 
     # check orthogonality
     for row in matrix[:-1, :-1]:
         # noinspection PyTypeChecker
-        np.testing.assert_allclose(np.linalg.norm(row), 1)
+        np.testing.assert_allclose(np.linalg.norm(row), 1)  # type: ignore
 
     for column in matrix[:, :-1].T:
         # noinspection PyTypeChecker
-        np.testing.assert_allclose(np.linalg.norm(column), 1)
+        np.testing.assert_allclose(np.linalg.norm(column), 1)  # type: ignore
 
     # check translation
     # noinspection PyTypeChecker
-    np.testing.assert_allclose(matrix[:-1, -1], xyz)
+    np.testing.assert_allclose(matrix[:-1, -1], xyz)  # type: ignore
 
     # check homogeneous matrix
     # noinspection PyTypeChecker
-    np.testing.assert_allclose(matrix[-1, :-1], 0)
+    np.testing.assert_allclose(matrix[-1, :-1], 0)  # type: ignore
 
     # test exception
     with raises(PyboticsError):
         pybotics.geometry.translation_matrix(np.zeros(10))
 
 
-def test_vector_2_matrix(vector_transforms: Sequence[dict]):
+def test_vector_2_matrix(
+    vector_transforms: Sequence[typing.Dict[str, npt.NDArray[np.float64]]]
+) -> None:
     """Test."""
     # test regular usage
     for d in vector_transforms:
         for c in [d["order"], OrientationConvention(d["order"])]:
-            actual = pybotics.geometry.vector_2_matrix(d["vector"], convention=c)
-            np.testing.assert_allclose(
+            actual = pybotics.geometry.vector_2_matrix(
+                d["vector"], convention=c  # type: ignore
+            )
+            np.testing.assert_allclose(  # type: ignore
                 actual=actual, desired=d["transform"].reshape((4, 4)), atol=1e-6
             )
 
@@ -142,7 +236,9 @@ def test_vector_2_matrix(vector_transforms: Sequence[dict]):
             pybotics.geometry.vector_2_matrix(d["vector"], convention="foobar")
 
 
-def test_matrix_2_vector(vector_transforms: Sequence[dict]):
+def test_matrix_2_vector(
+    vector_transforms: Sequence[typing.Dict[str, npt.NDArray[np.float64]]]
+) -> None:
     """Test."""
     for d in vector_transforms:
         for c in [
@@ -156,12 +252,12 @@ def test_matrix_2_vector(vector_transforms: Sequence[dict]):
                 # TODO: implement other conversions
                 # don't fail for NotImplementedError
                 continue
-            np.testing.assert_allclose(
+            np.testing.assert_allclose(  # type: ignore
                 actual=actual_vector, desired=d["vector"], atol=1e-6
             )
 
 
-def test_orientation():
+def test_orientation() -> None:
     """Test."""
     # ensure order and name match
     for e in list(OrientationConvention.__members__.values()):
@@ -175,6 +271,6 @@ def test_orientation():
 
     # ensure only x,y,z are used
     good_letters = set("xyz")
-    values = set([e.value for e in OrientationConvention.__members__.values()])
+    values = list(set([e.value for e in OrientationConvention.__members__.values()]))
     leftover_values = [x for x in values if set(x).difference(good_letters)]
     assert not leftover_values
diff --git a/tests/test_kinematic_chain.py b/tests/test_kinematic_chain.py
index 1290758b..beaaaa68 100644
--- a/tests/test_kinematic_chain.py
+++ b/tests/test_kinematic_chain.py
@@ -7,52 +7,52 @@
 from pybotics.link import MDHLink, RevoluteMDHLink
 
 
-def test_init():
+def test_init() -> None:
     """Test."""
     # test error
     with raises(PyboticsError):
-        MDHKinematicChain(np.eye(5))
+        MDHKinematicChain(np.eye(5))  # type: ignore
 
     # test sequence of links
     MDHKinematicChain([RevoluteMDHLink()])
 
 
-def test_num_parameters():
+def test_num_parameters() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
     assert kc.num_parameters == MDHLink._size
 
 
-def test_vector():
+def test_vector() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
-    np.testing.assert_allclose(kc.vector, link.vector)
+    np.testing.assert_allclose(kc.vector, link.vector)  # type: ignore
 
 
-def test_repr():
+def test_repr() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
     repr(kc)
 
 
-def test_to_json():
+def test_to_json() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
     kc.to_json()
 
 
-def test_links_setter():
+def test_links_setter() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
-    kc.links = link
+    kc.links = [link]
 
 
-def test_ndof():
+def test_ndof() -> None:
     """Test."""
     link = RevoluteMDHLink()
     kc = MDHKinematicChain([link])
diff --git a/tests/test_link.py b/tests/test_link.py
index 17f9acc1..5cf11976 100644
--- a/tests/test_link.py
+++ b/tests/test_link.py
@@ -4,24 +4,24 @@
 from pybotics.link import PrismaticMDHLink, RevoluteMDHLink
 
 
-def test_len():
+def test_len() -> None:
     """Test."""
     assert len(RevoluteMDHLink()) == 4
 
 
-def test_displace():
+def test_displace() -> None:
     """Test."""
     link = PrismaticMDHLink()
-    np.testing.assert_allclose(link.displace(), link.vector)
+    np.testing.assert_allclose(link.displace(), link.vector)  # type: ignore
 
 
-def test_repr():
+def test_repr() -> None:
     """Test."""
     link = RevoluteMDHLink()
     repr(link)
 
 
-def test_json():
+def test_json() -> None:
     """Test."""
     link = RevoluteMDHLink()
     link.to_json()
diff --git a/tests/test_optimization.py b/tests/test_optimization.py
index 09db379a..1550b9a6 100644
--- a/tests/test_optimization.py
+++ b/tests/test_optimization.py
@@ -4,7 +4,8 @@
 """
 import hypothesis.strategies as st
 import numpy as np
-import scipy.optimize
+import numpy.typing as npt
+import scipy.optimize  # type: ignore
 from hypothesis import given
 from hypothesis.extra.numpy import arrays
 from pytest import raises
@@ -29,21 +30,24 @@
         elements=st.floats(allow_nan=False, allow_infinity=False),
     )
 )
-def test_compute_absolute_errors(q: np.ndarray):
+def test_compute_absolute_errors(q: npt.NDArray[np.float64]) -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
     pose = robot.fk(q)
-    p = pose[:-1, -1]
+    position_vector = pose[:-1, -1]
 
-    # test 1D input
-    actual_error = compute_absolute_error(q=q, position=p, robot=robot)
-    np.testing.assert_allclose(actual_error, 0)
+    # test 1D input; i.e., only one pose
+    actual_error = compute_absolute_error(q=q, position=position_vector, robot=robot)
+    np.testing.assert_allclose(actual_error, 0)  # type: ignore
 
-    # test 2D input
-    actual_error = compute_absolute_errors(
-        qs=np.tile(q, (10, 1)), positions=np.tile(p, (10, 1)), robot=robot
+    # test 2D input; i.e., many poses
+    num_repeats = 10
+    actual_error = compute_absolute_errors(  # type: ignore
+        qs=np.tile(q, (num_repeats, 1)),  # type: ignore
+        positions=np.tile(position_vector, (num_repeats, 1)),  # type: ignore
+        robot=robot,
     )
-    np.testing.assert_allclose(actual_error, 0)
+    np.testing.assert_allclose(actual_error, 0)  # type: ignore
 
 
 @given(
@@ -58,39 +62,44 @@ def test_compute_absolute_errors(q: np.ndarray):
         elements=st.floats(allow_nan=False, allow_infinity=False),
     ),
 )
-def test_compute_relative_errors(q_a: np.ndarray, q_b: np.ndarray):
+def test_compute_relative_errors(
+    q_a: npt.NDArray[np.float64], q_b: npt.NDArray[np.float64]
+) -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
 
     p_a = robot.fk(q_a)[:-1, -1]
     p_b = robot.fk(q_b)[:-1, -1]
-    distance = np.linalg.norm(p_a - p_b)
+    distance = np.linalg.norm(p_a - p_b)  # type: ignore
 
-    # test 1D input
+    # test 1D input; i.e., only one pose
     actual_error = compute_relative_error(
         q_a=q_a, q_b=q_b, distance=distance, robot=robot
     )
-    np.testing.assert_allclose(actual_error, 0)
-
-    # test 2D input
-    actual_error = compute_relative_errors(
-        qs_a=np.tile(q_a, (10, 1)),
-        qs_b=np.tile(q_b, (10, 1)),
-        distances=np.tile(distance, (10, 1)),
+    np.testing.assert_allclose(actual_error, 0)  # type: ignore
+
+    # test 2D input; i.e., many poses
+    num_repeats = 10
+    actual_error = compute_relative_errors(  # type: ignore
+        qs_a=np.tile(q_a, (num_repeats, 1)),  # type: ignore
+        qs_b=np.tile(q_b, (num_repeats, 1)),  # type: ignore
+        distances=np.tile(distance, (num_repeats, 1)),  # type: ignore
         robot=robot,
     )
-    np.testing.assert_allclose(actual_error, 0)
+    np.testing.assert_allclose(actual_error, 0)  # type: ignore
 
 
-def test_optimization():
+def test_optimization() -> None:
     """Test."""
     # init robot model and error wrt nominal
     actual_robot = Robot.from_parameters(ur10())
-    actual_robot.tool.position = [0.1, 0, 0]
-    actual_robot.kinematic_chain.links[0].a += 0.1
+    actual_robot.tool.position = np.array([0.1, 0, 0])
+    # FIXME: "Link" has no attribute "a"
+    # TODO: review abstract inheritance
+    actual_robot.kinematic_chain.links[0].a += 0.1  # type: ignore
 
     # calculate fk
-    qs = np.tile(
+    qs = np.tile(  # type: ignore
         np.linspace(start=-np.pi, stop=np.pi, num=100), (len(ur10()), 1)
     ).transpose()
 
@@ -112,23 +121,23 @@ def test_optimization():
 
     # validate
     atol = 1e-2
-    np.testing.assert_allclose(
+    np.testing.assert_allclose(  # type: ignore
         actual=result.x, desired=handler.generate_optimization_vector(), atol=atol
     )
-    np.testing.assert_allclose(
+    np.testing.assert_allclose(  # type: ignore
         actual=handler.robot.kinematic_chain.vector,
         desired=actual_robot.kinematic_chain.vector,
         atol=atol,
     )
-    np.testing.assert_allclose(
+    np.testing.assert_allclose(  # type: ignore
         actual=handler.robot.tool.vector, desired=actual_robot.tool.vector, atol=atol
     )
-    np.testing.assert_allclose(
+    np.testing.assert_allclose(  # type: ignore
         actual=handler.robot.world_frame, desired=actual_robot.world_frame, atol=atol
     )
 
 
-def test_handler_validate_transform_mask():
+def test_handler_validate_transform_mask() -> None:
     """Test."""
     # test predesigned mask sequence
     OptimizationHandler(robot=Robot.from_parameters(ur10()), tool_mask=[False] * 6)
diff --git a/tests/test_robot.py b/tests/test_robot.py
index 52546b8d..1d693871 100644
--- a/tests/test_robot.py
+++ b/tests/test_robot.py
@@ -3,26 +3,40 @@
 
 import hypothesis
 import numpy as np
+import numpy.typing as npt
 from hypothesis import given
 from hypothesis.extra.numpy import arrays
 from hypothesis.strategies import floats
-from pytest import raises
+from pytest import fixture, raises
 
 from pybotics.errors import PyboticsError
+from pybotics.kinematic_chain import MDHKinematicChain
 from pybotics.predefined_models import ur10
 from pybotics.robot import Robot
 
 
-def test_fk(resources_path: Path):
+@fixture(scope="session")
+def planar_robot() -> Robot:
+    """Generate planar robot."""
+    return Robot(
+        MDHKinematicChain.from_parameters(
+            np.array([[0, 0, 0, 0], [0, 10, 0, 0], [0, 20, 0, 0]])
+        )
+    )
+
+
+def test_fk() -> None:
     """
     Test robot.
 
     :param robot:
     :return:
     """
-    # get resource
-    path = resources_path / "ur10-joints-poses.csv"
-    data = np.loadtxt(str(path), delimiter=",")
+    # load test data
+    data_path = (
+        Path(__file__).parent / "resources"
+    ).resolve() / "ur10-joints-poses.csv"
+    data = np.loadtxt(str(data_path), delimiter=",")  # type: ignore
 
     # load robot
     robot = Robot.from_parameters(ur10())
@@ -37,23 +51,23 @@ def test_fk(resources_path: Path):
 
         # test with position argument
         actual_pose = robot.fk(q=joints)
-        np.testing.assert_allclose(actual_pose, desired_pose, atol=atol)
+        np.testing.assert_allclose(actual_pose, desired_pose, atol=atol)  # type: ignore
 
         # test with internal position attribute
         robot.joints = joints
         actual_pose = robot.fk()
-        np.testing.assert_allclose(actual_pose, desired_pose, atol=atol)
+        np.testing.assert_allclose(actual_pose, desired_pose, atol=atol)  # type: ignore
 
 
-def test_home_position():
+def test_home_position() -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
     x = np.ones(len(robot))
     robot.home_position = x
-    np.testing.assert_allclose(robot.home_position, x)
+    np.testing.assert_allclose(robot.home_position, x)  # type: ignore
 
 
-def test_joint_limits():
+def test_joint_limits() -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
 
@@ -68,7 +82,7 @@ def test_joint_limits():
         robot.joints = robot.joint_limits.copy()[1] + 10
 
 
-def test_compute_joint_torques(planar_robot: Robot):
+def test_compute_joint_torques(planar_robot: Robot) -> None:
     """
     Test.
 
@@ -78,15 +92,17 @@ def test_compute_joint_torques(planar_robot: Robot):
     :return:
     """
     # set test force and angles
-    force = [-100, -200, 0]
-    moment = [0] * 3
-    wrench = force + moment
+    force = np.array([-100, -200, 0])
+    moment = np.zeros(3)
+    wrench = force + moment  # type: npt.NDArray[np.float64]
     joint_angles = np.deg2rad([30, 60, 0])
 
     # get link lengths
+    # FIXME: "Link" has no attribute "a"
+    # TODO: review abstract inheritance
     lengths = [
-        planar_robot.kinematic_chain.links[1].a,
-        planar_robot.kinematic_chain.links[2].a,
+        planar_robot.kinematic_chain.links[1].a,  # type: ignore
+        planar_robot.kinematic_chain.links[2].a,  # type: ignore
     ]
 
     # calculate expected torques
@@ -99,11 +115,11 @@ def test_compute_joint_torques(planar_robot: Robot):
 
     # test
     actual_torques = planar_robot.compute_joint_torques(q=joint_angles, wrench=wrench)
-    np.testing.assert_allclose(actual_torques, expected_torques)
+    np.testing.assert_allclose(actual_torques, expected_torques)  # type: ignore
 
     planar_robot.joints = joint_angles
     actual_torques = planar_robot.compute_joint_torques(wrench=wrench)
-    np.testing.assert_allclose(actual_torques, expected_torques)
+    np.testing.assert_allclose(actual_torques, expected_torques)  # type: ignore
 
 
 @given(
@@ -115,12 +131,14 @@ def test_compute_joint_torques(planar_robot: Robot):
         ),
     )
 )
-def test_jacobian_world(q: np.ndarray, planar_robot: Robot):
+def test_jacobian_world(q: npt.NDArray[np.float64], planar_robot: Robot) -> None:
     """Test."""
     # get link lengths
+    # FIXME: "Link" has no attribute "a"
+    # TODO: review abstract inheritance
     lengths = [
-        planar_robot.kinematic_chain.links[1].a,
-        planar_robot.kinematic_chain.links[2].a,
+        planar_robot.kinematic_chain.links[1].a,  # type: ignore
+        planar_robot.kinematic_chain.links[2].a,  # type: ignore
     ]
 
     # example from Craig has last joint set to 0
@@ -140,7 +158,7 @@ def test_jacobian_world(q: np.ndarray, planar_robot: Robot):
     expected[-1, :] = 1
 
     actual = planar_robot.jacobian_world(q)
-    np.testing.assert_allclose(actual, expected, atol=1e-3)
+    np.testing.assert_allclose(actual, expected, atol=1e-3)  # type: ignore
 
 
 @given(
@@ -148,12 +166,14 @@ def test_jacobian_world(q: np.ndarray, planar_robot: Robot):
         shape=(3,), dtype=float, elements=floats(allow_nan=False, allow_infinity=False)
     )
 )
-def test_jacobian_flange(q: np.ndarray, planar_robot: Robot):
+def test_jacobian_flange(q: npt.NDArray[np.float64], planar_robot: Robot) -> None:
     """Test."""
     # get link lengths
+    # FIXME: "Link" has no attribute "a"
+    # TODO: review abstract inheritance
     lengths = [
-        planar_robot.kinematic_chain.links[1].a,
-        planar_robot.kinematic_chain.links[2].a,
+        planar_robot.kinematic_chain.links[1].a,  # type: ignore
+        planar_robot.kinematic_chain.links[2].a,  # type: ignore
     ]
 
     # example from Craig has last joint set to 0
@@ -169,7 +189,7 @@ def test_jacobian_flange(q: np.ndarray, planar_robot: Robot):
     expected[-1, :] = 1
 
     actual = planar_robot.jacobian_flange(q)
-    np.testing.assert_allclose(actual, expected, atol=1e-6)
+    np.testing.assert_allclose(actual, expected, atol=1e-6)  # type: ignore
 
 
 @given(
@@ -192,7 +212,7 @@ def test_jacobian_flange(q: np.ndarray, planar_robot: Robot):
     ),
 )
 @hypothesis.settings(deadline=None)
-def test_ik(q: np.ndarray, q_offset: np.ndarray):
+def test_ik(q: npt.NDArray[np.float64], q_offset: npt.NDArray[np.float64]) -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
     pose = robot.fk(q)
@@ -209,22 +229,24 @@ def test_ik(q: np.ndarray, q_offset: np.ndarray):
 
     # test the matrix with lower accuracy
     # rotation components are hard to achieve when x0 isn't good
-    np.testing.assert_allclose(actual_pose, pose, atol=1)
+    np.testing.assert_allclose(actual_pose, pose, atol=1)  # type: ignore
 
     # test the position with higher accuracy
     desired_position = pose[:-1, -1]
     actual_position = actual_pose[:-1, -1]
-    np.testing.assert_allclose(actual_position, desired_position, atol=1e-1)
+    np.testing.assert_allclose(  # type: ignore
+        actual_position, desired_position, atol=1e-1
+    )
 
 
-def test_random_joints():
+def test_random_joints() -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
     robot.random_joints()
     robot.random_joints(in_place=True)
 
 
-def test_to_json():
+def test_to_json() -> None:
     """Test."""
     robot = Robot.from_parameters(ur10())
     robot.to_json()
diff --git a/tests/test_tool.py b/tests/test_tool.py
index 3ae6395b..95f4f4e6 100644
--- a/tests/test_tool.py
+++ b/tests/test_tool.py
@@ -5,15 +5,17 @@
 from pybotics.tool import Tool
 
 
-def test_tool():
+def test_tool() -> None:
     """Test."""
     tool = Tool()
 
-    cg = [1, 2, 3]
+    cg = np.array([1, 2, 3])
     tool.cg = cg
-    np.testing.assert_allclose(tool.cg, cg)
+    np.testing.assert_allclose(tool.cg, cg)  # type: ignore
 
-    p = [1, 2, 3]
+    p = np.array([1, 2, 3])
     tool.position = p
-    np.testing.assert_allclose(tool.position, p)
-    np.testing.assert_allclose(tool.vector, matrix_2_vector(tool.matrix))
+    np.testing.assert_allclose(tool.position, p)  # type: ignore
+    np.testing.assert_allclose(  # type: ignore
+        tool.vector, matrix_2_vector(tool.matrix)
+    )