Repository: KyleKing/calcipy (language: python)

Issue:
Switch from pdocs to mkdocstrings
See:

- [x] Follows #25
- [x] https://github.com/timothycrosley/pdocs/pull/25
- [x] https://github.com/artigraph/artigraph/issues/190
- [x] https://github.com/theskumar/python-dotenv/commit/cb53e1e530d9262e327c2b7b09e2c5424d544e3e
- [x] Generate markdown files for each module (grep all python files, take relative paths, generate files)
    - [x] See [template here](https://github.com/mkdocstrings/python/blob/32030df6a98b527a3f5167e1836a36efae6530d4/docs/gen_ref_nav.py) which generates the [nested left sidenav](https://mkdocstrings.github.io/python/reference/mkdocstrings_handlers/python/handler/)
refactor: move check_security from nox into doit (Sourcery refactored)
Pull Request #110 refactored by [Sourcery](https://sourcery.ai/github/).

If you're happy with these changes, merge this Pull Request using the *Squash and merge* strategy.

**NOTE**: As code is pushed to the original Pull Request, Sourcery will
re-run and update (force-push) this Pull Request with new refactorings as
necessary. If Sourcery finds no refactorings at any point, this Pull Request
will be closed automatically.

See our documentation [here](https://docs.sourcery.ai/GitHub/Using-Sourcery-for-GitHub/).

<details>
<summary>Run Sourcery locally</summary>
<p>
Reduce the feedback loop during development by using the Sourcery editor plugin:
</p>
<ul>
<li><a href="https://sourcery.ai/download/?editor=vscode">VS Code</a></li>
<li><a href="https://sourcery.ai/download/?editor=pycharm">PyCharm</a></li>
</ul>
</details>

<details>
<summary>Review changes via command line</summary>
<p>To manually merge these changes, make sure you're on the <code>dev/development</code> branch, then run:</p>
<pre>
git fetch origin sourcery/dev/development
git merge --ff-only FETCH_HEAD
git reset HEAD^
</pre>
</details>

Help us [improve](https://research.typeform.com/to/j06Spdfr?type=pr_refactor&github_login=KyleKing&base_repo=https%3A%2F%2Fgithub.com%2FKyleKing%2Fcalcipy.git&base_remote_ref=refs%2Fpull%2F110%2Fhead&base_ref=dev%2Fdevelopment&base_sha=f50827c0085816d6bb71e99a6cabd37e12eba4d8&head_repo=https%3A%2F%2Fgithub.com%2FKyleKing%2Fcalcipy.git&head_ref=sourcery%2Fdev%2Fdevelopment&base_pr_number=110&base_pr_state=open) this pull request!


Discussion / hints:

Fix patch:
diff --git a/.calcipy_packaging.lock b/.calcipy_packaging.lock
index e628e105..58050a09 100644
--- a/.calcipy_packaging.lock
+++ b/.calcipy_packaging.lock
@@ -23,21 +23,13 @@
         "name": "Jinja2",
         "version": "3.1.2"
     },
-    "Mako": {
-        "datetime": "2022-08-29T18:03:52.207596+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-08-29T18:03:52.207596+00:00",
-        "latest_version": "1.2.2",
-        "name": "Mako",
-        "version": "1.2.2"
-    },
     "Markdown": {
-        "datetime": "2022-07-15T19:15:37.044395+00:00",
+        "datetime": "2022-05-05T19:08:38.155612+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
         "latest_datetime": "2022-07-15T19:15:37.044395+00:00",
         "latest_version": "3.4.1",
         "name": "Markdown",
-        "version": "3.4.1"
+        "version": "3.3.7"
     },
     "MarkupSafe": {
         "datetime": "2022-03-15T13:22:05.942273+00:00",
@@ -71,6 +63,14 @@
         "name": "Pygments",
         "version": "2.13.0"
     },
+    "StrEnum": {
+        "datetime": "2022-06-22T06:04:09.015277+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-06-22T06:04:09.015277+00:00",
+        "latest_version": "0.4.8",
+        "name": "StrEnum",
+        "version": "0.4.8"
+    },
     "absolufy-imports": {
         "datetime": "2022-01-20T14:48:51.718037+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -80,20 +80,20 @@
         "version": "0.3.1"
     },
     "add-trailing-comma": {
-        "datetime": "2022-04-17T13:58:01.414145+00:00",
+        "datetime": "2022-09-26T02:38:10.441541+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-04-17T13:58:01.414145+00:00",
-        "latest_version": "2.2.3",
+        "latest_datetime": "2022-09-26T02:38:10.441541+00:00",
+        "latest_version": "2.3.0",
         "name": "add-trailing-comma",
-        "version": "2.2.3"
+        "version": "2.3.0"
     },
     "argcomplete": {
-        "datetime": "2021-04-19T15:48:18.241321+00:00",
+        "datetime": "2022-01-03T17:29:35.127698+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
         "latest_datetime": "2022-01-03T17:29:35.127698+00:00",
         "latest_version": "2.0.0",
         "name": "argcomplete",
-        "version": "1.12.3"
+        "version": "2.0.0"
     },
     "arrow": {
         "datetime": "2022-09-03T19:35:29.660994+00:00",
@@ -143,21 +143,13 @@
         "name": "attrs",
         "version": "21.4.0"
     },
-    "attrs-strict": {
-        "datetime": "2021-12-28T23:10:02.878548+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2021-12-28T23:10:02.878548+00:00",
-        "latest_version": "1.0.0",
-        "name": "attrs-strict",
-        "version": "1.0.0"
-    },
     "autoflake": {
-        "datetime": "2022-09-01T05:07:53.791347+00:00",
+        "datetime": "2022-09-21T16:16:28.816972+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-01T05:07:53.791347+00:00",
-        "latest_version": "1.5.3",
+        "latest_datetime": "2022-09-21T16:16:28.816972+00:00",
+        "latest_version": "1.6.1",
         "name": "autoflake",
-        "version": "1.5.3"
+        "version": "1.6.1"
     },
     "autopep8": {
         "datetime": "2022-08-09T12:55:25.914554+00:00",
@@ -176,12 +168,12 @@
         "version": "1.7.4"
     },
     "beartype": {
-        "datetime": "2022-03-15T06:06:23.515331+00:00",
+        "datetime": "2022-09-18T06:23:32.983253+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-03-15T06:06:23.515331+00:00",
-        "latest_version": "0.10.4",
+        "latest_datetime": "2022-09-18T06:23:32.983253+00:00",
+        "latest_version": "0.11.0",
         "name": "beartype",
-        "version": "0.10.4"
+        "version": "0.11.0"
     },
     "better-exceptions": {
         "datetime": "2021-01-29T16:48:55.897166+00:00",
@@ -207,6 +199,22 @@
         "name": "black",
         "version": "22.8.0"
     },
+    "boltons": {
+        "datetime": "2021-05-17T01:20:20.023620+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-05-17T01:20:20.023620+00:00",
+        "latest_version": "21.0.0",
+        "name": "boltons",
+        "version": "21.0.0"
+    },
+    "bracex": {
+        "datetime": "2022-05-30T14:58:01.709129+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-05-30T14:58:01.709129+00:00",
+        "latest_version": "2.3.post1",
+        "name": "bracex",
+        "version": "2.3.post1"
+    },
     "cement": {
         "datetime": "2022-08-18T19:10:40.137300+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -216,12 +224,12 @@
         "version": "3.0.8"
     },
     "certifi": {
-        "datetime": "2022-09-14T19:48:47.436198+00:00",
+        "datetime": "2022-09-24T14:26:51.522536+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-14T19:48:47.436198+00:00",
-        "latest_version": "2022.9.14",
+        "latest_datetime": "2022-09-24T14:26:51.522536+00:00",
+        "latest_version": "2022.9.24",
         "name": "certifi",
-        "version": "2022.9.14"
+        "version": "2022.9.24"
     },
     "cfgv": {
         "datetime": "2021-08-25T14:18:47.770979+00:00",
@@ -255,6 +263,14 @@
         "name": "click",
         "version": "8.1.3"
     },
+    "click-option-group": {
+        "datetime": "2021-05-14T20:39:50.222790+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-05-14T20:39:50.222790+00:00",
+        "latest_version": "0.5.3",
+        "name": "click-option-group",
+        "version": "0.5.3"
+    },
     "cloudpickle": {
         "datetime": "2022-09-07T16:12:56.619702+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -296,12 +312,12 @@
         "version": "6.7.0"
     },
     "commitizen": {
-        "datetime": "2022-09-16T05:49:22.702924+00:00",
+        "datetime": "2022-09-23T09:48:49.169775+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-16T05:49:22.702924+00:00",
-        "latest_version": "2.33.1",
+        "latest_datetime": "2022-09-23T09:48:49.169775+00:00",
+        "latest_version": "2.35.0",
         "name": "commitizen",
-        "version": "2.33.1"
+        "version": "2.35.0"
     },
     "commonmark": {
         "datetime": "2019-10-04T15:37:37.674968+00:00",
@@ -336,12 +352,12 @@
         "version": "0.11.0"
     },
     "cz-legacy": {
-        "datetime": "2022-02-18T21:01:09.403199+00:00",
+        "datetime": "2022-09-17T17:54:00.768969+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-02-18T21:01:09.403199+00:00",
-        "latest_version": "0.1.6",
+        "latest_datetime": "2022-09-17T17:54:00.768969+00:00",
+        "latest_version": "0.1.7",
         "name": "cz-legacy",
-        "version": "0.1.6"
+        "version": "0.1.7"
     },
     "darglint": {
         "datetime": "2021-10-18T03:40:35.034675+00:00",
@@ -367,21 +383,29 @@
         "name": "decorator",
         "version": "5.1.1"
     },
-    "dictdiffer": {
-        "datetime": "2021-07-22T13:24:26.783705+00:00",
+    "deepdiff6": {
+        "datetime": "2022-09-04T07:57:24.286688+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2021-07-22T13:24:26.783705+00:00",
-        "latest_version": "0.9.0",
-        "name": "dictdiffer",
-        "version": "0.9.0"
+        "latest_datetime": "2022-09-04T07:57:24.286688+00:00",
+        "latest_version": "6.2.0",
+        "name": "deepdiff6",
+        "version": "6.2.0"
+    },
+    "defusedxml": {
+        "datetime": "2021-03-08T10:59:24.450195+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-03-08T10:59:24.450195+00:00",
+        "latest_version": "0.7.1",
+        "name": "defusedxml",
+        "version": "0.7.1"
     },
     "diff-cover": {
-        "datetime": "2022-06-22T22:07:50.268916+00:00",
+        "datetime": "2022-09-20T00:16:55.140950+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-06-22T22:07:50.268916+00:00",
-        "latest_version": "6.5.1",
+        "latest_datetime": "2022-09-20T00:16:55.140950+00:00",
+        "latest_version": "7.0.1",
         "name": "diff-cover",
-        "version": "6.5.1"
+        "version": "7.0.1"
     },
     "dill": {
         "datetime": "2022-05-20T11:21:11.969466+00:00",
@@ -415,14 +439,6 @@
         "name": "docformatter",
         "version": "1.5.0"
     },
-    "docstring-parser": {
-        "datetime": "2020-09-19T11:51:11.452731+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-04-27T08:32:40.566840+00:00",
-        "latest_version": "0.14.1",
-        "name": "docstring-parser",
-        "version": "0.7.3"
-    },
     "doit": {
         "datetime": "2022-04-22T15:33:23.165732+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -447,13 +463,13 @@
         "name": "exceptiongroup",
         "version": "1.0.0rc9"
     },
-    "falcon": {
-        "datetime": "2019-04-26T18:26:34.046785+00:00",
+    "face": {
+        "datetime": "2020-01-22T20:03:46.102781+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-03-25T16:21:09.243363+00:00",
-        "latest_version": "3.1.0",
-        "name": "falcon",
-        "version": "2.0.0"
+        "latest_datetime": "2020-01-22T20:03:46.102781+00:00",
+        "latest_version": "20.1.1",
+        "name": "face",
+        "version": "20.1.1"
     },
     "fancycompleter": {
         "datetime": "2020-02-04T13:30:08.106140+00:00",
@@ -560,12 +576,12 @@
         "version": "0.5.0"
     },
     "flake8-bugbear": {
-        "datetime": "2022-09-11T19:15:31.013333+00:00",
+        "datetime": "2022-09-23T17:47:11.357469+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-11T19:15:31.013333+00:00",
-        "latest_version": "22.9.11",
+        "latest_datetime": "2022-09-23T17:47:11.357469+00:00",
+        "latest_version": "22.9.23",
         "name": "flake8-bugbear",
-        "version": "22.9.11"
+        "version": "22.9.23"
     },
     "flake8-builtins": {
         "datetime": "2020-05-14T10:45:01.642922+00:00",
@@ -608,12 +624,12 @@
         "version": "1.6.0"
     },
     "flake8-eradicate": {
-        "datetime": "2022-08-11T07:16:46.028643+00:00",
+        "datetime": "2022-09-20T20:05:28.635541+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-08-11T07:16:46.028643+00:00",
-        "latest_version": "1.3.0",
+        "latest_datetime": "2022-09-20T20:05:28.635541+00:00",
+        "latest_version": "1.4.0",
         "name": "flake8-eradicate",
-        "version": "1.3.0"
+        "version": "1.4.0"
     },
     "flake8-expression-complexity": {
         "datetime": "2022-03-19T07:13:03.128527+00:00",
@@ -671,6 +687,14 @@
         "name": "flake8-pep3101",
         "version": "1.3.0"
     },
+    "flake8-pie": {
+        "datetime": "2022-08-08T23:51:14.535335+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-08-08T23:51:14.535335+00:00",
+        "latest_version": "0.16.0",
+        "name": "flake8-pie",
+        "version": "0.16.0"
+    },
     "flake8-plone-hasattr": {
         "datetime": "2017-05-31T21:17:18.249556+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -751,6 +775,14 @@
         "name": "flake8-tuple",
         "version": "0.4.1"
     },
+    "flake8-typing-imports": {
+        "datetime": "2022-08-01T16:43:18.464875+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-08-01T16:43:18.464875+00:00",
+        "latest_version": "1.13.0",
+        "name": "flake8-typing-imports",
+        "version": "1.13.0"
+    },
     "flake8-variables-names": {
         "datetime": "2022-02-24T12:39:55.482580+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -760,12 +792,12 @@
         "version": "0.0.5"
     },
     "fonttools": {
-        "datetime": "2022-09-15T17:30:00.764022+00:00",
+        "datetime": "2022-09-20T10:00:51.959004+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-15T17:30:00.764022+00:00",
-        "latest_version": "4.37.2",
+        "latest_datetime": "2022-09-20T10:00:51.959004+00:00",
+        "latest_version": "4.37.3",
         "name": "fonttools",
-        "version": "4.37.2"
+        "version": "4.37.3"
     },
     "future": {
         "datetime": "2019-10-31T00:58:13.043307+00:00",
@@ -791,21 +823,29 @@
         "name": "gitdb",
         "version": "4.0.9"
     },
-    "hug": {
-        "datetime": "2020-02-06T18:13:37.987522+00:00",
+    "glom": {
+        "datetime": "2022-01-24T09:34:02.391619+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-01-24T09:34:02.391619+00:00",
+        "latest_version": "22.1.0",
+        "name": "glom",
+        "version": "22.1.0"
+    },
+    "griffe": {
+        "datetime": "2022-09-24T17:20:23.235612+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2020-02-06T18:13:37.987522+00:00",
-        "latest_version": "2.6.1",
-        "name": "hug",
-        "version": "2.6.1"
+        "latest_datetime": "2022-09-24T17:20:23.235612+00:00",
+        "latest_version": "0.22.2",
+        "name": "griffe",
+        "version": "0.22.2"
     },
     "hypothesis": {
-        "datetime": "2022-09-05T04:51:17.605468+00:00",
+        "datetime": "2022-09-18T05:03:22.359500+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-05T04:51:17.605468+00:00",
-        "latest_version": "6.54.5",
+        "latest_datetime": "2022-09-18T05:03:22.359500+00:00",
+        "latest_version": "6.54.6",
         "name": "hypothesis",
-        "version": "6.54.5"
+        "version": "6.54.6"
     },
     "identify": {
         "datetime": "2022-09-06T12:37:06.753575+00:00",
@@ -839,6 +879,14 @@
         "name": "importlib-metadata",
         "version": "4.12.0"
     },
+    "importlib-resources": {
+        "datetime": "2022-07-22T16:47:03.952797+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-07-22T16:47:03.952797+00:00",
+        "latest_version": "5.9.0",
+        "name": "importlib-resources",
+        "version": "5.9.0"
+    },
     "iniconfig": {
         "datetime": "2020-10-16T17:37:23.050185+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -855,6 +903,14 @@
         "name": "isort",
         "version": "5.10.1"
     },
+    "jsonschema": {
+        "datetime": "2022-09-09T09:46:03.358711+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-09T09:46:03.358711+00:00",
+        "latest_version": "4.16.0",
+        "name": "jsonschema",
+        "version": "4.16.0"
+    },
     "kiwisolver": {
         "datetime": "2022-07-15T20:37:00.009703+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -927,6 +983,14 @@
         "name": "mccabe",
         "version": "0.7.0"
     },
+    "mdx-truly-sane-lists": {
+        "datetime": "2022-07-19T13:42:43.375061+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-07-19T13:42:43.375061+00:00",
+        "latest_version": "1.3",
+        "name": "mdx-truly-sane-lists",
+        "version": "1.3"
+    },
     "mergedeep": {
         "datetime": "2021-02-05T18:55:29.583731+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -936,12 +1000,28 @@
         "version": "1.3.4"
     },
     "mkdocs": {
-        "datetime": "2022-03-26T19:53:18.459672+00:00",
+        "datetime": "2022-09-27T17:30:09.460337+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-07-19T07:24:33.424540+00:00",
-        "latest_version": "1.3.1",
+        "latest_datetime": "2022-09-27T17:30:09.460337+00:00",
+        "latest_version": "1.4.0",
         "name": "mkdocs",
-        "version": "1.3.0"
+        "version": "1.4.0"
+    },
+    "mkdocs-autorefs": {
+        "datetime": "2022-03-07T16:43:47.394684+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-03-07T16:43:47.394684+00:00",
+        "latest_version": "0.4.1",
+        "name": "mkdocs-autorefs",
+        "version": "0.4.1"
+    },
+    "mkdocs-gen-files": {
+        "datetime": "2022-08-22T17:39:48.902428+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-08-22T17:39:48.902428+00:00",
+        "latest_version": "0.4.0",
+        "name": "mkdocs-gen-files",
+        "version": "0.4.0"
     },
     "mkdocs-git-revision-date-localized-plugin": {
         "datetime": "2022-07-02T11:14:43.680107+00:00",
@@ -951,13 +1031,29 @@
         "name": "mkdocs-git-revision-date-localized-plugin",
         "version": "1.1.0"
     },
+    "mkdocs-include-markdown-plugin": {
+        "datetime": "2022-09-16T17:49:53.862035+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-16T17:49:53.862035+00:00",
+        "latest_version": "3.8.1",
+        "name": "mkdocs-include-markdown-plugin",
+        "version": "3.8.1"
+    },
+    "mkdocs-literate-nav": {
+        "datetime": "2022-09-18T16:20:15.636451+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-18T16:20:15.636451+00:00",
+        "latest_version": "0.5.0",
+        "name": "mkdocs-literate-nav",
+        "version": "0.5.0"
+    },
     "mkdocs-material": {
-        "datetime": "2022-09-15T17:10:56.242220+00:00",
+        "datetime": "2022-09-20T11:47:25.288746+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-15T17:10:56.242220+00:00",
-        "latest_version": "8.5.1",
+        "latest_datetime": "2022-09-20T11:47:25.288746+00:00",
+        "latest_version": "8.5.3",
         "name": "mkdocs-material",
-        "version": "8.5.1"
+        "version": "8.5.3"
     },
     "mkdocs-material-extensions": {
         "datetime": "2021-09-03T20:49:40.072718+00:00",
@@ -967,6 +1063,30 @@
         "name": "mkdocs-material-extensions",
         "version": "1.0.3"
     },
+    "mkdocs-section-index": {
+        "datetime": "2022-03-30T21:24:07.870475+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-03-30T21:24:07.870475+00:00",
+        "latest_version": "0.3.4",
+        "name": "mkdocs-section-index",
+        "version": "0.3.4"
+    },
+    "mkdocstrings": {
+        "datetime": "2022-05-28T15:55:53.205418+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-05-28T15:55:53.205418+00:00",
+        "latest_version": "0.19.0",
+        "name": "mkdocstrings",
+        "version": "0.19.0"
+    },
+    "mkdocstrings-python": {
+        "datetime": "2022-06-12T11:44:34.529543+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-06-12T11:44:34.529543+00:00",
+        "latest_version": "0.7.1",
+        "name": "mkdocstrings-python",
+        "version": "0.7.1"
+    },
     "mr-proper": {
         "datetime": "2021-10-27T13:06:01.852589+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -984,12 +1104,12 @@
         "version": "6.0.2"
     },
     "mypy": {
-        "datetime": "2022-07-19T15:03:09.188215+00:00",
+        "datetime": "2022-09-26T18:34:31.552477+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-07-19T15:03:09.188215+00:00",
-        "latest_version": "0.971",
+        "latest_datetime": "2022-09-26T18:34:31.552477+00:00",
+        "latest_version": "0.981",
         "name": "mypy",
-        "version": "0.971"
+        "version": "0.981"
     },
     "mypy-extensions": {
         "datetime": "2019-10-17T22:38:56.922784+00:00",
@@ -1031,6 +1151,14 @@
         "name": "numpy",
         "version": "1.23.3"
     },
+    "ordered-set": {
+        "datetime": "2022-01-26T14:38:48.677482+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-01-26T14:38:48.677482+00:00",
+        "latest_version": "4.1.0",
+        "name": "ordered-set",
+        "version": "4.1.0"
+    },
     "packaging": {
         "datetime": "2021-11-18T00:39:10.932370+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1040,12 +1168,12 @@
         "version": "21.3"
     },
     "pandas": {
-        "datetime": "2022-08-31T12:18:07.039749+00:00",
+        "datetime": "2022-09-19T15:53:13.348635+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-08-31T12:18:07.039749+00:00",
-        "latest_version": "1.4.4",
+        "latest_datetime": "2022-09-19T15:53:13.348635+00:00",
+        "latest_version": "1.5.0",
         "name": "pandas",
-        "version": "1.4.4"
+        "version": "1.5.0"
     },
     "pandas-vet": {
         "datetime": "2022-02-09T02:48:57.434022+00:00",
@@ -1079,21 +1207,13 @@
         "name": "pdbpp",
         "version": "0.10.3"
     },
-    "pdocs": {
-        "datetime": "2021-01-29T08:14:55.588161+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2021-01-29T08:14:55.588161+00:00",
-        "latest_version": "1.1.1",
-        "name": "pdocs",
-        "version": "1.1.1"
-    },
-    "pendulum": {
-        "datetime": "2020-07-24T18:16:40.817569+00:00",
+    "peewee": {
+        "datetime": "2022-09-22T02:56:09.456009+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2020-07-24T18:16:40.817569+00:00",
-        "latest_version": "2.1.2",
-        "name": "pendulum",
-        "version": "2.1.2"
+        "latest_datetime": "2022-09-22T02:56:09.456009+00:00",
+        "latest_version": "3.15.3",
+        "name": "peewee",
+        "version": "3.15.3"
     },
     "pep8-naming": {
         "datetime": "2022-08-19T19:24:22.900666+00:00",
@@ -1119,6 +1239,14 @@
         "name": "pip-check",
         "version": "2.7"
     },
+    "pkgutil_resolve_name": {
+        "datetime": "2021-07-21T08:19:03.106881+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-07-21T08:19:03.106881+00:00",
+        "latest_version": "1.3.10",
+        "name": "pkgutil_resolve_name",
+        "version": "1.3.10"
+    },
     "platformdirs": {
         "datetime": "2022-04-18T08:48:47.670933+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1159,14 +1287,6 @@
         "name": "proselint",
         "version": "0.13.0"
     },
-    "punq": {
-        "datetime": "2022-02-17T09:16:12.094458+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-02-17T09:16:12.094458+00:00",
-        "latest_version": "0.6.2",
-        "name": "punq",
-        "version": "0.6.2"
-    },
     "py": {
         "datetime": "2021-11-04T17:17:00.152107+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1216,12 +1336,12 @@
         "version": "2.5.0"
     },
     "pylint": {
-        "datetime": "2022-09-07T13:02:02.802242+00:00",
+        "datetime": "2022-09-19T14:29:30.781612+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-07T13:02:02.802242+00:00",
-        "latest_version": "2.15.2",
+        "latest_datetime": "2022-09-19T14:29:30.781612+00:00",
+        "latest_version": "2.15.3",
         "name": "pylint",
-        "version": "2.15.2"
+        "version": "2.15.3"
     },
     "pymdown-extensions": {
         "datetime": "2022-06-06T14:22:57.204037+00:00",
@@ -1240,12 +1360,12 @@
         "version": "3.0.9"
     },
     "pyrate-limiter": {
-        "datetime": "2022-04-12T06:05:20.346448+00:00",
+        "datetime": "2022-09-24T15:54:20.903193+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-04-12T06:05:20.346448+00:00",
-        "latest_version": "2.8.1",
+        "latest_datetime": "2022-09-24T15:54:20.903193+00:00",
+        "latest_version": "2.8.2",
         "name": "pyrate-limiter",
-        "version": "2.8.1"
+        "version": "2.8.2"
     },
     "pyreadline": {
         "datetime": "2015-09-16T08:27:16.911586+00:00",
@@ -1263,6 +1383,14 @@
         "name": "pyrepl",
         "version": "0.9.0"
     },
+    "pyrsistent": {
+        "datetime": "2022-01-14T19:55:29.951247+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-01-14T19:55:29.951247+00:00",
+        "latest_version": "0.18.1",
+        "name": "pyrsistent",
+        "version": "0.18.1"
+    },
     "pytest": {
         "datetime": "2022-09-02T11:13:13.780124+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1280,12 +1408,20 @@
         "version": "3.4.1"
     },
     "pytest-cache-assert": {
-        "datetime": "2022-05-02T02:57:46.765001+00:00",
+        "datetime": "2022-09-26T00:44:28.227787+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-05-02T02:57:46.765001+00:00",
-        "latest_version": "2.0.0",
+        "latest_datetime": "2022-09-26T00:44:28.227787+00:00",
+        "latest_version": "3.0.0",
         "name": "pytest-cache-assert",
-        "version": "2.0.0"
+        "version": "3.0.0"
+    },
+    "pytest-cov": {
+        "datetime": "2021-10-04T01:48:57.552984+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-10-04T01:48:57.552984+00:00",
+        "latest_version": "3.0.0",
+        "name": "pytest-cov",
+        "version": "3.0.0"
     },
     "pytest-html": {
         "datetime": "2020-12-13T15:24:58.788568+00:00",
@@ -1351,6 +1487,14 @@
         "name": "python-dateutil",
         "version": "2.8.2"
     },
+    "python-lsp-jsonrpc": {
+        "datetime": "2021-04-14T21:19:18.698784+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-04-14T21:19:18.698784+00:00",
+        "latest_version": "1.0.0",
+        "name": "python-lsp-jsonrpc",
+        "version": "1.0.0"
+    },
     "pytz": {
         "datetime": "2022-08-13T02:07:57.135432+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1359,21 +1503,13 @@
         "name": "pytz",
         "version": "2022.2.1"
     },
-    "pytzdata": {
-        "datetime": "2020-07-13T10:42:10.122147+00:00",
-        "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2020-07-13T10:42:10.122147+00:00",
-        "latest_version": "2020.1",
-        "name": "pytzdata",
-        "version": "2020.1"
-    },
     "pyupgrade": {
-        "datetime": "2022-09-15T16:27:40.383646+00:00",
+        "datetime": "2022-09-26T02:07:37.806912+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-15T16:27:40.383646+00:00",
-        "latest_version": "2.38.0",
+        "latest_datetime": "2022-09-26T02:07:37.806912+00:00",
+        "latest_version": "2.38.2",
         "name": "pyupgrade",
-        "version": "2.38.0"
+        "version": "2.38.2"
     },
     "pyyaml_env_tag": {
         "datetime": "2020-11-12T02:38:24.638742+00:00",
@@ -1423,13 +1559,37 @@
         "name": "rich",
         "version": "12.5.1"
     },
+    "ruamel.yaml": {
+        "datetime": "2022-02-12T08:54:03.046398+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-02-12T08:54:03.046398+00:00",
+        "latest_version": "0.17.21",
+        "name": "ruamel.yaml",
+        "version": "0.17.21"
+    },
+    "ruamel.yaml.clib": {
+        "datetime": "2021-12-13T11:23:41.263328+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2021-12-13T11:23:41.263328+00:00",
+        "latest_version": "0.2.6",
+        "name": "ruamel.yaml.clib",
+        "version": "0.2.6"
+    },
+    "semgrep": {
+        "datetime": "2022-09-27T10:51:21.246443+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-27T10:51:21.246443+00:00",
+        "latest_version": "0.115.0",
+        "name": "semgrep",
+        "version": "0.115.0"
+    },
     "setuptools": {
-        "datetime": "2022-08-24T11:09:27.546037+00:00",
+        "datetime": "2022-09-24T20:40:59.176002+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-08-24T11:09:27.546037+00:00",
-        "latest_version": "65.3.0",
+        "latest_datetime": "2022-09-24T20:40:59.176002+00:00",
+        "latest_version": "65.4.0",
         "name": "setuptools",
-        "version": "65.3.0"
+        "version": "65.4.0"
     },
     "setuptools-scm": {
         "datetime": "2022-07-11T12:51:57.671747+00:00",
@@ -1472,12 +1632,12 @@
         "version": "2.4.0"
     },
     "sqlparse": {
-        "datetime": "2021-09-10T06:51:56.892895+00:00",
+        "datetime": "2022-09-23T18:41:20.351878+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2021-09-10T06:51:56.892895+00:00",
-        "latest_version": "0.4.2",
+        "latest_datetime": "2022-09-23T18:41:20.351878+00:00",
+        "latest_version": "0.4.3",
         "name": "sqlparse",
-        "version": "0.4.2"
+        "version": "0.4.3"
     },
     "stdlib-list": {
         "datetime": "2020-11-19T14:11:08.070691+00:00",
@@ -1512,12 +1672,12 @@
         "version": "0.8.10"
     },
     "termcolor": {
-        "datetime": "2011-01-13T18:45:33.979856+00:00",
+        "datetime": "2022-09-12T05:14:18.775639+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2011-01-13T18:45:33.979856+00:00",
-        "latest_version": "1.1.0",
+        "latest_datetime": "2022-09-12T05:14:18.775639+00:00",
+        "latest_version": "2.0.1",
         "name": "termcolor",
-        "version": "1.1.0"
+        "version": "2.0.1"
     },
     "terminaltables": {
         "datetime": "2021-12-07T19:03:34.013333+00:00",
@@ -1559,6 +1719,14 @@
         "name": "tomlkit",
         "version": "0.11.4"
     },
+    "tqdm": {
+        "datetime": "2022-09-03T11:10:27.148080+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-03T11:10:27.148080+00:00",
+        "latest_version": "4.64.1",
+        "name": "tqdm",
+        "version": "4.64.1"
+    },
     "transitions": {
         "datetime": "2022-09-02T09:48:52.135381+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1567,6 +1735,14 @@
         "name": "transitions",
         "version": "0.9.0"
     },
+    "tryceratops": {
+        "datetime": "2022-04-30T13:28:47.037854+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-04-30T13:28:47.037854+00:00",
+        "latest_version": "1.1.0",
+        "name": "tryceratops",
+        "version": "1.1.0"
+    },
     "types-setuptools": {
         "datetime": "2022-08-26T04:46:51.427377+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
@@ -1600,12 +1776,12 @@
         "version": "5.5.0"
     },
     "unimport": {
-        "datetime": "2022-09-05T06:26:17.479511+00:00",
+        "datetime": "2022-09-21T20:22:59.430079+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-09-05T06:26:17.479511+00:00",
-        "latest_version": "0.11.3",
+        "latest_datetime": "2022-09-21T20:22:59.430079+00:00",
+        "latest_version": "0.12.1",
         "name": "unimport",
-        "version": "0.11.3"
+        "version": "0.12.1"
     },
     "untokenize": {
         "datetime": "2014-02-08T16:30:40.631383+00:00",
@@ -1640,12 +1816,12 @@
         "version": "20.16.5"
     },
     "vulture": {
-        "datetime": "2022-07-03T20:36:20.968411+00:00",
+        "datetime": "2022-09-19T07:16:34.367865+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
-        "latest_datetime": "2022-07-03T20:36:20.968411+00:00",
-        "latest_version": "2.5",
+        "latest_datetime": "2022-09-19T07:16:34.367865+00:00",
+        "latest_version": "2.6",
         "name": "vulture",
-        "version": "2.5"
+        "version": "2.6"
     },
     "watchdog": {
         "datetime": "2022-06-10T10:28:22.716530+00:00",
@@ -1655,6 +1831,14 @@
         "name": "watchdog",
         "version": "2.1.9"
     },
+    "wcmatch": {
+        "datetime": "2022-09-19T23:14:55.460389+00:00",
+        "domain": "https://pypi.org/pypi/{name}/json",
+        "latest_datetime": "2022-09-19T23:14:55.460389+00:00",
+        "latest_version": "8.4.1",
+        "name": "wcmatch",
+        "version": "8.4.1"
+    },
     "wcwidth": {
         "datetime": "2020-06-23T16:10:28.529454+00:00",
         "domain": "https://pypi.org/pypi/{name}/json",
diff --git a/.copier-answers.yml b/.copier-answers.yml
index def1ef53..b6a25ffc 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,7 +1,7 @@
 # yamllint disable-file
 # Answer file maintained by Copier for: https://github.com/KyleKing/calcipy_template
 # Check into version control. Edit by re-running copier and changing responses to the questions
-_commit: 0.10.0
+_commit: 0.13.2
 _src_path: gh:KyleKing/calcipy_template
 author_email: dev.act.kyle@gmail.com
 author_name: Kyle King
@@ -10,6 +10,7 @@ cname: calcipy.kyleking.me
 copyright_date: '2021'
 development_branch: dev/development
 doc_dir: docs
+include_adrs: false
 install_extras: -E dev -E lint -E test -E commitizen_legacy
 minimum_python: 3.8.4
 package_name_py: calcipy
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 69be597e..75e3953e 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -6,31 +6,24 @@ labels: 'Type: Bug'
 assignees: kyleking
 ---
 
-## Describe the bug
+**Describe the bug**
+<!-- Describe the bug -->
 
-<!-- TODO: Describe the bug -->
-
-### To Reproduce
-
-<!-- TODO: How can someone else replicate the issue -->
+#**To Reproduce**
+<!-- How can someone else replicate the issue -->
 
 Steps and/or code snippet(s) to reproduce the behavior:
 
 1. ...
 1. ...
 
-## Expected behavior
-
-<!-- TODO: What did you expect? -->
+**Expected behavior**
+<!-- What did you expect? -->
 
-## Additional Information
-
-<!-- TODO: Add any relevant versions -->
+**Additional Information**
+<!-- Add any relevant versions -->
 
 - `calcipy` Version:
 - OS: \[e.g. macOS/Windows\]:
 - OS Version \[e.g. Catalina/10\]:
-
-<!-- TODO: Add `pip freeze` or other version information that is relevant -->
-
-<!-- 'calcipy:skip_tags' -->
+<!-- Add `pip freeze` or other version information that is relevant -->
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 32968ad3..dc655c4b 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,25 +1,19 @@
 ---
 name: Feature request
-about: Let us know if  an idea for this project
+about: Please share your ideas for calcipy!
 title: ''
 labels: 'Type: Feature'
 assignees: kyleking
 ---
 
-## Is your feature request related to a problem? Please describe.
+**Is your feature request related to a problem? Please describe.**
+<!-- A description of what the problem is. Ex. I'm always frustrated when [...] -->
 
-<!-- TODO: A description of what the problem is. Ex. I'm always frustrated when [...] -->
+**Describe the solution you'd like**
+<!-- A description of what you want to happen -->
 
-## Describe the solution you'd like
+**Describe alternatives you've considered**
+<!-- A description of any alternative solutions or features you've considered -->
 
-<!-- TODO: A description of what you want to happen -->
-
-## Describe alternatives you've considered
-
-<!-- TODO: A description of any alternative solutions or features you've considered -->
-
-## Additional context
-
-<!-- TODO: Add any other context or screenshots about the feature request here -->
-
-<!-- 'calcipy:skip_tags' -->
+**Additional context**
+<!-- Add any other context or screenshots about the feature request here -->
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 59a585ba..cbc599a2 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -1,21 +1,7 @@
 ---
 name: Question
-about: Ask a question here (or use the "Github Discussion" instead)
+about: This is a general catch-all template
 title: ''
 labels: ''
 assignees: kyleking
 ---
-
-## Question
-
-<!-- TODO: What is your question -->
-
-*How can I ...?*
-
-*Is it possible to ...?*
-
-## Additional context
-
-<!-- Any additional information that might be helpful, such as Operating System, Context, Screenshots, etc. -->
-
-<!-- 'calcipy:skip_tags' -->
diff --git a/.github/labels.yml b/.github/labels.yml
index b813c22c..9e5ce5e8 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -4,7 +4,7 @@
 # Other Where Used: https://github.com/search?q=name+path%3A%2F.github+filename%3Alabels.yml&type=Code&ref=advsearch&l=&l=
 #
 # Style Guide: ../docs/STYLE_GUIDE.md
-#
+
 - name: Needs Discussion
   color: ff5722
   description: Ticket needs discussion and prioritization
diff --git a/.github/workflows/ci_pipeline.yml b/.github/workflows/ci_pipeline.yml
index 443d2142..eb26cb64 100644
--- a/.github/workflows/ci_pipeline.yml
+++ b/.github/workflows/ci_pipeline.yml
@@ -45,3 +45,19 @@ jobs:
       - name: Test with pytest
         run: |-
           poetry run doit run test_all
+
+  check_types:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest]
+        python-version: [3.8.4]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: ./.github/actions/setup
+        with:
+          os: ${{ matrix.os }}
+          python-version: ${{ matrix.python-version }}
+
+      - name: Run linters
+        run: poetry run doit run check_types
diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml
index 1ea64515..d82f7719 100644
--- a/.github/workflows/labels.yml
+++ b/.github/workflows/labels.yml
@@ -8,6 +8,9 @@ on:  # yamllint disable-line rule:truthy
   push:
     branches:
       - main
+    paths:
+      - .github/labels.yml
+      - .github/workflows/labels.yml
 
 jobs:
   labeler:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ddae11d0..84951e6d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,6 +12,7 @@ repos:
     rev: v4.3.0
     hooks:
       - id: check-added-large-files
+      - id: check-executables-have-shebangs
       - id: check-json
       - id: check-merge-conflict
       - id: check-symlinks
@@ -32,14 +33,14 @@ repos:
         args: [--fix=auto]
       - id: pretty-format-json
         args: [--autofix, --indent=4]
-        exclude: (.+/assert-cache/.+\.json)
+        exclude: (tests/.*assert-cache.*/.+\.json)
       - id: trailing-whitespace
   - repo: https://github.com/lovesegfault/beautysh
     rev: v6.2.1
     hooks:
       - id: beautysh
   - repo: https://github.com/commitizen-tools/commitizen
-    rev: v2.33.1
+    rev: v2.35.0
     hooks:
       - id: commitizen
         additional_dependencies: [cz_legacy]
@@ -71,7 +72,7 @@ repos:
       - id: shellcheck
         args: [--severity=warning]
   - repo: https://github.com/Yelp/detect-secrets
-    rev: v1.3.0
+    rev: v1.4.0
     hooks:
       # Can't generate baseline with Py 3.8. Use exclude regex rules instead
       # See: https://github.com/Yelp/detect-secrets/issues/452
diff --git a/.sourcery.yaml b/.sourcery.yaml
index 61895512..a9e77b13 100644
--- a/.sourcery.yaml
+++ b/.sourcery.yaml
@@ -1,7 +1,7 @@
 ---
 # See: https://docs.sourcery.ai/Configuration/
 refactor:
-  python_version: 3.8.4
+  python_version: '3.8'
 
 # Full Google Style Set from: https://sourcery.ai/blog/google-python-style-guide/
 rules:
@@ -333,70 +333,6 @@ rules:
             __initial_value: int = 0
       - no-match: __version__ = "3.14"
 
-  - id: snake-case-arguments
-    pattern: |
-      def ...(...,${arg_name}: ${type?} = ${default_value?},...):
-        ...
-    condition: not arg_name.is_snake_case() or arg_name.is_dunder_name()
-    description: Use snake case for arguments
-    explanation: |
-      Use snake case for function and method arguments.
-      From Google Style Guide [3.16.2](https://google.github.io/styleguide/pyguide.html#s3.16.2-naming-conventions) and [PEP 8](https://peps.python.org/pep-0008/#function-and-method-arguments)
-    tests:
-      - match: |
-          def placeholder(miXed):
-            pass
-      - match: |
-          def placeholder(randomWord):
-            pass
-      - match: |
-          def placeholder(randomWord: str):
-            pass
-      - match: |
-          def placeholder(randomWord: str = "random"):
-            pass
-      - match: |
-          def placeholder(randomWord, other):
-            pass
-      - match: |
-          class Something:
-            def placeholder(self,myCamelWord):
-              pass
-      - match: |
-          def placeholder(too__many__underscores):
-            pass
-      - match: |
-          def placeholder(double_underscore_suffix__):
-            pass
-      - match: |
-          def placeholder(mixed_and_underScore):
-            pass
-      - match: |
-          def placeholder(__dunder_arg__):
-            pass
-      - no-match: |
-          def placeholder(nice_arg_name):
-            pass
-      - no-match: |
-          def placeholder(nice_arg_name: str):
-            pass
-      - no-match: |
-          def placeholder(nice_arg_name: str = "random"):
-            pass
-      - no-match: |
-          def placeholder(simple):
-            pass
-      - no-match: |
-          def placeholder(simple: bool = False):
-            pass
-      - no-match: |
-          def placeholder(nr, other_nr, sth_completely_different):
-            pass
-      - no-match: |
-          class Something:
-            def placeholder(self,simple: bool = False):
-              pass
-
   - id: snake-case-functions
     pattern: |
       def ${function_name}(...):
@@ -735,45 +671,6 @@ rules:
 
 # Typing Rules
 
-  - id: require-parameter-annotation
-    pattern: |
-      def ${name}(..., ${arg}: !!!=${default?}, ...):
-          ...
-    condition: not name.starts_with("_") and not arg.equals("self") and not arg.equals("cls")
-    paths:
-      exclude:
-        - test/
-        - tests/
-    description: Annotate parameter `${arg}` in public function/method `${name}` with
-      a type annotation
-    explanation: |
-      Adding type annotations has several benefits:
-      1. It improves the documentation of the function
-      2. It allows the function to be checked for correctness
-      3. It allows checking that the function callers are passing the correct params
-      These [mypy docs](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#functions) describe how to
-      annotate function arguments and return types.
-      From Google Style Guide [2.21](https://google.github.io/styleguide/pyguide#221-type-annotated-code)
-    tests:
-      - match: |
-          def add(a, b: int):
-            return a + b
-      - match: |
-          def f(a=1):
-            return a * 2
-      - no-match: |
-          def f() -> int:
-              pass
-      - no-match: |
-          def f(a: int, b: str):
-              pass
-      - no-match: |
-          def f(self, a: int, b: str):
-              pass
-      - no-match: |
-          def f(cls, a: int, b: str):
-              pass
-
   - id: require-return-annotation
     pattern: |
       def ${name}(...) -> !!!:
@@ -802,123 +699,6 @@ rules:
       - match: |
           def f(a: int, b: str):
               x()
-      - no-match: |
+      - no-match: |-
           def f(something) -> int:
               x()
-
-# Docstring Rules
-
-  - id: docstrings-for-classes
-    pattern: |
-      class ${c}(...):
-        """!!!"""
-        ...
-    description: Public classes should have docstrings
-    explanation: |
-      All public classes should have a docstring describing the class.
-      The class docstring should also document the class' public attributes.
-      From Google Style Guide [3.8.4](https://google.github.io/styleguide/pyguide.html#384-classes)
-    condition: not c.starts_with("_")
-    paths:
-      exclude:
-        - test/
-        - tests/
-    tests:
-      - match: |
-          class CheeseShopAddress:
-            ...
-      - match: |
-          class CheeseShopAddress(Address):
-            ...
-      - match: |
-          class OutOfCheeseError(Exception):
-            def __str__(self):
-              ...
-      - no-match: |
-          class CheeseShopAddress:
-            """The address of a cheese shop."""
-      - no-match: |
-          class OutOfCheeseError(Exception):
-            """No more cheese is available."""
-            def __str__(self):
-              ...
-      - no-match: |
-          class _BrieCounter:
-            limit: 500
-
-  - id: docstrings-for-functions
-    pattern: |
-      def ${f}(...):
-        """!!!"""
-        ${body*}
-    description: Functions should have docstrings
-    explanation: |
-      A function must have a docstring, unless it meets all of the following criteria:
-      - not externally visible
-      - very short
-      - obvious
-      From Google Style Guide [3.8.3](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods)
-    condition: not ((f.starts_with("_") and body.statement_count() < 5) or pattern.in_function_scope())
-    paths:
-      exclude:
-        - test/
-        - tests/
-    tests:
-      - match: |
-          def grow(plant):
-            assert plant.is_alive()
-            _grow(plant)
-      - match: |
-          def _grow(plant):
-            pot = plant.owner.get_empty_pot()
-            pot.contents.add("soil", fraction=0.5)
-            pot.contents.add(plant, orientation="vertical")
-            for day in range(0, plant.__class__.gestation_time, plant.__class__.watering_interval):
-              pot.contents.add("water", amount="200ml")
-      - no-match: |
-          class PotContents:
-            def add(item, fraction=None, amount=None, **kwargs):
-              """Adds `item` to the pot.
-              The pot can be filled to either a `fraction` of its volume, or by an `amount` specified.
-              """
-              ...
-      - no-match: |
-          def _log_growth(plant):
-            logger.info("plant size: %s", plant.size)
-      - no-match: |
-          def log_growth_error(f):
-            """Captures and logs any growth errors inside `f`"""
-            def wrapped(*args, **kwargs):
-              try:
-                return f(*args, **kwargs)
-              except GrowthError:
-                logger.warning("growth error")
-            return wrapped
-
-  - id: docstrings-for-modules
-    pattern: |
-      '''!!!'''
-      ...
-    condition: pattern.in_module_scope()
-    paths:
-      exclude:
-        - test/
-        - tests/
-    description: Modules should have docstrings
-    explanation: |
-      Modules (Python files) should start with docstrings describing the contents and usage of the module.
-      From Google Style Guide [3.8.2](https://google.github.io/styleguide/pyguide.html#382-modules)
-    tests:
-      - match: |
-          def hello():
-            print("hello")
-      - match: |
-          class Hello:
-            """Hello"""
-            def hello():
-              """Prints 'hello'"""
-              print("hello")
-      - no-match: |-
-          """Hello module"""
-          def hello():
-            print("hello")
diff --git a/calcipy/__init__.py b/calcipy/__init__.py
index a88a4d8e..351d2ddb 100644
--- a/calcipy/__init__.py
+++ b/calcipy/__init__.py
@@ -2,7 +2,7 @@
 
 from loguru import logger
 
-__version__ = '0.17.0'
+__version__ = '0.19.1'
 __pkg_name__ = 'calcipy'
 
 logger.disable(__pkg_name__)
diff --git a/calcipy/cli/controllers/code_tag_collector_controller.py b/calcipy/cli/controllers/code_tag_collector_controller.py
index dbbfba84..2d6e4986 100644
--- a/calcipy/cli/controllers/code_tag_collector_controller.py
+++ b/calcipy/cli/controllers/code_tag_collector_controller.py
@@ -2,6 +2,7 @@
 
 import re
 from pathlib import Path
+from typing import Dict, List, Tuple
 
 from cement import Controller, ex
 from loguru import logger
@@ -10,20 +11,20 @@
 from ...file_search import find_project_files
 
 
-class CodeTagCollectorController(Controller):
+class CodeTagCollectorController(Controller):  # type: ignore[misc]
     """Base CLI Controller."""
 
     class Meta:
         label = 'CodeTagCollector'
 
-        arguments = []
+        arguments: List[Tuple[List[str], Dict[str, str]]] = []
         """Controller level arguments. ex: 'calcipy --version'."""
 
     def _default(self) -> None:
         """Default action if no sub-command is passed."""
         self.app.args.print_help()
 
-    @ex(
+    @ex(  # type: ignore[misc]
         help='Code Tag Collector subcommand',
         arguments=[
             (
@@ -67,7 +68,7 @@ def collect_code_tags(self) -> None:
         """Main subcommand to collect code tags."""
         pargs = self.app.pargs
 
-        base_dir = Path(pargs.base_dir).resolve().absolute()
+        base_dir = Path(pargs.base_dir).resolve()
         path_tag_summary = base_dir / pargs.filename
         ignore_patterns = pargs.ignore_patterns.split(',') if pargs.ignore_patterns else []
         paths_source = find_project_files(base_dir, ignore_patterns=ignore_patterns)
diff --git a/calcipy/cli/core/version.py b/calcipy/cli/core/version.py
index 5639f02b..2846e02c 100644
--- a/calcipy/cli/core/version.py
+++ b/calcipy/cli/core/version.py
@@ -12,4 +12,4 @@
 @beartype
 def get_version(version: Iterable[str] = _VERSION) -> str:
     """Format the version for printing."""
-    return cement_get_version(version)
+    return str(cement_get_version(version))
diff --git a/calcipy/cli/main.py b/calcipy/cli/main.py
index 1127c22c..de55b83c 100644
--- a/calcipy/cli/main.py
+++ b/calcipy/cli/main.py
@@ -14,7 +14,7 @@
 _CONFIG[__pkg_name__]['foo'] = 'bar-default'
 
 
-class CLIApp(App):
+class CLIApp(App):  # type: ignore[misc]
     """My CLI Application."""
 
     class Meta:
@@ -33,7 +33,7 @@ class Meta:
         """Register handlers."""
 
 
-class CLIAppTest(TestApp, CLIApp):
+class CLIAppTest(TestApp, CLIApp):  # type: ignore[misc]
     """A sub-class of CLIApp that is better suited for testing."""
 
     class Meta:
diff --git a/calcipy/code_tag_collector.py b/calcipy/code_tag_collector.py
index fdf59bf9..42d64de1 100644
--- a/calcipy/code_tag_collector.py
+++ b/calcipy/code_tag_collector.py
@@ -75,8 +75,7 @@ def _search_lines(
 
     comments = []
     for lineno, line in enumerate(lines):
-        match = regex_compiled.search(line)
-        if match:
+        if match := regex_compiled.search(line):
             if len(line) <= 400:  # FYI: Suppress long lines
                 group = match.groupdict()
                 comments.append(_CodeTag(lineno=lineno + 1, tag=group['tag'], text=group['text']))
@@ -105,8 +104,7 @@ def _search_files(paths_source: Sequence[Path], regex_compiled: Pattern[str]) ->
         except UnicodeDecodeError as err:
             logger.debug('Could not parse: {path_source}', path_source=path_source, err=err)
 
-        comments = _search_lines(lines, regex_compiled)
-        if comments:
+        if comments := _search_lines(lines, regex_compiled):
             matches.append(_Tags(path_source=path_source, code_tags=comments))
 
     return matches
@@ -134,7 +132,7 @@ def _git_info(cwd: Path) -> Tuple[Path, str]:
     # https://github.com/KyleKing/calcipy.git
     sub_url = re.findall(r'^.+github.com[:/]([^.]+)(?:\.git)?$', clone_uri)[0]
     repo_url = f'https://github.com/{sub_url}'
-    return (git_dir, repo_url)
+    return git_dir, repo_url
 
 
 @beartype
@@ -197,7 +195,7 @@ def _format_record(base_dir: Path, file_path: Path, comment: _CodeTag) -> Dict[s
 @beartype
 def _format_report(
     base_dir: Path, code_tags: List[_Tags], tag_order: List[str],
-) -> str:  # noqa: CCR001
+) -> str:    # noqa: CCR001
     """Pretty-format the code tags by file and line number.
 
     Args:
@@ -224,19 +222,20 @@ def _format_report(
 
     sorted_counter = {tag: counter[tag] for tag in tag_order if tag in counter}
     logger.debug('sorted_counter={sorted_counter}', sorted_counter=sorted_counter)
-    formatted_summary = ', '.join(f'{tag} ({count})' for tag, count in sorted_counter.items())
-    if formatted_summary:
+    if formatted_summary := ', '.join(
+        f'{tag} ({count})' for tag, count in sorted_counter.items()
+    ):
         output += f'\n\nFound code tags for {formatted_summary}\n'
     return output
 
 
-@log_fun
+@log_fun(do_not_log=['header'])
 @beartype
 def write_code_tag_file(
     path_tag_summary: Path, paths_source: List[Path], base_dir: Path,
     regex_compiled: Optional[Pattern[str]] = None, tag_order: Optional[List[str]] = None,
     header: str = '# Task Summary',
-) -> None:  # noqa: CCR001
+) -> None:    # noqa: CCR001
     """Create the code tag summary file.
 
     Args:
@@ -253,9 +252,9 @@ def write_code_tag_file(
     regex_compiled = regex_compiled or re.compile(CODE_TAG_RE.format(tag='|'.join(tag_order)))
 
     matches = _search_files(paths_source, regex_compiled)
-    report = _format_report(base_dir, matches, tag_order=tag_order).strip()
-
-    if report:
+    if report := _format_report(
+        base_dir, matches, tag_order=tag_order,
+    ).strip():
         path_tag_summary.write_text(f'{header}\n\n{report}\n\n<!-- {SKIP_PHRASE} -->\n')
     elif path_tag_summary.is_file():
         path_tag_summary.unlink()
diff --git a/calcipy/dev/noxfile.py b/calcipy/dev/noxfile.py
index e5ac85fb..58e8d346 100644
--- a/calcipy/dev/noxfile.py
+++ b/calcipy/dev/noxfile.py
@@ -6,12 +6,12 @@
 poetry run nox -l
 poetry run nox --list-sessions
 
-poetry run nox -s build_check-3.9 build_dist-3.9 check_security-3.9
-poetry run nox --session check_security-3.9
+poetry run nox -s build_check-3.9 build_dist-3.9 coverage-3.9
+poetry run nox --session coverage-3.9
 
 poetry run nox --python 3.8
 
-poetry run nox -k "not tests and not check_security"
+poetry run nox -k "not tests and not coverage"
 ```
 
 Useful nox snippets
@@ -40,10 +40,10 @@
 from urllib.request import url2pathname
 
 from beartype import beartype
-from beartype.typing import Callable, Dict, List
+from beartype.typing import Callable, Dict, Iterable, List
 from loguru import logger
 
-from ..doit_tasks.doit_globals import DoitAction, DoitTask, get_dg
+from ..doit_tasks.doit_globals import DoitTask, get_dg
 from ..doit_tasks.test import task_coverage, task_test
 from ..file_helpers import if_found_unlink
 
@@ -113,7 +113,7 @@ def _install_pinned(session: Session, key: str) -> None:
         for pin in _PINS.get(key, []):
             session.install(pin)
 
-    def _run_func_cmd(action: DoitAction) -> None:
+    def _run_func_cmd(action: Iterable) -> None:  # type: ignore[type-arg]
         """Run a python action.
 
         Args:
@@ -145,7 +145,7 @@ def _run_doit_task(session: Session, task_fun: Callable[[], DoitTask]) -> None:
             if isinstance(action, str):
                 _run_str_cmd(session, action)
             elif getattr(action, 'action', None):
-                _run_str_cmd(session, action.action)
+                _run_str_cmd(session, action.action)  # type: ignore[union-attr]
             elif isinstance(action, (list, tuple)):
                 _run_func_cmd(action)
             else:
@@ -208,44 +208,6 @@ def build_check(session: Session) -> None:
         session.install('poetry>=1.2.0b3')  # required for "poetry.core.masonry.api" build backend
         session.run('pyroma', '--file', path_sdist.as_posix(), '--min=9', stdout=True)
 
-    @nox_session(python=get_dg().test.pythons[-1:], reuse_venv=True)
-    def check_security(session: Session) -> None:
-        """More general checks for common security issues.
-
-        Args:
-            session: nox_poetry Session
-
-        """
-        session.install('semgrep>=0.112.1', '--upgrade')  # Runs "safety" and other tools
-        # TODO: Implement semgrep - what are a good ruleset to start with? Currently only a nox-session (no doit task)
-        #   https://github.com/returntocorp/semgrep-rules/tree/develop/python
-        #   https://awesomeopensource.com/project/returntocorp/semgrep-rules?categorypage=45
-        configs = ' '.join([
-            # See more at: https://semgrep.dev/explore
-            '--config=p/ci',
-            '--config=p/security-audit',
-            '--config=r/python.airflow',
-            '--config=r/python.attr',
-            '--config=r/python.click',
-            '--config=r/python.cryptography',
-            '--config=r/python.distributed',
-            '--config=r/python.docker',
-            '--config=r/python.flask',
-            '--config=r/python.jinja2',
-            '--config=r/python.jwt',
-            '--config=r/python.lang',
-            '--config=r/python.pycryptodome',
-            '--config=r/python.requests',
-            '--config=r/python.security',
-            '--config=r/python.sh',
-            '--config=r/python.sqlalchemy',
-            # dlukeomalley:unchecked-subprocess-call
-            # dlukeomalley:use-assertEqual-for-equality
-            # dlukeomalley:flask-set-cookie
-            # clintgibler:no-exec
-        ])
-        session.run(*shlex.split(f'semgrep {get_dg().meta.pkg_name} {configs}'), stdout=True)
-
     # TODO: https://github.com/tonybaloney/wily/blob/e72b7d95228bbe5538a072dc5d1186daa318bb03/src/wily/__main__.py#L261
     @nox_session(python=get_dg().test.pythons[-1:], reuse_venv=True)
     def check_wily(session: Session) -> None:
diff --git a/calcipy/doit_tasks/__init__.py b/calcipy/doit_tasks/__init__.py
old mode 100755
new mode 100644
index cd3e6e36..994ddf4a
--- a/calcipy/doit_tasks/__init__.py
+++ b/calcipy/doit_tasks/__init__.py
@@ -2,14 +2,13 @@
 
 Register all defaults doit tasks in a dodo.py file with the below snippet:
 
-`from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks). # skipcq: PYL-W0614`
+`from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks)`
 
 """
 
 __all__ = [  # noqa: F405
     'DOIT_CONFIG_RECOMMENDED',
-    'TASKS_CI',
-    'TASKS_LOCAL',
+    'TASKS_FULL',
     # from .base
     'task_zip_release',
     # from .code_tags
@@ -54,23 +53,15 @@
     'task_test',
 ]
 
-from getpass import getuser
-
 from .base import task_zip_release  # noqa: F401
 from .code_tags import task_collect_code_tags  # noqa: F401
-from .doc import *  # noqa: F401,F403,H303. lgtm [py/polluting-import]
-from .lint import *  # noqa: F401,F403,H303. lgtm [py/polluting-import]
-from .packaging import *  # noqa: F401,F403,H303. lgtm [py/polluting-import]
+from .doc import *  # noqa: F401,F403,H303
+from .lint import *  # noqa: F401,F403,H303
+from .packaging import *  # noqa: F401,F403,H303
 from .summary_reporter import SummaryReporter
-from .test import *  # noqa: F401,F403,H303. lgtm [py/polluting-import]
-
-TASKS_CI = [
-    'nox_test',
-    'security_checks',
-]
-"""More forgiving tasks to be run in CI."""
+from .test import *  # noqa: F401,F403,H303
 
-TASKS_LOCAL = [
+TASKS_FULL = [
     'collect_code_tags',
     'cl_write',
     'lock',
@@ -89,7 +80,7 @@
 DOIT_CONFIG_RECOMMENDED = {
     'action_string_formatting': 'old',  # Required for keyword-based tasks
     'backend': 'sqlite3',  # Best support for concurrency
-    'default_tasks': TASKS_CI if getuser().lower() == 'appveyor' else TASKS_LOCAL,
+    'default_tasks': TASKS_FULL,
     'dep_file': '.doit-db.sqlite',
     'reporter': SummaryReporter,
 }
diff --git a/calcipy/doit_tasks/base.py b/calcipy/doit_tasks/base.py
old mode 100755
new mode 100644
index 0e10ccaa..ac8510e8
--- a/calcipy/doit_tasks/base.py
+++ b/calcipy/doit_tasks/base.py
@@ -101,7 +101,7 @@ def _make_archive(path_dir: Path, archive_format: str = 'zip') -> None:
         archive_format: default is `zip`. See `shutil.make_archive` for supported types
 
     """
-    shutil.make_archive(path_dir, archive_format, path_dir)
+    shutil.make_archive(path_dir.as_posix(), archive_format, path_dir)
 
 
 @beartype
@@ -123,7 +123,7 @@ def _zip_release() -> List[DoitAction]:
                 (_make_archive, (path_dir,)),
                 (echo, (f'Created: {path_zip}',)),
             ])
-    return actions
+    return actions  # type: ignore[return-value]
 
 
 @beartype
diff --git a/calcipy/doit_tasks/code_tags.py b/calcipy/doit_tasks/code_tags.py
index cd0ab70d..0d000bb4 100644
--- a/calcipy/doit_tasks/code_tags.py
+++ b/calcipy/doit_tasks/code_tags.py
@@ -24,7 +24,7 @@ def task_collect_code_tags() -> DoitTask:
         'paths_source': user_files,
         'base_dir': dg.meta.path_project,
         'regex_compiled': dg.tags.compile_issue_regex(),
-        'header': f'# Task Summary\n\nAuto-Generated by `calcipy`',
+        'header': '# Task Summary\n\nAuto-Generated by `calcipy`',
         'tag_order': dg.tags.tags,
     }
     return debug_task([(write_code_tag_file, (), kwargs)])
diff --git a/calcipy/doit_tasks/doc.py b/calcipy/doit_tasks/doc.py
index cf11e25b..1933c4d1 100644
--- a/calcipy/doit_tasks/doc.py
+++ b/calcipy/doit_tasks/doc.py
@@ -7,7 +7,7 @@
 
 import pandas as pd
 from beartype import beartype
-from beartype.typing import Any, Callable, Dict, List, Optional, Pattern
+from beartype.typing import Any, Callable, Dict, List, Pattern
 from doit.tools import Interactive
 from loguru import logger
 from transitions import Machine
@@ -122,7 +122,7 @@ class _ParseSkipError(RuntimeError):
     ...
 
 
-class _ReplacementMachine(Machine):  # type: ignore[misc] # noqa: H601
+class _ReplacementMachine(Machine):  # noqa: H601
     """State machine to replace content with user-specified handlers.
 
     Uses `{cts}` and `{cte}` to demarcate sections (short for calcipy_template start|end)
@@ -152,8 +152,7 @@ def __init__(self) -> None:
 
     @beartype
     def _parse_line(
-        self, line: str, handler_lookup: Dict[str, Callable[[str, Path], List[str]]],
-        path_file: Optional[Path] = None,
+        self, line: str, handler_lookup: Dict[str, Callable[[str, Path], List[str]]], path_file: Path,
     ) -> List[str]:
         """Parse lines and insert new_text based on provided handler_lookup.
 
@@ -190,7 +189,7 @@ def _parse_line(
     @beartype
     def parse(
         self, lines: List[str], handler_lookup: Dict[str, Callable[[str, Path], List[str]]],
-        path_file: Optional[Path] = None,
+        path_file: Path,
     ) -> List[str]:
         """Parse lines and insert new_text based on provided handler_lookup.
 
@@ -214,7 +213,7 @@ def parse(
 
 
 @beartype
-def _parse_var_comment(line: str, matcher: Pattern = _RE_VAR_COMMENT_HTML) -> Dict[str, str]:
+def _parse_var_comment(line: str, matcher: Pattern = _RE_VAR_COMMENT_HTML) -> Dict[str, str]:  # type: ignore[type-arg]
     """Parse the variable from a matching comment.
 
     Args:
@@ -225,8 +224,7 @@ def _parse_var_comment(line: str, matcher: Pattern = _RE_VAR_COMMENT_HTML) -> Di
         Dict[str, str]: single key and value pair based on the parsed comment
 
     """
-    match = matcher.match(line.strip())
-    if match:
+    if match := matcher.match(line.strip()):
         matches = match.groupdict()
         return {matches['key']: matches['value']}
     return {}
@@ -287,7 +285,7 @@ def _format_cov_table(coverage_data: Dict[str, Any]) -> List[str]:
     # Format table for Github Markdown
     df_cov = pd.DataFrame(records)
     df_cov['Coverage'] = df_cov['Coverage'].round(1).astype(str) + '%'
-    lines_table = df_cov.to_markdown(index=False, tablefmt='github').split('\n')
+    lines_table = str(df_cov.to_markdown(index=False, tablefmt='github')).split('\n')
     short_date = coverage_data['meta']['timestamp'].split('T')[0]
     lines_table.extend(['', f'Generated on: {short_date}'])
     return lines_table
@@ -343,7 +341,7 @@ def write_autoformatted_md_sections() -> None:
 def _diagram_tasks(pdoc_out_path: Path) -> List[DoitAction]:
     """Return actions to generate code diagrams in the module documentation directory.
 
-    Note: must be run after `document` because pdocs will delete these files
+    Note: must be run after `document` because pdoc will delete these files
 
     PUML support may be coming in a future release: https://github.com/PyCQA/pylint/issues/4498
 
@@ -410,15 +408,12 @@ def task_document() -> DoitTask:
 
     """
     _ensure_handler_lookup()
-    pdoc_out_path = get_dg().doc.auto_doc_path
-    pdoc_out = f'--output_dir {pdoc_out_path} --overwrite'
-    pdoc_template = f'--template_dir {get_dg().calcipy_dir}/doit_tasks/templates'
+    auto_doc_path = get_dg().doc.auto_doc_path
     return debug_task([
         (write_autoformatted_md_sections, ()),
-        (delete_dir, (pdoc_out_path,)),
-        Interactive(f'poetry run pdocs as_markdown {get_dg().meta.pkg_name} {pdoc_out} {pdoc_template}'),
-        *_diagram_tasks(pdoc_out_path),
-        (_find_and_trim_trailing_whitespace, (pdoc_out_path,)),
+        (delete_dir, (auto_doc_path,)),
+        *_diagram_tasks(auto_doc_path),
+        (_find_and_trim_trailing_whitespace, (auto_doc_path,)),
         Interactive(f'poetry run mkdocs build --site-dir {get_dg().doc.path_out}'),
     ])
 
diff --git a/calcipy/doit_tasks/doit_globals.py b/calcipy/doit_tasks/doit_globals.py
index fae859d3..7af19414 100644
--- a/calcipy/doit_tasks/doit_globals.py
+++ b/calcipy/doit_tasks/doit_globals.py
@@ -1,3 +1,5 @@
+# mypy: disable-error-code="misc"
+# ^ ignores assignments to ClassVar
 """Global Variables for doit."""
 
 import inspect
@@ -67,7 +69,7 @@ def _member_filter(member: Any, instance_type: Any) -> bool:
     return instance_type is None or isinstance(member, instance_type)
 
 
-@dataclass(kw_only=True)
+@dataclass
 class _PathAttrBase:
 
     path_project: Path
@@ -90,7 +92,7 @@ def _get_members(self, prefix: Optional[str], **kwargs: Any) -> List[Tuple[str,
 
         Args:
             prefix: optional string prefix to check starts with
-            kwargs: keyword arguments passed to `_member_filter`
+            **kwargs: keyword arguments passed to `_member_filter`
 
         Returns:
             List[Tuple[str, Any]]: filtered members from the class
@@ -114,11 +116,11 @@ def _resolve_class_paths(self, base_path: Path) -> None:
         """
         for name, path_raw in self._get_members(instance_type=type(Path()), prefix=None):
             if not path_raw.is_absolute():
-                setattr(self, name, base_path / path_raw)  # type: ignore[operator]
+                setattr(self, name, base_path / path_raw)
                 logger.debug(f'Mutated: self.{name}={path_raw} (now: {getattr(self, name)})')
 
 
-@dataclass(kw_only=True)
+@dataclass
 class PackageMeta(_PathAttrBase):
     """Package Meta-Information."""
 
@@ -191,6 +193,7 @@ def _shorten_path_lists(self) -> Set[Path]:
         }
 
 
+# FIXME: Merge with .flake8 configuration...
 _DEF_IGNORE_LIST = [
     'AAA01',  # AAA01 / act block in pytest
     'C901',  # C901 / complexity from "max-complexity = 10"
@@ -209,7 +212,7 @@ def _shorten_path_lists(self) -> Set[Path]:
 """Default list of excluded flake8 rules for the pre-commit check (additional to .flake8)."""
 
 
-@dataclass(kw_only=True)
+@dataclass
 class LintConfig(_PathAttrBase):
     """Lint Config."""
 
@@ -232,7 +235,7 @@ def __post_init__(self) -> None:
         self.path_isort = _make_full_path(self.path_isort, path_base=self.path_project)
 
 
-@dataclass(kw_only=True)
+@dataclass
 class TestingConfig(_PathAttrBase):  # pylint: disable=too-many-instance-attributes
     """Test Config."""
 
@@ -251,13 +254,20 @@ class TestingConfig(_PathAttrBase):  # pylint: disable=too-many-instance-attribu
     path_tests: Path = Field(default=Path('tests'))
     """Relative path to the tests directory. Default is `tests`."""
 
-    args_pytest: str = Field(
-        default='--exitfirst --showlocals --failed-first --new-first --verbose --doctest-modules',
+    min_cov: int = Field(default=80)
+    """Configurable minimum percent coverage."""
+
+    args_pytest: str = Field(default='--exitfirst --showlocals --failed-first --new-first --verbose')
+    """Default arguments to Pytest"""
+
+    args_diff: str = Field(
+        default='--fail-under=50 --include-untracked --ignore-whitespace --show-uncovered --compare-branch=origin/main',
     )
-    """Default arguments to Pytest. In short form, the defaults are `-x -l --ff --nf -vv`."""
+    """Default arguments to diff-cover.
 
-    args_diff: str = Field(default='--fail-under=80 --compare-branch=origin/main')
-    """Default arguments to diff-cover."""
+    Note: this may need to overridden for projects that don't use a main branch or need a lower threshold
+
+    """
 
     path_test_report: ClassVar[Path]
     """Path to the self-contained test HTML report."""
@@ -288,7 +298,7 @@ def __post_init__(self) -> None:
         self.path_mypy_index = self.path_out / 'mypy_html/index.html'
 
 
-@dataclass(kw_only=True)
+@dataclass
 class CodeTagConfig(_PathAttrBase):
     """Code Tag Config."""
 
@@ -311,7 +321,7 @@ def __post_init__(self) -> None:
         """Finish initializing class attributes."""
         super().__post_init__()
         # Configure full path to the code tag summary file
-        self.path_code_tag_summary = self.path_project / self.doc_sub_dir / self.code_tag_summary_filename
+        self.path_code_tag_summary = (self.path_project / self.doc_sub_dir / self.code_tag_summary_filename)
 
     @beartype
     def compile_issue_regex(self) -> Pattern[str]:
@@ -324,7 +334,7 @@ def compile_issue_regex(self) -> Pattern[str]:
         return re.compile(self.re_raw.format(tag='|'.join(self.tags)))
 
 
-@dataclass(kw_only=True)
+@dataclass
 class DocConfig(_PathAttrBase):
     """Documentation Config."""
 
@@ -337,9 +347,7 @@ class DocConfig(_PathAttrBase):
     auto_doc_path: ClassVar[Path]
     """Auto-calculated based on `self.doc_sub_dir`."""
 
-    handler_lookup: Optional[
-        Dict[str, Callable[[str, Path], List[str]]]
-    ] = Field(default=None)
+    handler_lookup: Optional[Dict[str, Callable[[str, Path], List[str]]]] = Field(default=None)
     """Lookup dictionary for autoformatted sections of the project's markdown files."""
 
     path_out: ClassVar[Path]
@@ -406,8 +414,9 @@ def _set_submodules(
     # Configure global options
     section_keys = ['lint', 'test', 'code_tag', 'doc']
     supported_keys = section_keys + ['ignore_patterns']
-    unexpected_keys = [key for key in calcipy_config if key not in supported_keys]
-    if unexpected_keys:
+    if unexpected_keys := [
+        key for key in calcipy_config if key not in supported_keys
+    ]:
         raise RuntimeError(f'Found unexpected key(s) {unexpected_keys} (i.e. not in {supported_keys})')
 
     # Parse the Copier file for configuration information
@@ -420,10 +429,10 @@ def _set_submodules(
     paths_py = meta.paths_by_suffix.get('py', [])
     paths_md = meta.paths_by_suffix.get('md', [])
     return {
-        'lint': LintConfig(**meta_kwargs, paths_py=paths_py, **lint_k),
-        'test': TestingConfig(**meta_kwargs, **test_k),
-        'tags': CodeTagConfig(**meta_kwargs, doc_sub_dir=doc_sub_dir, **code_k),
-        'doc': DocConfig(**meta_kwargs, paths_md=paths_md, doc_sub_dir=doc_sub_dir, **doc_k),
+        'lint': LintConfig(**meta_kwargs, paths_py=paths_py, **lint_k),  # type: ignore[arg-type]
+        'test': TestingConfig(**meta_kwargs, **test_k),  # type: ignore[arg-type]
+        'tags': CodeTagConfig(**meta_kwargs, doc_sub_dir=doc_sub_dir, **code_k),  # type: ignore[arg-type]
+        'doc': DocConfig(**meta_kwargs, paths_md=paths_md, doc_sub_dir=doc_sub_dir, **doc_k),  # type: ignore[arg-type]
     }
 
 
@@ -444,7 +453,7 @@ def create_dg(*, path_project: Path) -> DoitGlobals:
 
     meta = _set_meta(path_project, calcipy_config)
     kwargs = _set_submodules(meta, calcipy_config)
-    return DoitGlobals(meta=meta, **kwargs)
+    return DoitGlobals(meta=meta, **kwargs)  # type: ignore[arg-type]
 
 
 class _DGContainer(BaseModel):
@@ -488,7 +497,7 @@ def get_dg(self) -> DoitGlobals:
 
 
 @beartype
-def init_dg():
+def init_dg() -> None:
     """Initialize the global DG instance."""
     work_dir = doit.get_initial_workdir()
     path_project = (Path(work_dir) if work_dir else Path.cwd()).resolve()
diff --git a/calcipy/doit_tasks/lint.py b/calcipy/doit_tasks/lint.py
old mode 100755
new mode 100644
index ef164513..db8b7c73
--- a/calcipy/doit_tasks/lint.py
+++ b/calcipy/doit_tasks/lint.py
@@ -101,8 +101,7 @@ def _lint_non_python() -> List[DoitAction]:
     """
     actions = []
     pbs = get_dg().meta.paths_by_suffix
-    paths_yaml = pbs.get('yml', []) + pbs.get('yaml', [])
-    if paths_yaml:
+    if paths_yaml := pbs.get('yml', []) + pbs.get('yaml', []):
         paths = ' '.join(f'"{pth}"' for pth in paths_yaml)
         actions.append(Interactive(f'poetry run yamllint {paths}'))
     return actions
@@ -116,7 +115,8 @@ def task_lint_python() -> DoitTask:
         DoitTask: doit task
 
     """
-    actions = _lint_python(get_dg().lint.paths_py, path_flake8=get_dg().lint.path_flake8)
+    dg = get_dg()
+    actions = _lint_python(dg.lint.paths_py, path_flake8=dg.lint.path_flake8)
     return debug_task(actions)
 
 
@@ -186,7 +186,8 @@ def task_static_checks() -> DoitTask:
     """
     paths = ' '.join(map(Path.as_posix, get_dg().lint.paths_py))
     return debug_task([
-        Interactive(f'poetry run vulture {paths} --min-confidence 70 --sort-by-size'),
+        # Set to 61% because pydantic.Config and attributes are flagged with 60% confidence
+        Interactive(f'poetry run vulture {paths} --min-confidence 61 --sort-by-size'),
     ])
 
 
@@ -198,9 +199,36 @@ def task_security_checks() -> DoitTask:
         DoitTask: doit task
 
     """
+    # TODO: Implement semgrep - what are a good ruleset to start with?
+    #   https://github.com/returntocorp/semgrep-rules/tree/develop/python
+    #   https://awesomeopensource.com/project/returntocorp/semgrep-rules?categorypage=45
+    configs = ' '.join([
+        # See more at: https://semgrep.dev/explore
+        '--config=p/ci',
+        '--config=p/security-audit',
+        '--config=r/python.airflow',
+        '--config=r/python.attr',
+        '--config=r/python.click',
+        '--config=r/python.cryptography',
+        '--config=r/python.distributed',
+        '--config=r/python.docker',
+        '--config=r/python.flask',
+        '--config=r/python.jinja2',
+        '--config=r/python.jwt',
+        '--config=r/python.lang',
+        '--config=r/python.pycryptodome',
+        '--config=r/python.requests',
+        '--config=r/python.security',
+        '--config=r/python.sh',
+        '--config=r/python.sqlalchemy',
+        # dlukeomalley:unchecked-subprocess-call
+        # dlukeomalley:use-assertEqual-for-equality
+        # dlukeomalley:flask-set-cookie
+        # clintgibler:no-exec
+    ])
     return debug_task([
         Interactive(f'poetry run bandit --recursive {get_dg().meta.pkg_name}'),
-        Interactive('poetry run nox --session check_security'),
+        Interactive(f'semgrep {get_dg().meta.pkg_name} {configs}'),
     ])
 
 
diff --git a/calcipy/doit_tasks/packaging.py b/calcipy/doit_tasks/packaging.py
old mode 100755
new mode 100644
index 32ea07a0..27e5b440
--- a/calcipy/doit_tasks/packaging.py
+++ b/calcipy/doit_tasks/packaging.py
@@ -13,7 +13,7 @@
 from bidict import bidict
 from doit.tools import Interactive
 from loguru import logger
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field, validator
 from pyrate_limiter import Duration, Limiter, RequestRate
 
 from .base import debug_task
@@ -48,8 +48,8 @@ def task_lock() -> DoitTask:
     """
     dg = get_dg()
     task = debug_task(['poetry lock --no-update'])
-    task['file_dep'].append(dg.meta.path_toml)
-    task['targets'].extend([dg.meta.path_project / 'poetry.lock'])
+    task['file_dep'].append(dg.meta.path_toml)  # type: ignore[union-attr]
+    task['targets'].extend([dg.meta.path_project / 'poetry.lock'])  # type: ignore[union-attr]
     return task
 
 
@@ -99,22 +99,6 @@ def task_publish_test_pypi() -> DoitTask:
 # Check for stale packages
 
 
-class ArrowType(Arrow):
-    """Create a type for pydantic use of Arrow."""
-
-    @classmethod
-    def __get_validators__(cls):
-        yield cls.validate
-
-    @classmethod
-    def validate(cls, value):
-        @beartype
-        def arrow_converter(date: Optional[Union[str, Arrow]]) -> Optional[Arrow]:
-            return arrow.get(date) if date else date
-
-        return arrow_converter(value)
-
-
 class _HostedPythonPackage(BaseModel):
     """Representative information for a python package hosted on some domain."""
 
@@ -122,22 +106,17 @@ class _HostedPythonPackage(BaseModel):
     domain: str = Field(default='https://pypi.org/pypi/{name}/json')
     name: str
     version: str
-    datetime: Optional[ArrowType] = Field(default=None)
+    datetime: Optional[Arrow] = Field(default=None)
     latest_version: str = Field(default='')
-    latest_datetime: Optional[ArrowType] = Field(default=None)
-
-    # HACK: See pending pydantic changes: https://github.com/pydantic/pydantic/discussions/4456
-    def _iter(self, *args, **kwargs):
-        """Hack to serialize Arrow types.
+    latest_datetime: Optional[Arrow] = Field(default=None)
 
-        Based on: https://github.com/pydantic/pydantic/issues/2277#issuecomment-764132528
+    class Config:
+        arbitrary_types_allowed = True
+        json_encoders = {Arrow: str}
 
-        """
-        for key, v in super()._iter(*args, **kwargs):
-            if 'datetime' in key:
-                yield key, str(v)
-            else:
-                yield key, v
+    @validator('datetime', 'latest_datetime', pre=True)
+    def date_validator(cls, value: Union[str, Arrow]) -> Arrow:  # noqa: F841
+        return arrow.get(value)
 
 
 _PATH_PACK_LOCK = get_dg().meta.path_project / '.calcipy_packaging.lock'
@@ -152,7 +131,7 @@ def _iter(self, *args, **kwargs):
 
 
 @beartype
-@_LIMITER.ratelimit(_ITEM, delay=True, max_delay=10)
+@_LIMITER.ratelimit(_ITEM, delay=True, max_delay=10)  # type: ignore[misc]
 def _get_release_date(package: _HostedPythonPackage) -> _HostedPythonPackage:
     """Retrieve release date metadata for the specified package.
 
@@ -179,8 +158,6 @@ def _get_release_date(package: _HostedPythonPackage) -> _HostedPythonPackage:
         if release_data
     })
     package.datetime = release_dates.inverse[package.version]
-    if package.datetime is None:  # FYI: Explore alternatives to ensure datetime is set
-        logger.error(f'No datetime for {package} from: {res_json}')
     package.latest_datetime = max([*release_dates])
     package.latest_version = release_dates[package.latest_datetime]
     return package
@@ -201,7 +178,7 @@ def _read_cache(path_pack_lock: Path = _PATH_PACK_LOCK) -> Dict[str, _HostedPyth
         path_pack_lock.write_text('{}')  # noqa: P103
     old_cache: Dict[str, Dict[str, str]] = json.loads(path_pack_lock.read_text())
     return {
-        package_name: _HostedPythonPackage(**meta_data)
+        package_name: _HostedPythonPackage(**meta_data)  # type: ignore[arg-type]
         for package_name, meta_data in old_cache.items()
     }
 
@@ -231,9 +208,9 @@ def _collect_release_dates(
             cached_version = '' if cached_package is None else cached_package.version
             if package.version != cached_version:
                 package = _get_release_date(package)
-            else:
-                package = cached_package
-            updated_packages.append(package)
+                updated_packages.append(package)
+            elif cached_package:
+                updated_packages.append(cached_package)
         except requests.exceptions.HTTPError as err:
             logger.warning(f'Could not resolve {package} with error {err}')
     return updated_packages
@@ -290,15 +267,16 @@ def _check_for_stale_packages(packages: List[_HostedPythonPackage], *, stale_mon
 
     """
     def format_package(pack: _HostedPythonPackage) -> str:
-        delta = pack.datetime.humanize()
+        delta = pack.datetime.humanize()  # type: ignore[union-attr]
         latest = '' if pack.version == pack.latest_version else f' (*New version available: {pack.latest_version}*)'
         return f'- {delta}: {pack.name} {pack.version}{latest}'
 
     now = arrow.utcnow()
     stale_cutoff = now.shift(months=-1 * stale_months)
-    stale_packages = [pack for pack in packages if pack.datetime < stale_cutoff]
+    stale_packages = [pack for pack in packages if not pack.datetime or pack.datetime < stale_cutoff]
     if stale_packages:
-        stale_list = '\n'.join(map(format_package, sorted(stale_packages, key=lambda x: x.datetime)))
+        pkgs = sorted(stale_packages, key=lambda x: x.datetime or stale_cutoff)
+        stale_list = '\n'.join(map(format_package, pkgs))
         logger.warning(f'Found stale packages that may be a dependency risk:\n\n{stale_list}\n\n')
     else:
         oldest_date = np.amin([pack.datetime for pack in packages])
@@ -342,6 +320,6 @@ def task_check_for_stale_packages() -> DoitTask:
         (find_stale_packages, (path_lock, path_pack_lock), {'stale_months': _DEF_STALE_M}),
         Interactive('poetry run pip-check --cmd="poetry run pip" --hide-unchanged'),
     ])
-    task['file_dep'].append(path_lock)
-    task['targets'].append(path_pack_lock)
+    task['file_dep'].append(path_lock)  # type: ignore[union-attr]
+    task['targets'].append(path_pack_lock)  # type: ignore[union-attr]
     return task
diff --git a/calcipy/doit_tasks/summary_reporter.py b/calcipy/doit_tasks/summary_reporter.py
index d2aee663..74704c97 100644
--- a/calcipy/doit_tasks/summary_reporter.py
+++ b/calcipy/doit_tasks/summary_reporter.py
@@ -8,7 +8,7 @@
 from enum import IntEnum
 
 from beartype import beartype
-from beartype.typing import Any, List, OrderedDict
+from beartype.typing import Any, Dict, List, OrderedDict
 from doit.reporter import ConsoleReporter
 from doit.task import Task
 from pydantic import BaseModel
@@ -46,14 +46,14 @@ def _format_task_summary(task_summary: _TaskSummary) -> str:
     return f'{foreground}{task_summary.name} {exit_summary}{fg.rs}'
 
 
-class SummaryReporter(ConsoleReporter):  # pragma: no cover # noqa: H601
+class SummaryReporter(ConsoleReporter):  # type: ignore[misc] # pragma: no cover # noqa: H601
     """Summarize results of doit tasks."""
 
-    def __init__(self, *args, **kwargs) -> None:
+    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
         """Initialize data members."""  # noqa: DAR101
         super().__init__(*args, **kwargs)
-        self._all_tasks = []
-        self._task_summaries = {}
+        self._all_tasks: List[str] = []
+        self._task_summaries: Dict[str, _TaskSummary] = {}
 
     def initialize(self, tasks: OrderedDict[str, Task], selected_tasks: List[str]) -> None:
         """Initialize the data members for tracking task run status."""  # noqa: DAR101
diff --git a/calcipy/doit_tasks/templates/LICENSE-pdocs b/calcipy/doit_tasks/templates/LICENSE-pdocs
deleted file mode 100644
index 58d29389..00000000
--- a/calcipy/doit_tasks/templates/LICENSE-pdocs
+++ /dev/null
@@ -1,55 +0,0 @@
-Code written on this repository post August 30th 2019 is done so under the MIT License:
-
-MIT License
-
-Copyright (c) 2019 Timothy Crosley
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-All code written before this date, which is contained in the following commit: https://github.com/timothycrosley/pdocs/commit/7cf925101e4ffc5690f2952ac9ad0b7b0410b4f8
-
-Followed the compatible "Unlicense" License:
-
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-
-For more information, please refer to <http://unlicense.org/>
-
-In the most important areas, the licenses have the same intent and general guidelines. And, they both are compatible with each-other.
-The change was undertaking simply because the MIT license is more universally known.
diff --git a/calcipy/doit_tasks/templates/README.md b/calcipy/doit_tasks/templates/README.md
deleted file mode 100644
index 84a0c5b4..00000000
--- a/calcipy/doit_tasks/templates/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Custom Templates
-
-The license included in this directory is for the [pdocs template](https://github.com/timothycrosley/pdocs/tree/c122cdf873aad4fa131bc7ed968da3dd393df43b/pdocs/templates).
diff --git a/calcipy/doit_tasks/templates/text.mako b/calcipy/doit_tasks/templates/text.mako
deleted file mode 100644
index bb7461f4..00000000
--- a/calcipy/doit_tasks/templates/text.mako
+++ /dev/null
@@ -1,253 +0,0 @@
-## Based on: https://github.com/timothycrosley/pdocs/blob/c122cdf873aad4fa131bc7ed968da3dd393df43b/pdocs/templates/text.mako
-
-## Define mini-templates for each portion of the doco.
-
-<%def name="h1(s)"># ${s}
-</%def>
-<%def name="h2(s)">## ${s}
-</%def>
-<%def name="h3(s)">### ${s}
-</%def>
-<%def name="h4(s)">#### ${s}
-</%def>
-<%def name="par(s)">
-% if s:
-${s}
-
-% endif
-</%def>
-<%def name="folded_code(source)">
-% if show_source_code and source:
-
-??? example "View Source"
-    ```python3
-    ${"    ".join(source)}
-    ```
-
-% endif
-</%def>
-
-## Define Larger templates
-
-<%def name="function(func, class_level=False)" buffered="True">
-    <%
-        returns = func.return_annotation()
-        if returns:
-            returns = ' -> ' + returns
-        parsed_ds = func.parsed_docstring
-    %>
-% if class_level:
-${h4(func.name)}
-% else:
-${h3(func.name)}
-% endif
-
-```python3
-%if func.params():
-def ${func.name}(
-    ${",\n    ".join(func.params())}
-)${returns}
-% else:
-def ${func.name}()${returns}
-% endif
-```
-
-% if parsed_ds:
-    <%
-        short_desc = parsed_ds.short_description
-        long_desc = parsed_ds.long_description
-        params = parsed_ds.params
-        ret = parsed_ds.returns
-        raises = parsed_ds.raises
-    %>
-${par(short_desc)}
-${par(long_desc)}
-
-    % if params:
-**Parameters:**
-
-| Name | Description |
-|---|---|
-        % for p in params:
-| ${p.arg_name} | ${p.description.replace('\n', '<br>')} |
-        % endfor
-    % endif
-
-    % if ret:
-
-**${"Yields:" if ret.is_generator else "Returns:"}**
-
-| Type | Description |
-|---|---|
-## PLANNED: handle multiline descriptions
-| ${ret.type_name} | ${ret.description} |
-    % endif
-    % if raises:
-
-**Raises:**
-
-| Type | Description |
-|---|---|
-        % for r in raises:
-## PLANNED: handle multiline descriptions
-| ${r.type_name} | ${r.description} |
-        % endfor
-    % endif
-% else:
-${func.docstring}
-% endif
-
-${folded_code(func.source)}
-</%def>
-
-<%def name="variable(var)" buffered="True">
-```python3
-${var.name}
-```
-<%
-    var_pd = var.parsed_docstring
-    if var_pd:
-        short_desc = var_pd.short_description
-        long_desc = var_pd.long_description
-%>
-% if var_pd:
-${par(short_desc)}
-${par(long_desc)}
-% else:
-${var.docstring}
-% endif
-
-</%def>
-
-<%def name="class_(cls)" buffered="True">
-${h3(cls.name)}
-
-```python3
-class ${cls.name}(
-    ${",\n    ".join(cls.params())}
-)
-```
-
-% if cls.parsed_docstring:
-    % if cls.parsed_docstring.params:
-${h4("Attributes")}
-
-| Name | Description |
-|---|---|
-        % for p in cls.parsed_docstring.params:
-| ${p.arg_name} | ${p.description.replace('\n', '<br>')} |
-        % endfor
-    % endif
-% else:
-${cls.docstring}
-% endif
-
-${folded_code(cls.source)}
-
-<%
-  class_vars = cls.class_variables()
-  static_methods = cls.functions()
-  inst_vars = cls.instance_variables()
-  methods = cls.methods()
-  mro = cls.mro()
-  subclasses = cls.subclasses()
-%>
-% if mro:
-${h4('Ancestors (in MRO)')}
-    % for c in mro:
-* ${c.refname}
-    % endfor
-% endif
-
-% if subclasses:
-${h4('Descendants')}
-    % for c in subclasses:
-* ${c.refname}
-    % endfor
-% endif
-
-% if class_vars:
-${h4('Class variables')}
-    % for v in class_vars:
-${variable(v)}
-
-    % endfor
-% endif
-
-% if static_methods:
-${h4('Static methods')}
-    % for f in static_methods:
-${function(f, True)}
-
-    % endfor
-% endif
-
-% if inst_vars:
-${h4('Instance variables')}
-% for v in inst_vars:
-${variable(v)}
-
-% endfor
-% endif
-% if methods:
-${h4('Methods')}
-% for m in methods:
-${function(m, True)}
-
-% endfor
-% endif
-
-</%def>
-
-## Start the output logic for an entire module.
-
-<%
-  variables = module.variables()
-  classes = module.classes()
-  functions = module.functions()
-  submodules = module.submodules
-  heading = 'Namespace' if module.is_namespace else ''
-  parsed_ds = module.parsed_docstring
-%>
-
-${h1(heading + " " + module.name)}
-% if parsed_ds:
-${par(parsed_ds.short_description)}
-${par(parsed_ds.long_description)}
-## PLANNED: add meta (example and notes)
-% else:
-${module.docstring}
-% endif
-
-${folded_code(module.source)}
-
-% if submodules:
-${h2("Sub-modules")}
-    % for m in submodules:
-* [${m.name}](${m.name.split(".")[-1]}/)
-    % endfor
-% endif
-
-% if variables:
-${h2("Variables")}
-    % for v in variables:
-${variable(v)}
-
-    % endfor
-% endif
-
-% if functions:
-${h2("Functions")}
-    % for f in functions:
-${function(f)}
-
-    % endfor
-% endif
-
-% if classes:
-${h2("Classes")}
-    % for c in classes:
-${class_(c)}
-
-    % endfor
-% endif
diff --git a/calcipy/dot_dict.py b/calcipy/dot_dict.py
index e9bce9fc..00f92210 100644
--- a/calcipy/dot_dict.py
+++ b/calcipy/dot_dict.py
@@ -17,7 +17,7 @@ def ddict(**kwargs: Dict[str, Any]) -> DDICT_TYPE:
     Other variations are no longer supported, such as `bunch` and `ddict` among others
 
     Args:
-        kwargs: keyword arguments formatted into dictionary
+        **kwargs: keyword arguments formatted into dictionary
 
     Returns:
         DDICT_TYPE: dotted dictionary
diff --git a/calcipy/file_helpers.py b/calcipy/file_helpers.py
index 9a451d68..f68f1bbc 100644
--- a/calcipy/file_helpers.py
+++ b/calcipy/file_helpers.py
@@ -4,6 +4,7 @@
 import shutil
 import string
 import time
+from contextlib import suppress
 from pathlib import Path
 
 import yaml
@@ -84,7 +85,7 @@ def get_doc_dir(path_project: Path) -> Path:
 
     """
     path_copier = path_project / _COPIER_ANSWERS_NAME
-    return path_project / _read_yaml_file(path_copier).get('doc_dir', 'docs')
+    return path_project / _read_yaml_file(path_copier).get('doc_dir', 'docs')  # type: ignore[no-any-return]
 
 
 @beartype
@@ -216,7 +217,6 @@ def get_relative(full_path: Path, other_path: Path) -> Optional[Path]:
         relative path
 
     """
-    try:
+    with suppress(ValueError):
         return full_path.relative_to(other_path)
-    except ValueError:
-        return
+    return None
diff --git a/calcipy/file_search.py b/calcipy/file_search.py
index e5dfddf1..27e2cb31 100644
--- a/calcipy/file_search.py
+++ b/calcipy/file_search.py
@@ -89,4 +89,4 @@ def find_project_files_by_suffix(path_project: Path, ignore_patterns: List[str])
     file_lookup = defaultdict(list)
     for path_file in find_project_files(path_project, ignore_patterns):
         file_lookup[path_file.suffix.lstrip('.')].append(path_file)
-    return file_lookup
+    return dict(file_lookup)
diff --git a/calcipy/log_helpers.py b/calcipy/log_helpers.py
index ff3385ca..fb47ddaf 100644
--- a/calcipy/log_helpers.py
+++ b/calcipy/log_helpers.py
@@ -1,6 +1,6 @@
 """Loguru Helpers."""
 
-# PLANNED: consider a STDOUT format like https://pypi.org/project/readable-log-formatter
+# FIXME: consider a STDOUT format like https://pypi.org/project/readable-log-formatter
 #   Colorful Debug Level / Parent/FileName / Line NUmber
 #       Indented and wrapped summary string
 #       Indented, optional variables (would otherwise not be shown) (i.e. **{name}**: {value})
@@ -8,6 +8,8 @@
 #       See: https://github.com/Delgan/loguru/issues/156
 #           And: https://loguru.readthedocs.io/en/stable/api/logger.html#message
 #       Or: https://github.com/Delgan/loguru/issues/133
+# TODO: Provide interface for logger.info, etc.)
+# TODO: Improve logging for function timing (maybe size/number of data in arguments?)
 
 from __future__ import annotations
 
@@ -76,7 +78,8 @@ def serializable_compact(record: Dict[str, Any]) -> str:
 
 
 # FYI: loguru.Logger is PEP563 Postponed and can't be use with beartype runtime
-def _log_action(
+@contextmanager
+def log_duration(
     message: str, level: str = 'INFO',
     _logger: loguru.Logger = logger,  # pylint: disable=no-member
     **kwargs: Any,
@@ -87,7 +90,7 @@ def _log_action(
         message: string message to describe the context
         level: log level. Default is `INFO`
         _logger: Optional logger instance
-        kwargs: function keyword arguments passed to the start log statement
+        **kwargs: function keyword arguments passed to the start log statement
 
     Yields:
         yields the logger instance
@@ -100,27 +103,33 @@ def _log_action(
     _logger.log(level, f'(end) {message}', start_time=start_time, runtime=runtime)
 
 
-# When using `contextmanager` as a decorator, Deepsource won't see the __enter__/__exit__ methods (PYL-E1129)
-#   Rather than skipping each use of log_action, use `contextmanager` as a function
-log_action = contextmanager(_log_action)
-
-
 @decorator
-def log_fun(fun: Callable[[Any], Any], *args: Iterable[Any], **kwargs: Any) -> Any:
+def log_fun(
+    fun: Callable[[Any], Any], do_not_log: Optional[List[str]] = None, *args: Iterable[Any], **kwargs: Any,
+) -> Any:
     """Decorate a function to log the function name and completed time.
 
     Args:
         fun: the decorated function
-        args: functional arguments
-        kwargs: function keyword arguments
+        *args: functional arguments
+        do_not_log: optional list of strings to exclude from kwargs when logging
+        **kwargs: function keyword arguments
 
     Returns:
         Any: result of the function
 
     """
     fun_name = fun.__name__
-    with log_action(f'Running {fun_name}{signature(fun)}', args=args, kwargs=kwargs):
-        return fun(*args, **kwargs)  # type: ignore[call-arg]
+    params = [*signature(fun).parameters]
+    if not_found := set(do_not_log or []).difference(set(params)):
+        raise ValueError(f'There are no arguments for {not_found} in {fun_name}')
+
+    extra = {**dict(zip(params, args or [])), **(kwargs or {})}
+    for key in do_not_log or []:
+        extra.pop(key)
+
+    with log_duration(f'Called "{fun_name}"', fun_name=fun_name, extra=extra):
+        return fun(*args, **kwargs)
 
 
 _LOG_SUB_DIR = '.logs'
@@ -167,9 +176,7 @@ def _format_jsonl_handler(log_dir: Path, production: bool = True) -> Dict[str, A
 
 @beartype
 def build_logger_config(
-    path_parent: Optional[Path] = None,
-    *, format_handler: Callable[[Path, bool], Dict[str, Any]] = _format_jsonl_handler,
-    production: bool = True,
+    path_parent: Optional[Path] = None, *, production: bool = True,
 ) -> Dict[str, Any]:
     """Build the loguru configuration. Use with `loguru.configure(**configuration)`.
 
@@ -177,7 +184,6 @@ def build_logger_config(
 
     Args:
         path_parent: Path to the directory where the '.logs/' folder should be created. Default is this package
-        format_handler: function that will generate the loguru handler dictionary. Default is `_format_jsonl_handler`
         production: if True, will tweak logging configuration for production code. Default is `True`
 
     Returns:
@@ -191,7 +197,8 @@ def build_logger_config(
     log_dir.mkdir(exist_ok=True, parents=True)
     logger.info(f'Started logging to {log_dir} (production={production})')
 
-    return format_handler(log_dir=log_dir, production=production)
+    # PLANNED: Consider making the format handler specified via Protocol
+    return _format_jsonl_handler(log_dir=log_dir, production=production)
 
 
 @beartype
diff --git a/calcipy/proc_helpers.py b/calcipy/proc_helpers.py
index dfde477c..760d2ddb 100644
--- a/calcipy/proc_helpers.py
+++ b/calcipy/proc_helpers.py
@@ -8,7 +8,7 @@
 
 
 @beartype
-def run_cmd(cmd: str, printer: Optional[Callable[[str], None]] = None, **kwargs) -> str:
+def run_cmd(cmd: str, printer: Optional[Callable[[str], None]] = None, **kwargs) -> str:  # type: ignore[no-untyped-def]
     """Run command with subprocess and return the output.
 
     Inspired by: https://stackoverflow.com/a/38745040/3219667
@@ -16,7 +16,7 @@ def run_cmd(cmd: str, printer: Optional[Callable[[str], None]] = None, **kwargs)
     Args:
         cmd: string command
         printer: optional callable to output the lines in real time
-        kwargs: any additional keyword arguments to pass to `subprocess.Popen` (typically `cwd`)
+        **kwargs: any additional keyword arguments to pass to `subprocess.Popen` (typically `cwd`)
 
     Returns:
         str: stripped output
@@ -29,19 +29,18 @@ def run_cmd(cmd: str, printer: Optional[Callable[[str], None]] = None, **kwargs)
         cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True,
         shell=True, **kwargs,  # noqa: S602
     ) as proc:
-        stdout: BufferedReader = proc.stdout  # type: ignore
+        stdout: BufferedReader = proc.stdout  # type: ignore[assignment]
         lines = []
         return_code = None
         while return_code is None:
-            line = stdout.readline()
-            if line:
+            if line := stdout.readline():
                 lines.append(line)
                 if printer:
-                    printer(line.rstrip())
+                    printer(line.rstrip())  # type: ignore[arg-type]
             else:
                 return_code = proc.poll()
 
-    output = ''.join(lines)
+    output = ''.join(lines)  # type: ignore[unreachable]
     if return_code != 0:
         raise subprocess.CalledProcessError(returncode=return_code, cmd=cmd, output=output)
     return output
diff --git a/docs/README.md b/docs/README.md
index 1b8ed4bc..4448b39e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,11 +9,32 @@
 - [pyscaffold](https://github.com/pyscaffold/pyscaffold) is a much more mature project that aims for the same goals, but with a slightly different approach and tech stack (tox vs. nox, cookiecutter vs. copier, etc.)
 - [tidypy](https://github.com/jayclassless/tidypy#features), [pylama](https://github.com/klen/pylama), and [codecheck](https://pypi.org/project/codecheck/) offer similar functionality of bundling and running static checkers, but makes far fewer assumptions
 - [pytoil](https://github.com/FollowTheProcess/pytoil) is a general CLI tool for developer automation
-- And many more such as [prospector](https://github.com/PyCQA/prospector), [wemake-python-styleguide](https://github.com/wemake-services/wemake-python-styleguide) / [cjolowicz/cookiecutter-hypermodern-python](https://github.com/cjolowicz/cookiecutter-hypermodern-python), etc.
+- And many more such as [pyta](https://github.com/pyta-uoft/pyta), [prospector](https://github.com/PyCQA/prospector), [wemake-python-styleguide](https://github.com/wemake-services/wemake-python-styleguide) / [cjolowicz/cookiecutter-hypermodern-python](https://github.com/cjolowicz/cookiecutter-hypermodern-python), etc.
 
-## Calcipy CLI
+## Installation
 
-`calcipy` can be run as a CLI application without adding the package as a dependency!
+Calcipy needs a few static files managed using copier and a template project: [kyleking/calcipy_template](https://github.com/KyleKing/calcipy_template/)
+
+You can quickly use the template to create a new project or add calcipy to an existing one:
+
+```sh
+# Install copier. Pipx is recommended
+pipx install copier
+
+# To create a new project
+copier copy gh:KyleKing/calcipy_template new_project
+cd new_project
+
+# Or update an existing one
+cd my_project
+copier copy gh:KyleKing/calcipy_template .
+```
+
+See [./Advanced_Configuration.md](./Advanced_Configuration.md) for documentation on the configurable aspects of `calcipy`
+
+### Calcipy CLI
+
+Additionally, `calcipy` can be run as a CLI application without adding the package as a dependency.
 
 Quick Start:
 
@@ -24,14 +45,11 @@ pipx install calcipy
 calcipy collect-code-tags -h
 calcipy collect-code-tags -b=~/Some/Project
 
-
 # See additional documentation from the CLI help
 calcipy -h
 ```
 
-To utilize all of the functionality from `calcipy`, see the following sections on adding `calcipy` as a dependency
-
-## Calcipy Pre-Commit (beta!)
+### Calcipy Pre-Commit
 
 `calcipy` can also be used as a `pre-commit` task by adding the below snippet to your `pre-commit` file:
 
@@ -43,8 +61,6 @@ repos:
       - id: calcipy-code-tags
 ```
 
-This is a beta-feature that will be expanded with additional functionality as the CLI features are extended
-
 ## Calcipy Module Features
 
 The core functionality of calcipy is the rich set of tasks run with `doit`
@@ -65,16 +81,13 @@ The core functionality of calcipy is the rich set of tasks run with `doit`
     - **security_checks**: Use linting tools to identify possible security vulnerabilities.
     - **check_types**: Run type annotation checks.
 
-- Additional tasks of not:
+- Additional tasks include:
 
   - **nox**/**test**/**coverage**: Tasks for running nox sessions, pytest in the local environment, and pytest coverage
   - **ptw\_\***: Variations of tasks to run pytest watch
   - **cl_bump** (**cl_bump_pre**):Bumps project version based on commits & settings in pyproject.toml.
   - **deploy_docs**: Deploy docs to the Github `gh-pages` branch.
   - **publish**: Build the distributable format(s) and publish.
-
-- Other additional tasks include:
-
   - **check_license**: Check licenses for compatibility.
   - **lint_critical_only**: Suppress non-critical linting errors. Great for gating PRs/commits.
   - **lint_python**: Lint all Python files and create summary of errors.
@@ -92,72 +105,38 @@ The core functionality of calcipy is the rich set of tasks run with `doit`
     - The benefit of this wrapper is that there is a stable interface and you don't need to rewrite code as packages are born and die (i.e. [Bunch](https://pypi.org/project/bunch/) > [Chunk](https://pypi.org/project/chunk/) > [Munch](https://pypi.org/project/munch/) > [flexible-dotdict](https://pypi.org/project/flexible-dotdict/) > [Python-Box](https://pypi.org/project/python-box/) > ...)
     - Note: if you need nested dotted dictionaries, check out [classy-json](https://pypi.org/project/classy-json/)
 
-**Tip**: For the full list of available tasks, run `poetry run doit list`
+> NOTE
+>
+> For the full list of available tasks, run `poetry run doit list`
 
-## Calcipy Installation
+## Project Status
 
-Calcipy needs a few static files managed using copier and a template project: [kyleking/calcipy_template](https://github.com/KyleKing/calcipy_template/)
-
-You can quickly use the template to create a new project or add calcipy to an existing one:
+See the `Open Issues` and/or the [CODE_TAG_SUMMARY]. For release history, see the [CHANGELOG].
 
-```sh
-# Install copier. Pipx is recommended
-pipx install copier
-
-# To create a new project
-copier copy gh:KyleKing/calcipy_template new_project
-cd new_project
-
-# Or update an existing one
-cd my_project
-copier copy gh:KyleKing/calcipy_template .
-```
-
-## Usage
-
-1. Run `poetry install`
-1. Run `poetry run doit list` to see available tasks
-1. And try `poetry run doit --continue` to see if the default tasks work
-
-If you have any questions, please [start a Discussion on Github](https://github.com/KyleKing/calcipy/discussions/) or [open an issue for feature requests or bug reports](https://github.com/KyleKing/calcipy/issues/)
-
-See [./Advanced_Configuration.md](./Advanced_Configuration.md) for documentation on the configurable aspects of `calcipy`
-
-Additionally, for more examples, see other projects that use `calcipy`:
-
-- [KyleKing/dash_charts](https://github.com/KyleKing/dash_charts) - *Active*
-- [KyleKing/recipes](https://github.com/KyleKing/recipes) - *Active*
-- [KyleKing/Goodreads_Library_Availability](https://github.com/KyleKing/Goodreads_Library_Availability) - *On Hold*
-- [KyleKing/cz_legacy](https://github.com/KyleKing/cz_legacy) - *Published*
-- See other [projects tagged with the topic "calcipy"](https://github.com/topics/calcipy)
-
-## Updating Calcipy
-
-Review the [./docs/CHANGELOG.md](./docs/CHANGELOG.md) before updating. Calcipy uses the year followed by standard semantic versioning to indicate major and minor changes. Note that this is a personal project and may change dramatically, but for the most part, the project should be relatively stable
-
-```sh
-# Update files
-copier update
-# and update dependencies
-poetry update
-```
+## Contributing
 
-## Roadmap
+We welcome pull requests! For your pull request to be accepted smoothly, we suggest that you first open a GitHub issue to discuss your idea. For resources on getting started with the code base, see the below documentation:
 
-See the `Open Issues` and `Milestones` for current status and [./docs/CODE_TAG_SUMMARY.md](./docs/CODE_TAG_SUMMARY.md) for annotations in the source code.
+- [DEVELOPER_GUIDE]
+- [STYLE_GUIDE]
+- [CONTRIBUTING]
 
-For release history, see the [./docs/CHANGELOG.md](./docs/CHANGELOG.md)
+## Code of Conduct
 
-## Contributing
+We follow the [Contributor Covenant Code of Conduct][contributor-covenant].
 
-See the Developer Guide, Contribution Guidelines, etc
+## Responsible Disclosure
 
-- [./docs/DEVELOPER_GUIDE.md](./docs/DEVELOPER_GUIDE.md)
-- [./docs/STYLE_GUIDE.md](./docs/STYLE_GUIDE.md)
-- [./docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md)
-- [./docs/CODE_OF_CONDUCT.md](./docs/CODE_OF_CONDUCT.md)
-- [./docs/SECURITY.md](./docs/SECURITY.md)
+If you have any security issue to report, please contact the project maintainers privately. You can reach us at [dev.act.kyle@gmail.com](mailto:dev.act.kyle@gmail.com).
 
 ## License
 
-[LICENSE](https://github.com/KyleKing/calcipy/tree/main/LICENSE)
+[LICENSE]
+
+[changelog]: ./docs/CHANGELOG.md
+[code_tag_summary]: ./docs/CODE_TAG_SUMMARY.md
+[contributing]: ./docs/CONTRIBUTING.md
+[contributor-covenant]: https://www.contributor-covenant.org
+[developer_guide]: ./docs/DEVELOPER_GUIDE.md
+[license]: https://github.com/kyleking/calcipy/LICENSE
+[style_guide]: ./docs/STYLE_GUIDE.md
diff --git a/docs/adr/NNN-Template.md b/docs/adr/NNN-Template.md
deleted file mode 100644
index d162e8b2..00000000
--- a/docs/adr/NNN-Template.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# \[short title of solved problem and solution\]
-
-- Status: \[proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)\] <!-- optional -->
-- Deciders: \[list everyone involved in the decision\] <!-- optional -->
-- Date: \[YYYY-MM-DD when the decision was last updated\] <!-- optional -->
-
-Technical Story: \[description | ticket/issue URL\] <!-- optional -->
-
-## Context and Problem Statement
-
-\[Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.\]
-
-## Decision Drivers <!-- optional -->
-
-- \[driver 1, e.g., a force, facing concern, …\]
-- \[driver 2, e.g., a force, facing concern, …\]
-- … <!-- numbers of drivers can vary -->
-
-## Considered Options
-
-- \[option 1\]
-- \[option 2\]
-- \[option 3\]
-- … <!-- numbers of options can vary -->
-
-## Decision Outcome
-
-Chosen option: "\[option 1\]", because \[justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force force | … | comes out best (see below)\].
-
-### Positive Consequences <!-- optional -->
-
-- \[e.g., improvement of quality attribute satisfaction, follow-up decisions required, …\]
-- …
-
-### Negative Consequences <!-- optional -->
-
-- \[e.g., compromising quality attribute, follow-up decisions required, …\]
-- …
-
-## Pros and Cons of the Options <!-- optional -->
-
-### \[option 1\]
-
-\[example | description | pointer to more information | …\] <!-- optional -->
-
-- Good, because \[argument a\]
-- Good, because \[argument b\]
-- Bad, because \[argument c\]
-- … <!-- numbers of pros and cons can vary -->
-
-### \[option 2\]
-
-\[example | description | pointer to more information | …\] <!-- optional -->
-
-- Good, because \[argument a\]
-- Good, because \[argument b\]
-- Bad, because \[argument c\]
-- … <!-- numbers of pros and cons can vary -->
-
-### \[option 3\]
-
-\[example | description | pointer to more information | …\] <!-- optional -->
-
-- Good, because \[argument a\]
-- Good, because \[argument b\]
-- Bad, because \[argument c\]
-- … <!-- numbers of pros and cons can vary -->
-
-## Links <!-- optional -->
-
-- \[Link type\] \[Link to ADR\] <!-- example: Refined by [ADR-0005](0005-example.md) -->
-- … <!-- numbers of links can vary -->
diff --git a/docs/adr/README.md b/docs/adr/README.md
deleted file mode 100644
index a1b83cee..00000000
--- a/docs/adr/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# ADR Documentation
-
-*ADR*: Architectural Design Record
-
-## ADRs
-
-- ADR 000: [Meta-ADR.md](./000-Meta-ADR.md)
diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css
new file mode 100644
index 00000000..e0e08b70
--- /dev/null
+++ b/docs/css/mkdocstrings.css
@@ -0,0 +1,10 @@
+/* Provide more vertical spacing between docstrings
+
+Source:  ;https://github.com/pawamoy/copier-pdm/blob/79135565c4c7f756204a5f460e87129649f8b704/project/docs/css/mkdocstrings.css
+
+ */
+div.doc-contents:not(.first) {
+  padding-left: 25px;
+  border-left: 4px solid rgba(230, 230, 230);
+  margin-bottom: 80px;
+}
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index b896a15f..6492004e 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -1,5 +1,55 @@
 ## Unreleased
 
+### Fix
+
+- don't auto-install types
+
+## 0.19.0 (2022-10-06)
+
+### Feat
+
+- support optional logging of arguments
+
+### Fix
+
+- split mypy install from mypy run
+
+## 0.18.0 (2022-09-27)
+
+### Feat
+
+- support Arrow in pydantic and mypy (ArrowType was causing errors)
+- remove pdoc(s) from document task
+- add gen_ref_nav!
+- tried pybetter, but too focused on adding __all__
+- **#102**: sort-of-replace pdocs, but pdoc only shows one function
+- add mypy install argument
+- expand flake8 and reduce tests
+
+### Fix
+
+- re-run prdc
+- lower fail-under for diff. Raise for regular test
+- standardize on a single doit task list
+- Pathlib.absolute is not documented. Use .resolve
+
+### Refactor
+
+- add type hints and minor changes for mypy
+- make python files non-executable (chmod -x)
+
+## 0.17.1 (2022-09-22)
+
+### Fix
+
+- pytest cache assert circular reference
+
+### Refactor
+
+- move check_security from nox into doit
+
+## 0.17.0 (2022-09-17)
+
 ### Feat
 
 - add docformat
diff --git a/docs/docs/CODE_OF_CONDUCT.md b/docs/docs/CODE_OF_CONDUCT.md
deleted file mode 100644
index 809262ff..00000000
--- a/docs/docs/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, religion, or sexual identity
-and orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-- Demonstrating empathy and kindness toward other people
-- Being respectful of differing opinions, viewpoints, and experiences
-- Giving and gracefully accepting constructive feedback
-- Accepting responsibility and apologizing to those affected by our mistakes,
-  and learning from the experience
-- Focusing on what is best not just for us as individuals, but for the
-  overall community
-
-Examples of unacceptable behavior include:
-
-- The use of sexualized language or imagery, and sexual attention or
-  advances of any kind
-- Trolling, insulting or derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or email
-  address, without their explicit permission
-- Other conduct which could reasonably be considered inappropriate in a
-  professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-dev.act.kyle@gmail.com.
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-## Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-### 2. Warning
-
-**Community Impact**: A violation through a single incident or series
-of actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or
-permanent ban.
-
-### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior,  harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within
-the community.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available at
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct
-enforcement ladder](https://github.com/mozilla/diversity).
-
-For answers to common questions about this code of conduct, see the FAQ at
-https://www.contributor-covenant.org/faq. Translations are available at
-https://www.contributor-covenant.org/translations.
-
-[homepage]: https://www.contributor-covenant.org
diff --git a/docs/docs/CODE_TAG_SUMMARY.md b/docs/docs/CODE_TAG_SUMMARY.md
index 3439a080..03db76ab 100644
--- a/docs/docs/CODE_TAG_SUMMARY.md
+++ b/docs/docs/CODE_TAG_SUMMARY.md
@@ -4,35 +4,35 @@ Auto-Generated by `calcipy`
 
 | Type    | Comment                                                                                                     | Last Edit   | Source File                                                                                                                                                                    |
 |---------|-------------------------------------------------------------------------------------------------------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| FIXME   | Needs to support mkdocs: https://github.com/executablebooks/mdformat/issues/317                             | 2022-09-17  | [.pre-commit-config.yaml:47](https://github.com/KyleKing/calcipy/blame/b8fa0c9b86d9a034066ec0b1c25c32331138e586/.pre-commit-config.yaml#L47)                                   |
-| PLANNED | newest version fails on positional argument errors: https://github.com/tox-dev/pyproject-fmt                | 2022-05-30  | [.pre-commit-config.yaml:67](https://github.com/KyleKing/calcipy/blame/dfb2845414e2823fe10a03b24675a7c6a5293fe7/.pre-commit-config.yaml#L65)                                   |
+| FIXME   | Needs to support mkdocs: https://github.com/executablebooks/mdformat/issues/317                             | 2022-09-17  | [.pre-commit-config.yaml:48](https://github.com/KyleKing/calcipy/blame/b8fa0c9b86d9a034066ec0b1c25c32331138e586/.pre-commit-config.yaml#L47)                                   |
+| PLANNED | newest version fails on positional argument errors: https://github.com/tox-dev/pyproject-fmt                | 2022-05-30  | [.pre-commit-config.yaml:68](https://github.com/KyleKing/calcipy/blame/dfb2845414e2823fe10a03b24675a7c6a5293fe7/.pre-commit-config.yaml#L65)                                   |
 | TODO    | Update from: https://github.com/PyCQA/pylint/blob/main/examples/pylintrc                                    | 2022-09-17  | [.pylintrc:1](https://github.com/KyleKing/calcipy/blame/3d0849142bbc5f75facb281425138cd1e3788945/.pylintrc#L1)                                                                 |
 | PLANNED | Take a look at a few useful issues for cement:                                                              | 2021-12-31  | [calcipy/cli/__init__.py:3](https://github.com/KyleKing/calcipy/blame/91236083605413540509f142ff80dd7aae3fd513/calcipy/cli/__init__.py#L3)                                     |
 | PLANNED | Consider additional commands to add, such as `check-for-stale-packages` or to wrap copier                   | 2022-01-17  | [calcipy/cli/main.py:29](https://github.com/KyleKing/calcipy/blame/ed231ee5dae2d65a4b6696ef795d8984e7698c47/calcipy/cli/main.py#L29)                                           |
-| PLANNED | Consider making "blame" configurable                                                                        | 2022-01-17  | [calcipy/code_tag_collector.py:185](https://github.com/KyleKing/calcipy/blame/ccc2e30c4c859c416ca87585cc56324228632016/calcipy/code_tag_collector.py#L180)                     |
+| PLANNED | Consider making "blame" configurable                                                                        | 2022-01-17  | [calcipy/code_tag_collector.py:183](https://github.com/KyleKing/calcipy/blame/ccc2e30c4c859c416ca87585cc56324228632016/calcipy/code_tag_collector.py#L180)                     |
 | TODO    | Make this auto-resolve based on pyproject.toml                                                              | 2021-10-30  | [calcipy/dev/noxfile.py:70](https://github.com/KyleKing/calcipy/blame/004df806a85aa9d1a6d0bbb5ab5b1f98ac263a3f/calcipy/dev/noxfile.py#L66)                                     |
-| TODO    | Implement semgrep - what are a good ruleset to start with? Currently only a nox-session (no doit task)      | 2021-06-08  | [calcipy/dev/noxfile.py:220](https://github.com/KyleKing/calcipy/blame/2822d0915aa092e099ccf31bc480c47dc2fb42a6/calcipy/dev/noxfile.py#L205)                                   |
-| TODO    | https://github.com/tonybaloney/wily/blob/e72b7d95228bbe5538a072dc5d1186daa318bb03/src/wily/__main__.py#L261 | 2021-12-09  | [calcipy/dev/noxfile.py:249](https://github.com/KyleKing/calcipy/blame/16463ec8d6dcd828106df039a41ac82dc5726790/calcipy/dev/noxfile.py#L270)                                   |
-| TODO    | How are the additional metrics configured?                                                                  | 2021-12-31  | [calcipy/dev/noxfile.py:261](https://github.com/KyleKing/calcipy/blame/29459cfa64e77c2ee291c522230094d84f3ad647/calcipy/dev/noxfile.py#L282)                                   |
-| TODO    | Could use file archiver instead of git when the above fails?                                                | 2021-12-09  | [calcipy/dev/noxfile.py:277](https://github.com/KyleKing/calcipy/blame/16463ec8d6dcd828106df039a41ac82dc5726790/calcipy/dev/noxfile.py#L296)                                   |
+| TODO    | https://github.com/tonybaloney/wily/blob/e72b7d95228bbe5538a072dc5d1186daa318bb03/src/wily/__main__.py#L261 | 2021-12-09  | [calcipy/dev/noxfile.py:211](https://github.com/KyleKing/calcipy/blame/16463ec8d6dcd828106df039a41ac82dc5726790/calcipy/dev/noxfile.py#L270)                                   |
+| TODO    | How are the additional metrics configured?                                                                  | 2021-12-31  | [calcipy/dev/noxfile.py:223](https://github.com/KyleKing/calcipy/blame/29459cfa64e77c2ee291c522230094d84f3ad647/calcipy/dev/noxfile.py#L282)                                   |
+| TODO    | Could use file archiver instead of git when the above fails?                                                | 2021-12-09  | [calcipy/dev/noxfile.py:239](https://github.com/KyleKing/calcipy/blame/16463ec8d6dcd828106df039a41ac82dc5726790/calcipy/dev/noxfile.py#L296)                                   |
 | FIXME   | Refactor "cl_bump*" functions. Make "which $(..) >> /dev/null && " a function                               | 2022-01-19  | [calcipy/doit_tasks/doc.py:80](https://github.com/KyleKing/calcipy/blame/88a59195792115e9328dc418f24ab73970e0fffe/calcipy/doit_tasks/doc.py#L79)                               |
 | FIXME   | Refactor "cl_bump*" functions. Make "which $(..) >> /dev/null && " a function                               | 2022-01-19  | [calcipy/doit_tasks/doc.py:100](https://github.com/KyleKing/calcipy/blame/88a59195792115e9328dc418f24ab73970e0fffe/calcipy/doit_tasks/doc.py#L99)                              |
-| TODO    | Better differentiate Task vs. Dict when typing and a dictionary is expected                                 | 2022-09-15  | [calcipy/doit_tasks/doit_globals.py:35](https://github.com/KyleKing/calcipy/blame/af48411a58a6bad447c8eb2a3e07cf842bb3a3a7/calcipy/doit_tasks/doit_globals.py#L35)             |
-| PLANNED | Consider adding a check to validate that the minimum Python is above calcipy's minimum                      | 2022-09-17  | [calcipy/doit_tasks/doit_globals.py:244](https://github.com/KyleKing/calcipy/blame/ccbc5b22360b789bcdaf0068eb76373caddbeec1/calcipy/doit_tasks/doit_globals.py#L244)           |
+| TODO    | Better differentiate Task vs. Dict when typing and a dictionary is expected                                 | 2022-09-15  | [calcipy/doit_tasks/doit_globals.py:37](https://github.com/KyleKing/calcipy/blame/af48411a58a6bad447c8eb2a3e07cf842bb3a3a7/calcipy/doit_tasks/doit_globals.py#L35)             |
+| FIXME   | Merge with .flake8 configuration...                                                                         | 2022-09-27  | [calcipy/doit_tasks/doit_globals.py:196](https://github.com/KyleKing/calcipy/blame/800249ea39d3dc6dd2a4558b9e17ccc60d46e483/calcipy/doit_tasks/doit_globals.py#L194)           |
+| PLANNED | Consider adding a check to validate that the minimum Python is above calcipy's minimum                      | 2022-09-17  | [calcipy/doit_tasks/doit_globals.py:247](https://github.com/KyleKing/calcipy/blame/ccbc5b22360b789bcdaf0068eb76373caddbeec1/calcipy/doit_tasks/doit_globals.py#L244)           |
 | PLANNED | Simplify and choose one way of using Radon                                                                  | 2021-06-08  | [calcipy/doit_tasks/lint.py:160](https://github.com/KyleKing/calcipy/blame/b7f0f8d3b2a6c659511d690a1125a101c3945de8/calcipy/doit_tasks/lint.py#L153)                           |
-| PLANNED | Considering splitting paths on commas if above some maximum character length?                               | 2022-01-16  | [calcipy/doit_tasks/lint.py:215](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L225)                           |
-| FIXME   | Add bulk toml formatting to the auto_format task!                                                           | 2022-01-16  | [calcipy/doit_tasks/lint.py:268](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L270)                           |
-| PLANNED | Could provide more hooks for configuring taplo options. See:                                                | 2022-01-16  | [calcipy/doit_tasks/lint.py:282](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L284)                           |
-| FIXME   | capturing to /dev/null doesn't seem to work reliably                                                        | 2022-02-23  | [calcipy/doit_tasks/lint.py:285](https://github.com/KyleKing/calcipy/blame/81c0adea275d4ed227d8ca95e6d752ae54577fed/calcipy/doit_tasks/lint.py#L289)                           |
-| HACK    | See pending pydantic changes: https://github.com/pydantic/pydantic/discussions/4456                         | 2022-09-15  | [calcipy/doit_tasks/packaging.py:129](https://github.com/KyleKing/calcipy/blame/af48411a58a6bad447c8eb2a3e07cf842bb3a3a7/calcipy/doit_tasks/packaging.py#L128)                 |
-| HACK    | Check that this works then refactor (shouldn't be global)                                                   | 2021-06-11  | [calcipy/doit_tasks/packaging.py:146](https://github.com/KyleKing/calcipy/blame/bbf19e63cbe0fc891dc76d686d7e113fa15be4ab/calcipy/doit_tasks/packaging.py#L146)                 |
-| TBD     | Handle non-pypi domains and format the URL accordingly (i.e. TestPyPi, etc.)                                | 2021-06-03  | [calcipy/doit_tasks/packaging.py:274](https://github.com/KyleKing/calcipy/blame/adaf2a6b09bfaa1cd4c55792fadf1ea0adeacd76/calcipy/doit_tasks/packaging.py#L157)                 |
-| PLANNED | handle multiline descriptions                                                                               | 2021-06-05  | [calcipy/doit_tasks/templates/text.mako:82](https://github.com/KyleKing/calcipy/blame/b0da1f50be6f40a8b083056c96b96e989449610f/calcipy/doit_tasks/templates/text.mako#L80)     |
-| PLANNED | handle multiline descriptions                                                                               | 2021-06-05  | [calcipy/doit_tasks/templates/text.mako:92](https://github.com/KyleKing/calcipy/blame/b0da1f50be6f40a8b083056c96b96e989449610f/calcipy/doit_tasks/templates/text.mako#L90)     |
-| PLANNED | add meta (example and notes)                                                                                | 2021-06-05  | [calcipy/doit_tasks/templates/text.mako:217](https://github.com/KyleKing/calcipy/blame/b0da1f50be6f40a8b083056c96b96e989449610f/calcipy/doit_tasks/templates/text.mako#L215)   |
-| TODO    | modify so that mkdocs.yml can be read, but Python won't be executed...                                      | 2021-06-05  | [calcipy/file_helpers.py:57](https://github.com/KyleKing/calcipy/blame/6379b222573bd879001f6677b9dfd16f48735acc/calcipy/file_helpers.py#L57)                                   |
-| PLANNED | handle carriage returns                                                                                     | 2021-10-31  | [calcipy/file_helpers.py:94](https://github.com/KyleKing/calcipy/blame/5e085724fff76d1ffb95a36ebd14448f1aa342d2/calcipy/file_helpers.py#L94)                                   |
-| PLANNED | consider a STDOUT format like https://pypi.org/project/readable-log-formatter                               | 2021-06-08  | [calcipy/log_helpers.py:3](https://github.com/KyleKing/calcipy/blame/d459ba077570bce60b39233c1c771359a4c14316/calcipy/log_helpers.py#L3)                                       |
+| TODO    | Implement semgrep - what are a good ruleset to start with?                                                  | 2022-09-20  | [calcipy/doit_tasks/lint.py:202](https://github.com/KyleKing/calcipy/blame/d402123d86a72145caf9703f7890fea8622b5f4b/calcipy/doit_tasks/lint.py#L201)                           |
+| PLANNED | Considering splitting paths on commas if above some maximum character length?                               | 2022-01-16  | [calcipy/doit_tasks/lint.py:243](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L225)                           |
+| FIXME   | Add bulk toml formatting to the auto_format task!                                                           | 2022-01-16  | [calcipy/doit_tasks/lint.py:296](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L270)                           |
+| PLANNED | Could provide more hooks for configuring taplo options. See:                                                | 2022-01-16  | [calcipy/doit_tasks/lint.py:310](https://github.com/KyleKing/calcipy/blame/f51fc70c2f237deef0d039c2a1f616ff1b901427/calcipy/doit_tasks/lint.py#L284)                           |
+| FIXME   | capturing to /dev/null doesn't seem to work reliably                                                        | 2022-02-23  | [calcipy/doit_tasks/lint.py:313](https://github.com/KyleKing/calcipy/blame/81c0adea275d4ed227d8ca95e6d752ae54577fed/calcipy/doit_tasks/lint.py#L289)                           |
+| HACK    | Check that this works then refactor (shouldn't be global)                                                   | 2021-06-11  | [calcipy/doit_tasks/packaging.py:125](https://github.com/KyleKing/calcipy/blame/bbf19e63cbe0fc891dc76d686d7e113fa15be4ab/calcipy/doit_tasks/packaging.py#L146)                 |
+| TBD     | Handle non-pypi domains and format the URL accordingly (i.e. TestPyPi, etc.)                                | 2021-06-03  | [calcipy/doit_tasks/packaging.py:251](https://github.com/KyleKing/calcipy/blame/adaf2a6b09bfaa1cd4c55792fadf1ea0adeacd76/calcipy/doit_tasks/packaging.py#L157)                 |
+| TODO    | modify so that mkdocs.yml can be read, but Python won't be executed...                                      | 2021-06-05  | [calcipy/file_helpers.py:58](https://github.com/KyleKing/calcipy/blame/6379b222573bd879001f6677b9dfd16f48735acc/calcipy/file_helpers.py#L57)                                   |
+| PLANNED | handle carriage returns                                                                                     | 2021-10-31  | [calcipy/file_helpers.py:95](https://github.com/KyleKing/calcipy/blame/5e085724fff76d1ffb95a36ebd14448f1aa342d2/calcipy/file_helpers.py#L94)                                   |
+| FIXME   | consider a STDOUT format like https://pypi.org/project/readable-log-formatter                               | 2022-09-28  | [calcipy/log_helpers.py:3](https://github.com/KyleKing/calcipy/blame/20e72a21767d7053a6b22f89d472fe5938ba6608/calcipy/log_helpers.py#L3)                                       |
+| TODO    | Provide interface for logger.info, etc.)                                                                    | 2022-09-28  | [calcipy/log_helpers.py:11](https://github.com/KyleKing/calcipy/blame/20e72a21767d7053a6b22f89d472fe5938ba6608/calcipy/log_helpers.py#L11)                                     |
+| TODO    | Improve logging for function timing (maybe size/number of data in arguments?)                               | 2022-09-28  | [calcipy/log_helpers.py:12](https://github.com/KyleKing/calcipy/blame/20e72a21767d7053a6b22f89d472fe5938ba6608/calcipy/log_helpers.py#L12)                                     |
+| PLANNED | Consider making the format handler specified via Protocol                                                   | 2022-09-28  | [calcipy/log_helpers.py:200](https://github.com/KyleKing/calcipy/blame/20e72a21767d7053a6b22f89d472fe5938ba6608/calcipy/log_helpers.py#L193)                                   |
 | TODO    | Add documentation on configuration items in `pyproject.toml`                                                | 2021-06-08  | [docs/Advanced_Configuration.md:3](https://github.com/KyleKing/calcipy/blame/d459ba077570bce60b39233c1c771359a4c14316/docs/Advanced_Configuration.md#L3)                       |
 | TODO    | Add documentation on how to change DG within limited bounds (i.e. `handler_lookup` in `recipes`)            | 2021-06-08  | [docs/Advanced_Configuration.md:4](https://github.com/KyleKing/calcipy/blame/d459ba077570bce60b39233c1c771359a4c14316/docs/Advanced_Configuration.md#L4)                       |
 | TODO    | Implement fully optional imports with: https://github.com/KyleKing/calcipy/issues/19#issuecomment-807886404 | 2021-12-08  | [pyproject.toml:93](https://github.com/KyleKing/calcipy/blame/561583f54114efa41dec220bba6a8fb7ae0af80b/pyproject.toml#L82)                                                     |
@@ -44,9 +44,11 @@ Auto-Generated by `calcipy`
 | PLANNED | Not yet used. From cement boilerplate                                                                       | 2021-12-31  | [tests/cli/test_main.py:22](https://github.com/KyleKing/calcipy/blame/21c5b446fca11d83dce8d2daedc2027c3d58b221/tests/cli/test_main.py#L22)                                     |
 | TODO    | Replace magic numbers in tests with meta-data about the "test_calcipy_project"                              | 2022-02-27  | [tests/configuration.py:24](https://github.com/KyleKing/calcipy/blame/ae7a371c863f742aed94fe4c86e0b5ac0499bc2a/tests/configuration.py#L24)                                     |
 | FIXME   | Replace MockLogger with a more generic alternative. See:                                                    | 2021-06-05  | [tests/test_doit_tasks/test_packaging.py:20](https://github.com/KyleKing/calcipy/blame/b0da1f50be6f40a8b083056c96b96e989449610f/tests/test_doit_tasks/test_packaging.py#L20)   |
-| TODO    | Capture logging output and check...                                                                         | 2021-06-08  | [tests/test_doit_tasks/test_packaging.py:132](https://github.com/KyleKing/calcipy/blame/f5ae6a5d029efa6cbc7ad6d26f2f61c6e28427f2/tests/test_doit_tasks/test_packaging.py#L132) |
-| TODO    | Convert to hypothesis test!                                                                                 | 2022-02-27  | [tests/test_dot_dict.py:9](https://github.com/KyleKing/calcipy/blame/884ff912d5716f91fc36a41be0eb492740335f24/tests/test_dot_dict.py#L9)                                       |
+| TODO    | Capture logging output and check...                                                                         | 2021-06-08  | [tests/test_doit_tasks/test_packaging.py:126](https://github.com/KyleKing/calcipy/blame/f5ae6a5d029efa6cbc7ad6d26f2f61c6e28427f2/tests/test_doit_tasks/test_packaging.py#L132) |
+| FIXME   | Revisit. Seems unstable                                                                                     | 2022-09-27  | [tests/test_doit_tasks/test_test.py:50](https://github.com/KyleKing/calcipy/blame/4403a42fbc4b566ef70032df845fd7f7cfb08223/tests/test_doit_tasks/test_test.py#L50)             |
+| FIXME   | Revisit. Seems unstable                                                                                     | 2022-09-27  | [tests/test_doit_tasks/test_test.py:58](https://github.com/KyleKing/calcipy/blame/4403a42fbc4b566ef70032df845fd7f7cfb08223/tests/test_doit_tasks/test_test.py#L58)             |
+| TODO    | Convert to hypothesis test!                                                                                 | 2022-02-27  | [tests/test_dot_dict.py:11](https://github.com/KyleKing/calcipy/blame/884ff912d5716f91fc36a41be0eb492740335f24/tests/test_dot_dict.py#L9)                                      |
 
-Found code tags for FIXME (7), TODO (17), PLANNED (14), HACK (3), TBD (1)
+Found code tags for FIXME (11), TODO (19), PLANNED (11), HACK (2), TBD (1)
 
 <!-- calcipy:skip_tags -->
diff --git a/docs/docs/CONTRIBUTING.md b/docs/docs/CONTRIBUTING.md
deleted file mode 100644
index 0b96663f..00000000
--- a/docs/docs/CONTRIBUTING.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Contributing
-
-Thanks for taking a look! This is primarily a personal project, but Pull Requests and Issues (questions, feature requests, etc.) are welcome. If you would like to submit a Pull Request, please open an issue first to discuss what you would like to change
-
-## Pull Requests (PR)
-
-### Code Development
-
-See [./DEVELOPER_GUIDE.md](./DEVELOPER_GUIDE.md)
-
-### PR Process
-
-1. Fork the Project and Clone
-1. Create a new branch (`git checkout -b feat/feature-name`)
-1. Edit code; update documentation and tests; commit and push
-1. Before submitting the review and pushing, make sure to run `poetry run doit`
-1. Open a new Pull Request
-
-> See the style guide for commit message format ([./STYLE_GUIDE](./STYLE_GUIDE))
-
-If you run into any issues, please check to see if there is an open issues or open a new one
-
-### Other PR Tips
-
-- Link the issue with `Fixes #N` in the Pull Request body
-- Please add a short summary of `why` the change was made, `what changed`, and any relevant information or screenshots
-
-```sh
-# SHA is the SHA of the commit you want to fix
-git commit --fixup=SHA
-# Once all the changes are approved, you can squash your commits:
-git rebase --interactive --autosquash main
-# Force Push
-git push --force
-```
diff --git a/docs/docs/DEVELOPER_GUIDE.md b/docs/docs/DEVELOPER_GUIDE.md
index 9ac975ec..d5ffb587 100644
--- a/docs/docs/DEVELOPER_GUIDE.md
+++ b/docs/docs/DEVELOPER_GUIDE.md
@@ -19,7 +19,7 @@ poetry run doit run test
 
 ## Publishing
 
-For testing, create an account on [TestPyPi](https://test.pypi.org/legacy/). Replace `...` with the API token generated on TestPyPi|PyPi respectively
+For testing, create an account on [TestPyPi](https://test.pypi.org/legacy/). Replace `...` with the API token generated on TestPyPi or PyPi respectively
 
 ```sh
 poetry config repositories.testpypi https://test.pypi.org/legacy/
@@ -52,30 +52,30 @@ poetry run doit run lock document deploy_docs publish
 | `calcipy/__init__.py`                                      |            6 |         0 |          0 | 100.0%     |
 | `calcipy/cli/__init__.py`                                  |            0 |         0 |          0 | 100.0%     |
 | `calcipy/cli/controllers/__init__.py`                      |            0 |         0 |          0 | 100.0%     |
-| `calcipy/cli/controllers/code_tag_collector_controller.py` |           26 |        12 |          0 | 53.8%      |
+| `calcipy/cli/controllers/code_tag_collector_controller.py` |           27 |        12 |          0 | 55.6%      |
 | `calcipy/cli/core/__init__.py`                             |            0 |         0 |          0 | 100.0%     |
 | `calcipy/cli/core/exceptions.py`                           |            2 |         0 |          0 | 100.0%     |
 | `calcipy/cli/core/version.py`                              |            8 |         8 |          0 | 0.0%       |
 | `calcipy/cli/main.py`                                      |           38 |        19 |          0 | 50.0%      |
-| `calcipy/code_tag_collector.py`                            |          120 |        23 |          0 | 80.8%      |
+| `calcipy/code_tag_collector.py`                            |          116 |        23 |          0 | 80.2%      |
 | `calcipy/dev/__init__.py`                                  |            0 |         0 |          0 | 100.0%     |
 | `calcipy/dev/conftest.py`                                  |           16 |         0 |         23 | 100.0%     |
-| `calcipy/dev/noxfile.py`                                   |           23 |         1 |         81 | 95.7%      |
-| `calcipy/doit_tasks/__init__.py`                           |           13 |         0 |          0 | 100.0%     |
+| `calcipy/dev/noxfile.py`                                   |           23 |         1 |         75 | 95.7%      |
+| `calcipy/doit_tasks/__init__.py`                           |           11 |         0 |          0 | 100.0%     |
 | `calcipy/doit_tasks/base.py`                               |           50 |        10 |          3 | 80.0%      |
 | `calcipy/doit_tasks/code_tags.py`                          |           11 |         0 |          0 | 100.0%     |
-| `calcipy/doit_tasks/doc.py`                                |          149 |        10 |          5 | 93.3%      |
-| `calcipy/doit_tasks/doit_globals.py`                       |          185 |         5 |          4 | 97.3%      |
-| `calcipy/doit_tasks/lint.py`                               |          102 |         8 |          0 | 92.2%      |
-| `calcipy/doit_tasks/packaging.py`                          |          141 |        13 |          0 | 90.8%      |
+| `calcipy/doit_tasks/doc.py`                                |          146 |        10 |          5 | 93.2%      |
+| `calcipy/doit_tasks/doit_globals.py`                       |          186 |         5 |          4 | 97.3%      |
+| `calcipy/doit_tasks/lint.py`                               |          103 |         9 |          0 | 91.3%      |
+| `calcipy/doit_tasks/packaging.py`                          |          132 |        12 |          0 | 90.9%      |
 | `calcipy/doit_tasks/summary_reporter.py`                   |           21 |         0 |         40 | 100.0%     |
-| `calcipy/doit_tasks/test.py`                               |           66 |        11 |          0 | 83.3%      |
+| `calcipy/doit_tasks/test.py`                               |           67 |         4 |          0 | 94.0%      |
 | `calcipy/dot_dict.py`                                      |            7 |         0 |          0 | 100.0%     |
-| `calcipy/file_helpers.py`                                  |           76 |         7 |          3 | 90.8%      |
+| `calcipy/file_helpers.py`                                  |           76 |         6 |          3 | 92.1%      |
 | `calcipy/file_search.py`                                   |           34 |         0 |          2 | 100.0%     |
-| `calcipy/log_helpers.py`                                   |           62 |         6 |          0 | 90.3%      |
-| `calcipy/proc_helpers.py`                                  |           21 |         1 |          0 | 95.2%      |
-| **Totals**                                                 |         1177 |       134 |        161 | 88.6%      |
+| `calcipy/log_helpers.py`                                   |           68 |         7 |          0 | 89.7%      |
+| `calcipy/proc_helpers.py`                                  |           20 |         1 |          0 | 95.0%      |
+| **Totals**                                                 |         1168 |       127 |        155 | 89.1%      |
 
-Generated on: 2022-09-17
+Generated on: 2022-10-13
 <!-- {cte} -->
diff --git a/docs/docs/SECURITY.md b/docs/docs/SECURITY.md
deleted file mode 100644
index d7bd41c2..00000000
--- a/docs/docs/SECURITY.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Security
-
-## Reporting Security Issues
-
-> Do not open issues that might have security implications! It is critical that security related issues are reported privately so we have time to address them before they become public knowledge.
-
-Vulnerabilities can be reported by emailing core members:
-
-- Kyle King ([dev.act.kyle@gmail.com](mailto:dev.act.kyle@gmail.com))
-
-Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
-
-- [ ] Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
-- [ ] Full paths of source file(s) related to the manifestation of the issue
-- [ ] The location of the affected source code (tag/branch/commit or direct URL)
-- [ ] Any special configuration required to reproduce the issue
-- [ ] Environment (e.g. Linux / Windows / macOS)
-- [ ] Step-by-step instructions to reproduce the issue
-- [ ] Proof-of-concept or exploit code (if possible)
-- [ ] Impact of the issue, including how an attacker might exploit the issue
-
-This information will help us triage your report more quickly.
-
-## Preferred Languages
-
-We prefer all communications to be in English.
-
-## Attribution
-
-This file was based on the template from [TezRomacH/python-package-template/SECURITY.md](https://github.com/TezRomacH/python-package-template/blob/184e6cd577106753f0129d8341803b0e7b425404/SECURITY.md)
diff --git a/docs/docs/STYLE_GUIDE.md b/docs/docs/STYLE_GUIDE.md
index 33e43e91..1d0b58b3 100644
--- a/docs/docs/STYLE_GUIDE.md
+++ b/docs/docs/STYLE_GUIDE.md
@@ -2,39 +2,33 @@
 
 ## Git
 
-Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and [Commitizen](https://github.com/commitizen-tools/commitizen)
+We use [Commitizen](https://github.com/commitizen-tools/commitizen) to manage both an auto-generated [Changelog](https://keepachangelog.com/en/1.0.0/) and incrementing the release version following [semver](https://semver.org/). For both of these automated outputs to work well, please follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style, which is described in more detail below.
 
-The [Changelog](https://keepachangelog.com/en/1.0.0/) is an important part of a project (built with `commitizen`). Use [semver](https://semver.org/)
-
-### Conventional Commits
+### Commitizen Types and Scopes
 
-- `type(scope): description / body`
-- The type feat MUST be used when a commit adds a new feature to your application or library.
-- The type fix MUST be used when a commit represents a bug fix for your application.
-- A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., `fix(parser):` or issue number `fix(#32):`
-- A `!` can be used to indicate a breaking change, e.g. `refactor!: drop support for Node 6`
-- What if a commit fits multiple types?
-  - Go back and make multiple commits whenever possible. Part of the benefit of Conventional Commits is its ability to drive us to make more organized commits and PRs.
-  - It discourages moving fast in a disorganized way. It helps you be able to move fast long term across multiple projects with varied contributors.
-- `semver`: `fix : PATCH / feat : MINOR / BREAKING CHANGE : MAJOR`
+> `type(scope): description`
+
+- **Types**
+  - *fix*: A bug fix
+  - *feat*: A new feature
+  - *docs*: Documentation-only changes (code comments, separate docs)
+  - *style*: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons)
+  - *perf*: A code change that improves performance
+  - *refactor*: A change to production code that is not a *fix*, *feat*, or *perf*
+  - *test*: Adding missing or correcting existing tests
+  - *build*: Changes that affect the build system or external dependencies
+  - *ci*: Changes to our CI configuration files and scripts
+  - A `!` can be used to indicate a breaking change (`refactor!: drop support for Node 6`)
+  - **SemVer Rules**
+    - Based on commit type, the version will be auto-incremented: `fix : PATCH // feat : MINOR // BREAKING CHANGE : MAJOR`
+- **Scopes**
+  - A Class, File name, Issue Number, other appropriate noun. As examples: `build(poetry): bump requests to v3` or `style(#32): add missing type annotations`
+- **Tips**
+  - What if a commit fits multiple types?
+    - Go back and make multiple commits whenever possible. Part of the benefit of Conventional Commits is the focus on more organized and intentional changes
   - Use `git rebase -i` to fix commit names prior to merging if incorrect types/scopes are used
 
-### Commitizen Types and Scopes
-
-- Types
-  - fix: A bug fix
-  - feat: A new feature
-  - docs: Documentation-only changes (code comments, separate docs)
-  - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons)
-  - perf: A code change that improves performance
-  - refactor: A change to production code that is not fix, feat, or perf
-  - test: Adding missing or correcting existing tests
-  - build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)
-  - ci: Changes to our CI configuration files and scripts (example scopes: GitLabCI)
-- Scopes
-  - Class, File name, Issue Number, other approved noun
-
-### Git Message Guidelines
+### Git Description Guidelines
 
 - [Commit message guidelines](https://writingfordevelopers.substack.com/p/how-to-write-a-commit-message)
   - Full sentence with verb (*lowercase*) and concise description. Below are modified examples for Conventional Commits
@@ -47,7 +41,7 @@ The [Changelog](https://keepachangelog.com/en/1.0.0/) is an important part of a
 - [How to write a good commit message](https://chris.beams.io/posts/git-commit/)
   - A diff will tell you what changed, but only the commit message can properly tell you why.
   - Keep in mind: [This](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [has](https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines) [all](https://github.com/torvalds/subsurface-for-dirk/blob/master/README.md#contributing) [been](http://who-t.blogspot.co.at/2009/12/on-commit-messages.html) [said](https://github.com/erlang/otp/wiki/writing-good-commit-messages) [before](https://github.com/spring-projects/spring-framework/blob/30bce7/CONTRIBUTING.md#format-commit-messages).
-  - The seven rules of a great Git commit message
+  - From the seven rules of a great Git commit message:
     - 2. [Try for 50 characters, but consider 72 the hard limit](https://chris.beams.io/posts/git-commit/#limit-50)
     - 7. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how)
 
@@ -55,83 +49,76 @@ The [Changelog](https://keepachangelog.com/en/1.0.0/) is an important part of a
 
 Personal Guide
 
-- Labels
-  - `Needs Discussion`: (`#ff5722`) Ticket needs discussion and prioritization
-  - `Type: Bug`: (`#d73a4a`) Something isn't working
-  - `Type: Documentation`: (`#69cde9`) Documentation changes
-  - `Type: Maintenance`: (`#c5def5`) Chore including build/dep, CI, refactor, or perf
-  - `Type: Idea`: (`#fbca04`) General idea or concept that could become a feature request
-  - `Type: Feature`: (`#0075ca`) Clearly defined new feature request
+- For Issue Labels, see [labels.yml][labels]
 - Milestones
-  - Current Tasks (Main Milestone) - name could change based on a specific project, sprint, or month
-  - Next Tasks
-  - Blue Sky
-- Search
-  - `is:open is:issue assignee:{{ author_username }} archived:false milestone:"blue sky"` or `no:milestone` etc.
+  - **Current Tasks**: main milestone (*name could change based on a specific project, sprint, or month*)
+  - **Next Tasks**
+  - **Blue Sky**
 
 <details>
 <summary>Research</summary>
 <ul>
 <li>[Sane Github Labels](https://medium.com/@dave_lunny/sane-github-labels-c5d2e6004b63) and see [sensible-github-labels](https://github.com/Relequestual/sensible-github-labels) for full descriptions of each</li>
     <ul>
-    <li>“it is much more helpful to see the status and type of all issues at a glance.”</li>
-    <li>One of each:</li>
+        <li>“it is much more helpful to see the status and type of all issues at a glance.”</li>
+        <li>One of each:</li>
         <ul>
-        <li>Status: …</li>
-            <ul><li>Abandoned, Accepted, Available, Blocked, Completed, In Progress, On Hold, Pending, Review Needed, Revision Needed</li></ul>
-        <li>Type: …</li>
-            <ul><li>Bug, Maintenance, Question, Enhancement</li></ul>
-        <li>Priority: …</li>
-            <ul><li>Critical, High, Medium, Low</li></ul>
+            <li>Status: …</li>
+                <ul><li>Abandoned, Accepted, Available, Blocked, Completed, In Progress, On Hold, Pending, Review Needed, Revision Needed</li></ul>
+            <li>Type: …</li>
+                <ul><li>Bug, Maintenance, Question, Enhancement</li></ul>
+            <li>Priority: …</li>
+                <ul><li>Critical, High, Medium, Low</li></ul>
         </ul>
     </ul>
-<li>[Britecharts](https://britecharts.github.io/britecharts/github-labels.html)</li>
+    <li>[Britecharts](https://britecharts.github.io/britecharts/github-labels.html)</li>
     <ul>
-    <li>Status: …</li>
-        <ul>
-        <li>On Review – Request that we are pondering if including or not</li>
-        <li>Needs Reproducing – For bugs that need to be reproduced in order to get fixed</li>
-        <li>Needs Design – Feature that needs a design</li>
-        <li>Ready to Go – Issue that has been defined and is ready to get started with</li>
-        <li>In Progress – Issue that is being worked on right now.</li>
-        <li>Completed – Finished feature or fix</li>
-        </ul>
-    <li>Type: …</li>
-        <ul>
-        <li>Bug – An unexpected problem or unintended behavior</li>
-        <li>Feature – A new feature request</li>
-        <li>Maintenance – A regular maintenance chore or task, including refactors, build system, CI, performance improvements</li>
-        <li>Documentation – A documentation improvement task</li>
-        <li>Question – An issue or PR that needs more information or a user question</li>
-        </ul>
-    <li>Not Included</li>
-        <ul>
-        <li>Priority: They would add complexity and overhead due to the discussions, but could help with the roadmap</li>
-        <li>Technology Labels: It can create too much overhead, as properly tag with technologies all the issues could be time consuming</li>
+        <li>Status: …</li>
+            <ul>
+                <li>On Review – Request that we are pondering if including or not</li>
+                <li>Needs Reproducing – For bugs that need to be reproduced in order to get fixed</li>
+                <li>Needs Design – Feature that needs a design</li>
+                <li>Ready to Go – Issue that has been defined and is ready to get started with</li>
+                <li>In Progress – Issue that is being worked on right now.</li>
+                <li>Completed – Finished feature or fix</li>
+            </ul>
+        <li>Type: …</li>
+            <ul>
+                <li>Bug – An unexpected problem or unintended behavior</li>
+                <li>Feature – A new feature request</li>
+                <li>Maintenance – A regular maintenance chore or task, including refactors, build system, CI, performance improvements</li>
+                <li>Documentation – A documentation improvement task</li>
+                <li>Question – An issue or PR that needs more information or a user question</li>
+            </ul>
+        <li>Not Included</li>
+            <ul>
+                <li>Priority: They would add complexity and overhead due to the discussions, but could help with the roadmap</li>
+                <li>Technology Labels: It can create too much overhead, as properly tag with technologies all the issues could be time consuming</li>
+            </ul>
         </ul>
     </ul>
-<li>[Ian Bicking Blog](https://www.ianbicking.org/blog/2014/03/use-github-issues-to-organize-a-project.html)</li>
+    <li>[Ian Bicking Blog](https://www.ianbicking.org/blog/2014/03/use-github-issues-to-organize-a-project.html)</li>
     <ul>
-    <li>Milestone Overview</li>
+        <li>Milestone Overview</li>
         <ul>
-        <li>What are we doing right now?</li>
-        <li>What aren’t we doing right now?</li>
-            <ul>
-            <li>2a. Stuff we’ll probably do soon</li>
-            <li>2b. Stuff we probably won’t do soon</li>
-            </ul>
-        <li>What aren’t we sure about?</li>
+            <li>What are we doing right now?</li>
+            <li>What aren’t we doing right now?</li>
+                <ul>
+                    <li>2a. Stuff we’ll probably do soon</li>
+                    <li>2b. Stuff we probably won’t do soon</li>
+                </ul>
+            <li>What aren’t we sure about?</li>
         </ul>
-    <li>Milestone Descriptions</li>
+        <li>Milestone Descriptions</li>
         <ul>
-        <li>Stuff we are doing right now: this is the “main” milestone. We give it a name (like Alpha 2 or Strawberry Rhubarb Pie) and we write down what we are trying to accomplish with the milestone. We create a new milestone when we are ready for the next iteration.</li>
-        <li>Stuff we’ll probably do soon: this is a standing “**Next Tasks**” milestone. We never change or rename this milestone.</li>
-            <ul><li>We use a permanent “Next Tasks” milestone (as opposed to renaming it to “Alpha 3” or actual-next-iteration milestone) because we don’t want to presume or default to including something in the real next iteration. When we’re ready to start planning the next iteration we’ll create a new milestone, and only deliberately move things into that milestone.</li></ul>
-        <li>Stuff we probably won’t do soon: this is a standing “**Blue Sky**” milestone. We refer to these tickets and sometimes look through them, but they are easy to ignore, somewhat intentionally ignored.</li>
-        <li>What aren’t we sure about?: issues with no milestone.</li>
+            <li>Stuff we are doing right now: this is the “main” milestone. We give it a name (like Alpha 2 or Strawberry Rhubarb Pie) and we write down what we are trying to accomplish with the milestone. We create a new milestone when we are ready for the next iteration.</li>
+            <li>Stuff we’ll probably do soon: this is a standing “**Next Tasks**” milestone. We never change or rename this milestone.</li>
+                <ul><li>We use a permanent “Next Tasks” milestone (as opposed to renaming it to “Alpha 3” or actual-next-iteration milestone) because we don’t want to presume or default to including something in the real next iteration. When we’re ready to start planning the next iteration we’ll create a new milestone, and only deliberately move things into that milestone.</li></ul>
+            <li>Stuff we probably won’t do soon: this is a standing “**Blue Sky**” milestone. We refer to these tickets and sometimes look through them, but they are easy to ignore, somewhat intentionally ignored.</li>
+            <li>What aren’t we sure about?: issues with no milestone.</li>
         </ul>
-    <li>Label: “Needs Discussion” - (addressed in a triage meeting) - use liberally for either big or small tickets</li>
-    <li>“It’s better to give people more power: it’s actually helpful if people can overreach because it is an opportunity to establish where the limits really are and what purpose those limits have”</li>
+        <li>Label: “Needs Discussion” - (addressed in a triage meeting) - use liberally for either big or small tickets</li>
+        <li>“It’s better to give people more power: it’s actually helpful if people can overreach because it is an opportunity to establish where the limits really are and what purpose those limits have”</li>
     </ul>
 </ul>
 </details>
@@ -140,8 +127,8 @@ Personal Guide
 
 **TODO: Revisit**
 
-- [Git: The Simple Guide][1]
-- [Commit Messages][2] and [why use the present tense](https://news.ycombinator.com/item?id=8874177)
+- [Git: The Simple Guide][simple_git]
+- [Commit Messages][gcmsg] and [why use the present tense](https://news.ycombinator.com/item?id=8874177)
 - [Github's Advice on Github](https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
 - [Most Comprehensive Guide](https://chris.beams.io/posts/git-commit/)
 - [Git Pro Book (free)](https://git-scm.com/book/en/v2)
@@ -167,5 +154,7 @@ Personal Guide
 - Examples
   - <https://github.com/pawamoy/mkdocstrings/issues/28>
 
-[1]: http://rogerdudler.github.io/git-guide/
-[2]: https://github.com/atom/atom/blob/master/CONTRIBUTING.md#styleguides
+<-- Links -->
+  [simple_git]: http://rogerdudler.github.io/git-guide/
+  [gcmsg]: https://github.com/atom/atom/blob/master/CONTRIBUTING.md#styleguides
+  [labels]: https://github.com/kyleking/.github/labels.yml
diff --git a/docs/gen_ref_nav.py b/docs/gen_ref_nav.py
new file mode 100644
index 00000000..0cf6b64f
--- /dev/null
+++ b/docs/gen_ref_nav.py
@@ -0,0 +1,39 @@
+"""Generate the code reference pages and navigation.
+
+Copied from: https://github.com/pawamoy/copier-pdm/blob/79135565c4c7f756204a5f460e87129649f8b704/project/docs/gen_ref_nav.py
+
+"""
+
+from pathlib import Path
+
+import mkdocs_gen_files
+
+nav = mkdocs_gen_files.Nav()
+
+for path in sorted(Path('calcipy').rglob('*.py')):
+    module_path = path.with_suffix('')
+    doc_path = path.with_suffix('.md')
+    full_doc_path = Path('reference', doc_path)
+
+    parts = tuple(module_path.parts)
+
+    if parts[-1] == '__init__':
+        parts = parts[:-1]
+        doc_path = doc_path.with_name('index.md')
+        full_doc_path = full_doc_path.with_name('index.md')
+    elif parts[-1] == '__main__':
+        continue
+
+    nav[parts] = doc_path.as_posix()
+
+    with mkdocs_gen_files.open(full_doc_path, 'w') as fd:
+        ident = '.'.join(parts)
+        fd.write(f'::: {ident}')
+
+    mkdocs_gen_files.set_edit_path(full_doc_path, path)
+
+# add pages manually:
+# nav["package", "module"] = "path/to/file.md"
+
+with mkdocs_gen_files.open('reference/SUMMARY.md', 'w') as nav_file:
+    nav_file.writelines(nav.build_literate_nav())
diff --git a/dodo.py b/dodo.py
index fed7d916..3b18fce9 100644
--- a/dodo.py
+++ b/dodo.py
@@ -19,7 +19,7 @@
 """
 
 from calcipy import __pkg_name__
-from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks). # skipcq: PYL-W0614
+from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks)
 from calcipy.doit_tasks import DOIT_CONFIG_RECOMMENDED
 from calcipy.log_helpers import activate_debug_logging
 
diff --git a/mkdocs.yml b/mkdocs.yml
index 2ca3193a..e11d54f2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -23,10 +23,24 @@ theme:
 
 plugins:
   - search
+  - gen-files:
+      scripts:
+        - docs/gen_ref_nav.py
   - git-revision-date-localized:
       type: date
       fallback_to_build_date: true
       enable_creation_date: true
+  - mkdocstrings:
+      handlers:
+        python:
+          rendering:
+            docstring_section_style: spacy
+            line_length: 120
+            separate_signature: true
+            show_category_heading: true
+  - literate-nav:
+      nav_file: SUMMARY.md
+  - section-index:
 
 markdown_extensions:
   - abbr
@@ -38,6 +52,7 @@ markdown_extensions:
   - extra
   - fenced_code
   - footnotes
+  - mdx_truly_sane_lists
   - pymdownx.emoji:
       emoji_index: !!python/name:materialx.emoji.twemoji
       emoji_generator: !!python/name:materialx.emoji.to_svg
@@ -56,6 +71,9 @@ markdown_extensions:
       permalink: ⚓︎
       toc_depth: 5
 
+extra_css:
+  - css/mkdocstrings.css
+
 extra_javascript:
   - https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.2.1/tablesort.min.js
   - _javascript/tables.js
diff --git a/mypy.ini b/mypy.ini
index 8da3fa92..432f0bc8 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,25 +1,23 @@
 # Global options:
 
 [mypy]
-python_version = 3.9
+python_version = 3.8
 
 check_untyped_defs = True
 disallow_any_decorated = False
 disallow_any_explicit = False
-; disallow_any_expr = True
 disallow_any_generics = True
 disallow_any_unimported = False
 disallow_incomplete_defs = True
 disallow_subclassing_any = True
 disallow_untyped_calls = True
-; disallow_untyped_decorators = True
+disallow_untyped_decorators = True
 disallow_untyped_defs = True
+enable_error_code = ignore-without-code
 ignore_missing_imports = True
 no_implicit_optional = True
-; pretty = True
-; show_column_numbers = True
-; show_error_codes = True
-; show_error_context = True
+show_column_numbers = True
+show_error_codes = True
 strict_equality = True
 warn_redundant_casts = True
 warn_return_any = True
@@ -31,3 +29,10 @@ warn_unused_ignores = True
 ; linecount_report = coverage_mypy-txt
 html_report = releases/tests/mypy_html
 ; any_exprs_report = coverage_mypy_any-txt
+
+plugins = pydantic.mypy
+
+[pydantic-mypy]
+; https://pydantic-docs.helpmanual.io/mypy_plugin/
+init_typed = True
+warn_untyped_fields = True
diff --git a/noxfile.py b/noxfile.py
index b53b2138..a92db1e8 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -1,3 +1,12 @@
 """nox-poetry configuration file."""
 
-from calcipy.dev.noxfile import build_check, build_dist, check_security, coverage, tests  # noqa: F401
+from calcipy.dev.noxfile import build_check, build_dist, coverage, pin_dev_dependencies, tests  # noqa: F401
+
+# Ensure that non-calcipy dev-dependencies are available in Nox environments
+pin_dev_dependencies([
+    'pytest-cache-assert>=2.0.0',
+    'pytest-benchmark>=3.4.1',
+    'pytest-randomly>=3.12.0',
+    'pytest-recording>=0.12.0',
+    'pytest-subprocess>=1.4.1',
+])
diff --git a/poetry.lock b/poetry.lock
index 02ba2b86..6278bba0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -8,7 +8,7 @@ python-versions = ">=3.6.1"
 
 [[package]]
 name = "add-trailing-comma"
-version = "2.2.3"
+version = "2.3.0"
 description = "Automatically add trailing commas to calls and literals"
 category = "main"
 optional = true
@@ -19,11 +19,11 @@ tokenize-rt = ">=3.0.1"
 
 [[package]]
 name = "argcomplete"
-version = "1.12.3"
+version = "2.0.0"
 description = "Bash tab completion for argparse"
 category = "main"
 optional = true
-python-versions = "*"
+python-versions = ">=3.6"
 
 [package.extras]
 test = ["coverage", "flake8", "pexpect", "wheel"]
@@ -90,33 +90,18 @@ name = "attrs"
 version = "21.4.0"
 description = "Classes Without Boilerplate"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.extras]
 dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
 docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
 tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"]
-tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
-
-[[package]]
-name = "attrs-strict"
-version = "1.0.0"
-description = "Runtime validators for attrs"
-category = "main"
-optional = true
-python-versions = ">=3.7"
-
-[package.dependencies]
-attrs = ">=19.1.0"
-
-[package.extras]
-docs = ["Sphinx (>=3)"]
-test = ["covdefaults (>=2)", "pytest (>=6)", "pytest-cov (>=3)"]
+tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
 
 [[package]]
 name = "autoflake"
-version = "1.5.3"
+version = "1.6.1"
 description = "Removes unused imports and unused variables"
 category = "main"
 optional = true
@@ -124,7 +109,7 @@ python-versions = ">=3.7"
 
 [package.dependencies]
 pyflakes = ">=1.1.0"
-toml = ">=0.10.2"
+tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
 
 [[package]]
 name = "autopep8"
@@ -170,11 +155,11 @@ yaml = ["PyYAML"]
 
 [[package]]
 name = "beartype"
-version = "0.10.4"
+version = "0.11.0"
 description = "Unbearably fast runtime type checking in pure Python."
 category = "main"
 optional = false
-python-versions = ">=3.6.0"
+python-versions = ">=3.7.0"
 
 [package.extras]
 all = ["typing-extensions (>=3.10.0.0)"]
@@ -206,8 +191,8 @@ python-versions = ">=3.7"
 name = "black"
 version = "22.8.0"
 description = "The uncompromising code formatter."
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.6.2"
 
 [package.dependencies]
@@ -224,6 +209,22 @@ d = ["aiohttp (>=3.7.4)"]
 jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
 uvloop = ["uvloop (>=0.15.2)"]
 
+[[package]]
+name = "boltons"
+version = "21.0.0"
+description = "When they're not builtins, they're boltons."
+category = "main"
+optional = true
+python-versions = "*"
+
+[[package]]
+name = "bracex"
+version = "2.3.post1"
+description = "Bash style brace expander."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
 [[package]]
 name = "cement"
 version = "3.0.8"
@@ -245,7 +246,7 @@ yaml = ["pyYaml"]
 
 [[package]]
 name = "certifi"
-version = "2022.9.14"
+version = "2022.9.24"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "main"
 optional = false
@@ -276,19 +277,34 @@ optional = false
 python-versions = ">=3.6.0"
 
 [package.extras]
-unicode_backport = ["unicodedata2"]
+unicode-backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
 version = "8.1.3"
 description = "Composable command line interface toolkit"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
 colorama = {version = "*", markers = "platform_system == \"Windows\""}
 
+[[package]]
+name = "click-option-group"
+version = "0.5.3"
+description = "Option groups missing in Click"
+category = "main"
+optional = true
+python-versions = ">=3.6,<4"
+
+[package.dependencies]
+Click = ">=7.0,<9"
+
+[package.extras]
+docs = ["Pallets-Sphinx-Themes", "m2r", "sphinx (>=2.3,<3)"]
+tests = ["coverage (<6)", "coveralls", "pytest", "pytest-cov"]
+
 [[package]]
 name = "cloudpickle"
 version = "2.2.0"
@@ -340,14 +356,14 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
 
 [[package]]
 name = "commitizen"
-version = "2.33.1"
+version = "2.35.0"
 description = "Python commitizen client tool"
 category = "main"
 optional = true
 python-versions = ">=3.6.2,<4.0.0"
 
 [package.dependencies]
-argcomplete = ">=1.12.1,<2.0.0"
+argcomplete = ">=1.12.1,<2.1"
 charset-normalizer = ">=2.1.0,<3.0.0"
 colorama = ">=0.4.1,<0.5.0"
 decli = ">=0.5.2,<0.6.0"
@@ -355,7 +371,7 @@ jinja2 = ">=2.10.3"
 packaging = ">=19,<22"
 pyyaml = ">=3.08"
 questionary = ">=1.4.0,<2.0.0"
-termcolor = ">=1.1,<2.0"
+termcolor = {version = ">=1.1,<3", markers = "python_version >= \"3.7\""}
 tomlkit = ">=0.5.3,<1.0.0"
 typing-extensions = ">=4.0.1,<5.0.0"
 
@@ -364,7 +380,7 @@ name = "commonmark"
 version = "0.9.1"
 description = "Python parser for the CommonMark Markdown spec"
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [package.extras]
@@ -396,6 +412,9 @@ category = "main"
 optional = true
 python-versions = ">=3.7"
 
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
 [package.extras]
 toml = ["tomli"]
 
@@ -409,14 +428,14 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "cz-legacy"
-version = "0.1.6"
+version = "0.1.7"
 description = "Extends Conventional Commits Change Types with User-Defined Legacy Types for Commitizen"
 category = "main"
 optional = true
-python-versions = ">=3.6.1,<4.0.0"
+python-versions = ">=3.6.2,<4.0.0"
 
 [package.dependencies]
-commitizen = ">=2"
+commitizen = ">=2.0.0"
 
 [[package]]
 name = "darglint"
@@ -443,26 +462,34 @@ optional = false
 python-versions = ">=3.5"
 
 [[package]]
-name = "dictdiffer"
-version = "0.9.0"
-description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
-category = "main"
-optional = true
-python-versions = "*"
+name = "deepdiff6"
+version = "6.2.0"
+description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ordered-set = ">=4.0.2,<4.2.0"
 
 [package.extras]
-all = ["Sphinx (>=3)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "tox (>=3.7.0)"]
-docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"]
-numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"]
-tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"]
+cli = ["clevercsv (==0.7.4)", "click (==8.1.3)", "pyyaml (==6.0)", "toml (==0.10.2)"]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+description = "XML bomb protection for Python stdlib modules"
+category = "main"
+optional = true
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "diff-cover"
-version = "6.5.1"
+version = "7.0.1"
 description = "Run coverage and linting reports on diffs"
 category = "main"
 optional = true
-python-versions = ">=3.6.2,<4.0.0"
+python-versions = ">=3.7.2,<4.0.0"
 
 [package.dependencies]
 chardet = ">=3.0.0"
@@ -518,14 +545,6 @@ untokenize = ">=0.1.1,<0.2.0"
 [package.extras]
 tomli = ["tomli (<2.0.0)"]
 
-[[package]]
-name = "docstring-parser"
-version = "0.7.3"
-description = ""
-category = "main"
-optional = true
-python-versions = "~=3.5"
-
 [[package]]
 name = "doit"
 version = "0.36.0"
@@ -553,20 +572,23 @@ python-versions = "*"
 name = "exceptiongroup"
 version = "1.0.0rc9"
 description = "Backport of PEP 654 (exception groups)"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7"
 
 [package.extras]
 test = ["pytest (>=6)"]
 
 [[package]]
-name = "falcon"
-version = "2.0.0"
-description = "An unladen web framework for building APIs and app backends."
+name = "face"
+version = "20.1.1"
+description = "A command-line application framework (and CLI parser). Friendly for users, full-featured for developers."
 category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = "*"
+
+[package.dependencies]
+boltons = ">=20.0.0"
 
 [[package]]
 name = "fancycompleter"
@@ -706,7 +728,7 @@ flake8 = ">=3.5,<6"
 
 [[package]]
 name = "flake8-bugbear"
-version = "22.9.11"
+version = "22.9.23"
 description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
 category = "main"
 optional = true
@@ -782,17 +804,16 @@ pydocstyle = ">=2.1"
 
 [[package]]
 name = "flake8-eradicate"
-version = "1.3.0"
+version = "1.4.0"
 description = "Flake8 plugin to find commented out code"
 category = "main"
 optional = true
-python-versions = ">=3.6,<4.0"
+python-versions = ">=3.7,<4.0"
 
 [package.dependencies]
 attrs = "*"
 eradicate = ">=2.0,<3.0"
 flake8 = ">=3.5,<6"
-setuptools = "*"
 
 [[package]]
 name = "flake8-expression-complexity"
@@ -878,6 +899,17 @@ flake8 = ">=3.0"
 [package.extras]
 test = ["pytest", "testfixtures"]
 
+[[package]]
+name = "flake8-pie"
+version = "0.16.0"
+description = "A flake8 extension that implements misc. lints"
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+typing_extensions = "*"
+
 [[package]]
 name = "flake8-plone-hasattr"
 version = "0.2.post0"
@@ -1001,6 +1033,17 @@ python-versions = "*"
 flake8 = "*"
 six = "*"
 
+[[package]]
+name = "flake8-typing-imports"
+version = "1.13.0"
+description = "flake8 plugin which checks that typing imports are properly guarded"
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+flake8 = ">=5.0"
+
 [[package]]
 name = "flake8-variables-names"
 version = "0.0.5"
@@ -1011,7 +1054,7 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "fonttools"
-version = "4.37.2"
+version = "4.37.3"
 description = "Tools to manipulate font files"
 category = "dev"
 optional = false
@@ -1076,25 +1119,40 @@ python-versions = ">=3.7"
 gitdb = ">=4.0.1,<5"
 
 [[package]]
-name = "hug"
-version = "2.6.1"
-description = "A Python framework that makes developing APIs as simple as possible, but no simpler."
+name = "glom"
+version = "22.1.0"
+description = "A declarative object transformer and formatter, for conglomerating nested data."
 category = "main"
 optional = true
-python-versions = ">=3.5"
+python-versions = "*"
 
 [package.dependencies]
-falcon = "2.0.0"
-requests = "*"
+attrs = "*"
+boltons = ">=19.3.0"
+face = ">=20.1.0"
+
+[package.extras]
+yaml = ["PyYAML"]
 
 [[package]]
-name = "hypothesis"
-version = "6.54.5"
-description = "A library for property-based testing"
+name = "griffe"
+version = "0.22.2"
+description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
 category = "main"
 optional = true
 python-versions = ">=3.7"
 
+[package.extras]
+async = ["aiofiles (>=0.7,<1.0)"]
+
+[[package]]
+name = "hypothesis"
+version = "6.54.6"
+description = "A library for property-based testing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
 [package.dependencies]
 attrs = ">=19.2.0"
 black = {version = ">=19.10b0", optional = true, markers = "extra == \"cli\""}
@@ -1142,8 +1200,8 @@ python-versions = ">=3.5"
 name = "implements"
 version = "0.3.0"
 description = "Pythonic interfaces using decorators"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = "*"
 
 [package.dependencies]
@@ -1165,12 +1223,27 @@ docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
 perf = ["ipython"]
 testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
 
+[[package]]
+name = "importlib-resources"
+version = "5.9.0"
+description = "Read resources from Python packages"
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
+testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
 [[package]]
 name = "iniconfig"
 version = "1.1.1"
 description = "iniconfig: brain-dead simple config-ini parsing"
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -1183,9 +1256,9 @@ python-versions = ">=3.6.1,<4.0"
 
 [package.extras]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
-pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
 plugins = ["setuptools"]
-requirements_deprecated_finder = ["pip-api", "pipreqs"]
+requirements-deprecated-finder = ["pip-api", "pipreqs"]
 
 [[package]]
 name = "Jinja2"
@@ -1201,6 +1274,24 @@ MarkupSafe = ">=2.0"
 [package.extras]
 i18n = ["Babel (>=2.7)"]
 
+[[package]]
+name = "jsonschema"
+version = "4.16.0"
+description = "An implementation of JSON Schema validation for Python"
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""}
+pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"]
+
 [[package]]
 name = "kiwisolver"
 version = "1.4.4"
@@ -1276,22 +1367,6 @@ html5 = ["html5lib"]
 htmlsoup = ["BeautifulSoup4"]
 source = ["Cython (>=0.29.7)"]
 
-[[package]]
-name = "Mako"
-version = "1.2.2"
-description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
-category = "main"
-optional = true
-python-versions = ">=3.7"
-
-[package.dependencies]
-MarkupSafe = ">=0.9.2"
-
-[package.extras]
-babel = ["Babel"]
-lingua = ["lingua"]
-testing = ["pytest"]
-
 [[package]]
 name = "mando"
 version = "0.6.4"
@@ -1304,15 +1379,15 @@ python-versions = "*"
 six = "*"
 
 [package.extras]
-restructuredText = ["rst2ansi"]
+restructuredtext = ["rst2ansi"]
 
 [[package]]
 name = "Markdown"
-version = "3.4.1"
+version = "3.3.7"
 description = "Python implementation of Markdown."
 category = "main"
 optional = true
-python-versions = ">=3.7"
+python-versions = ">=3.6"
 
 [package.dependencies]
 importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
@@ -1356,6 +1431,17 @@ category = "main"
 optional = true
 python-versions = ">=3.6"
 
+[[package]]
+name = "mdx-truly-sane-lists"
+version = "1.3"
+description = "Extension for Python-Markdown that makes lists truly sane. Custom indents for nested lists and fix for messy linebreaks."
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+Markdown = ">=2.6"
+
 [[package]]
 name = "mergedeep"
 version = "1.3.4"
@@ -1366,27 +1452,50 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "mkdocs"
-version = "1.3.0"
+version = "1.4.0"
 description = "Project documentation with Markdown."
 category = "main"
 optional = true
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
-click = ">=3.3"
+click = ">=7.0"
 ghp-import = ">=1.0"
-importlib-metadata = ">=4.3"
-Jinja2 = ">=2.10.2"
-Markdown = ">=3.2.1"
+importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
+Jinja2 = ">=2.11.1"
+Markdown = ">=3.2.1,<3.4"
 mergedeep = ">=1.3.4"
 packaging = ">=20.5"
-PyYAML = ">=3.10"
+PyYAML = ">=5.1"
 pyyaml-env-tag = ">=0.1"
 watchdog = ">=2.0"
 
 [package.extras]
 i18n = ["babel (>=2.9.0)"]
 
+[[package]]
+name = "mkdocs-autorefs"
+version = "0.4.1"
+description = "Automatically link across pages in MkDocs."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+Markdown = ">=3.3"
+mkdocs = ">=1.1"
+
+[[package]]
+name = "mkdocs-gen-files"
+version = "0.4.0"
+description = "MkDocs plugin to programmatically generate documentation pages during the build"
+category = "main"
+optional = true
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+mkdocs = ">=1.0.3,<2.0.0"
+
 [[package]]
 name = "mkdocs-git-revision-date-localized-plugin"
 version = "1.1.0"
@@ -1400,9 +1509,32 @@ babel = ">=2.7.0"
 GitPython = "*"
 mkdocs = ">=1.0"
 
+[[package]]
+name = "mkdocs-include-markdown-plugin"
+version = "3.8.1"
+description = "Mkdocs Markdown includer plugin."
+category = "main"
+optional = true
+python-versions = ">=3.6"
+
+[package.extras]
+dev = ["bump2version (==1.0.1)", "flake8 (==3.9.2)", "flake8-implicit-str-concat (==0.2.0)", "flake8-print (==4.0.0)", "isort (==5.9.1)", "mdpo (==0.3.61)", "mkdocs (==1.3.1)", "pre-commit (==2.13.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pyupgrade (==2.19.4)", "yamllint (==1.26.1)"]
+test = ["mkdocs (==1.3.1)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)"]
+
+[[package]]
+name = "mkdocs-literate-nav"
+version = "0.5.0"
+description = "MkDocs plugin to specify the navigation in Markdown instead of YAML"
+category = "main"
+optional = true
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+mkdocs = ">=1.0.3,<2.0.0"
+
 [[package]]
 name = "mkdocs-material"
-version = "8.5.1"
+version = "8.5.3"
 description = "Documentation that simply works"
 category = "main"
 optional = true
@@ -1425,6 +1557,51 @@ category = "main"
 optional = true
 python-versions = ">=3.6"
 
+[[package]]
+name = "mkdocs-section-index"
+version = "0.3.4"
+description = "MkDocs plugin to allow clickable sections that lead to an index page"
+category = "main"
+optional = true
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+mkdocs = ">=1.1,<2.0"
+
+[[package]]
+name = "mkdocstrings"
+version = "0.19.0"
+description = "Automatic documentation from sources, for MkDocs."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+Jinja2 = ">=2.11.1"
+Markdown = ">=3.3"
+MarkupSafe = ">=1.1"
+mkdocs = ">=1.2"
+mkdocs-autorefs = ">=0.3.1"
+mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
+pymdown-extensions = ">=6.3"
+
+[package.extras]
+crystal = ["mkdocstrings-crystal (>=0.3.4)"]
+python = ["mkdocstrings-python (>=0.5.2)"]
+python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "0.7.1"
+description = "A Python handler for mkdocstrings."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+griffe = ">=0.11.1"
+mkdocstrings = ">=0.19"
+
 [[package]]
 name = "mr-proper"
 version = "0.0.7"
@@ -1442,17 +1619,17 @@ stdlib-list = ">=0.5.0"
 name = "multidict"
 version = "6.0.2"
 description = "multidict implementation"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7"
 
 [[package]]
 name = "mypy"
-version = "0.971"
+version = "0.981"
 description = "Optional static typing for Python"
 category = "main"
 optional = true
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
 mypy-extensions = ">=0.4.3"
@@ -1469,7 +1646,7 @@ name = "mypy-extensions"
 version = "0.4.3"
 description = "Experimental type system extensions for programs checked with the mypy typechecker."
 category = "main"
-optional = true
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -1499,7 +1676,7 @@ py = ">=1.4,<2.0.0"
 virtualenv = ">=14"
 
 [package.extras]
-tox_to_nox = ["jinja2", "tox"]
+tox-to-nox = ["jinja2", "tox"]
 
 [[package]]
 name = "nox-poetry"
@@ -1522,6 +1699,17 @@ category = "main"
 optional = false
 python-versions = ">=3.8"
 
+[[package]]
+name = "ordered-set"
+version = "4.1.0"
+description = "An OrderedSet is a custom MutableSet that remembers its order, so that every"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+dev = ["black", "mypy", "pytest"]
+
 [[package]]
 name = "packaging"
 version = "21.3"
@@ -1535,7 +1723,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
 [[package]]
 name = "pandas"
-version = "1.4.4"
+version = "1.5.0"
 description = "Powerful data structures for data analysis, time series, and statistics"
 category = "main"
 optional = false
@@ -1543,9 +1731,7 @@ python-versions = ">=3.8"
 
 [package.dependencies]
 numpy = [
-    {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
-    {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""},
-    {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""},
+    {version = ">=1.20.3", markers = "python_version < \"3.10\""},
     {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
 ]
 python-dateutil = ">=2.8.1"
@@ -1571,7 +1757,7 @@ name = "pathspec"
 version = "0.10.1"
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.7"
 
 [[package]]
@@ -1600,30 +1786,12 @@ funcsigs = ["funcsigs"]
 testing = ["funcsigs", "pytest"]
 
 [[package]]
-name = "pdocs"
-version = "1.1.1"
-description = "A simple program and library to auto generate API documentation for Python modules."
-category = "main"
-optional = true
-python-versions = ">=3.6,<4.0"
-
-[package.dependencies]
-docstring_parser = ">=0.7.2,<0.8.0"
-hug = ">=2.6,<3.0"
-Mako = ">=1.1,<2.0"
-Markdown = ">=3.0.0,<4.0.0"
-
-[[package]]
-name = "pendulum"
-version = "2.1.2"
-description = "Python datetimes made easy"
+name = "peewee"
+version = "3.15.3"
+description = "a little orm"
 category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[package.dependencies]
-python-dateutil = ">=2.6,<3.0"
-pytzdata = ">=2020.1"
+python-versions = "*"
 
 [[package]]
 name = "pep8-naming"
@@ -1668,6 +1836,14 @@ python-versions = ">=3.5"
 pip = ">=9"
 terminaltables = "*"
 
+[[package]]
+name = "pkgutil_resolve_name"
+version = "1.3.10"
+description = "Resolve a name to an object."
+category = "main"
+optional = true
+python-versions = ">=3.6"
+
 [[package]]
 name = "platformdirs"
 version = "2.5.2"
@@ -1685,7 +1861,7 @@ name = "pluggy"
 version = "1.0.0"
 description = "plugin and hook calling mechanisms for python"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6"
 
 [package.extras]
@@ -1732,28 +1908,20 @@ click = ">=8.0.0,<9.0.0"
 future = ">=0.18.2,<0.19.0"
 six = ">=1.15.0,<2.0.0"
 
-[[package]]
-name = "punq"
-version = "0.6.2"
-description = "An IOC Container for Python 3.6+"
-category = "main"
-optional = true
-python-versions = ">=3.6.2,<4.0.0"
-
 [[package]]
 name = "py"
 version = "1.11.0"
 description = "library with cross-python path, ini-parsing, io, code, log facilities"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "py-cpuinfo"
 version = "8.0.0"
 description = "Get CPU info with pure Python 2 & 3"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = "*"
 
 [[package]]
@@ -1806,7 +1974,7 @@ name = "Pygments"
 version = "2.13.0"
 description = "Pygments is a syntax highlighting package written in Python."
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6"
 
 [package.extras]
@@ -1814,14 +1982,14 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pylint"
-version = "2.15.2"
+version = "2.15.3"
 description = "python code static checker"
 category = "main"
 optional = true
 python-versions = ">=3.7.2"
 
 [package.dependencies]
-astroid = ">=2.12.9,<=2.14.0-dev0"
+astroid = ">=2.12.10,<=2.14.0-dev0"
 colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
 dill = ">=0.2"
 isort = ">=4.2.5,<6"
@@ -1859,11 +2027,11 @@ diagrams = ["jinja2", "railroad-diagrams"]
 
 [[package]]
 name = "pyrate-limiter"
-version = "2.8.1"
+version = "2.8.2"
 description = "Python Rate-Limiter using Leaky-Bucket Algorithm"
 category = "main"
 optional = false
-python-versions = ">=3.6.2,<4.0.0"
+python-versions = ">=3.7,<4.0"
 
 [package.extras]
 all = ["filelock (>=3.0)", "redis (>=3.3,<4.0)", "redis-py-cluster (>=2.1.3,<3.0.0)"]
@@ -1885,12 +2053,20 @@ category = "main"
 optional = true
 python-versions = "*"
 
+[[package]]
+name = "pyrsistent"
+version = "0.18.1"
+description = "Persistent/Functional/Immutable data structures"
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
 [[package]]
 name = "pytest"
 version = "7.1.3"
 description = "pytest: simple powerful testing with Python"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
@@ -1909,8 +2085,8 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
 name = "pytest-benchmark"
 version = "3.4.1"
 description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.dependencies]
@@ -1924,22 +2100,37 @@ histogram = ["pygal", "pygaljs"]
 
 [[package]]
 name = "pytest-cache-assert"
-version = "2.0.0"
+version = "3.0.0"
 description = "Cache assertion data to simplify regression testing of complex serializable data"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7.6,<4.0.0"
 
 [package.dependencies]
-attrs-strict = ">=0.2"
+arrow = ">=1.2.3"
 beartype = ">=0.10.0"
-dictdiffer = ">=0.9"
+deepdiff6 = ">=6.2.0"
 implements = ">=0.3.0"
-pendulum = ">=2.1.2"
-punq = ">=0.6.2"
+pydantic = ">=1.10.2"
 pytest = ">=5.0.0"
+StrEnum = ">=0.4.8"
 ujson = ">=5.1.0"
 
+[[package]]
+name = "pytest-cov"
+version = "3.0.0"
+description = "Pytest plugin for measuring coverage."
+category = "main"
+optional = true
+python-versions = ">=3.6"
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+
 [[package]]
 name = "pytest-html"
 version = "3.1.1"
@@ -1967,8 +2158,8 @@ pytest = ">=3.0.0,<8.0.0"
 name = "pytest-randomly"
 version = "3.12.0"
 description = "Pytest plugin to randomly order tests and control random.seed."
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
@@ -1979,8 +2170,8 @@ pytest = "*"
 name = "pytest-recording"
 version = "0.12.1"
 description = "A pytest plugin that allows you recording of network interactions via VCR.py"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.5"
 
 [package.dependencies]
@@ -1992,8 +2183,8 @@ vcrpy = ">=2.0.1"
 name = "pytest-subprocess"
 version = "1.4.1"
 description = "A plugin to fake subprocess for pytest"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
@@ -2027,7 +2218,7 @@ python-versions = ">=3.6"
 all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"]
 msgpack = ["msgpack"]
 pyyaml = ["PyYAML"]
-"ruamel.yaml" = ["ruamel.yaml (>=0.17)"]
+ruamel-yaml = ["ruamel.yaml (>=0.17)"]
 toml = ["toml"]
 yaml = ["ruamel.yaml (>=0.17)"]
 
@@ -2042,6 +2233,20 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 [package.dependencies]
 six = ">=1.5"
 
+[[package]]
+name = "python-lsp-jsonrpc"
+version = "1.0.0"
+description = "JSON RPC 2.0 server library"
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+ujson = ">=3.0.0"
+
+[package.extras]
+test = ["coverage", "pycodestyle", "pyflakes", "pylint", "pytest", "pytest-cov"]
+
 [[package]]
 name = "pytz"
 version = "2022.2.1"
@@ -2050,17 +2255,9 @@ category = "main"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "pytzdata"
-version = "2020.1"
-description = "The Olson timezone database for Python."
-category = "main"
-optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
 [[package]]
 name = "pyupgrade"
-version = "2.38.0"
+version = "2.38.2"
 description = "A tool to automatically upgrade syntax for newer versions."
 category = "main"
 optional = true
@@ -2131,7 +2328,7 @@ urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "requirements-parser"
@@ -2149,7 +2346,7 @@ name = "rich"
 version = "12.5.1"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.6.3,<4.0.0"
 
 [package.dependencies]
@@ -2160,9 +2357,63 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9
 [package.extras]
 jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
 
+[[package]]
+name = "ruamel.yaml"
+version = "0.17.21"
+description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
+category = "main"
+optional = true
+python-versions = ">=3"
+
+[package.dependencies]
+"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}
+
+[package.extras]
+docs = ["ryd"]
+jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
+
+[[package]]
+name = "ruamel.yaml.clib"
+version = "0.2.6"
+description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
+category = "main"
+optional = true
+python-versions = ">=3.5"
+
+[[package]]
+name = "semgrep"
+version = "0.115.0"
+description = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=21.3,<22.0"
+boltons = ">=21.0,<22.0"
+click = ">=8.1,<9.0"
+click-option-group = ">=0.5,<1.0"
+colorama = ">=0.4.0,<0.5.0"
+defusedxml = ">=0.7.1,<0.8.0"
+glom = ">=22.1,<23.0"
+jsonschema = ">=4.6,<5.0"
+packaging = ">=21.0,<22.0"
+peewee = ">=3.14,<4.0"
+python-lsp-jsonrpc = ">=1.0.0,<1.1.0"
+requests = ">=2.22,<3.0"
+"ruamel.yaml" = ">=0.16.0,<0.18"
+tomli = ">=2.0.1,<2.1.0"
+tqdm = ">=4.46,<5.0"
+typing-extensions = ">=4.2,<5.0"
+urllib3 = ">=1.26,<2.0"
+wcmatch = ">=8.3,<9.0"
+
+[package.extras]
+experiments = ["jsonnet (>=0.18,<1.0)"]
+
 [[package]]
 name = "setuptools"
-version = "65.3.0"
+version = "65.4.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "main"
 optional = false
@@ -2177,7 +2428,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
 name = "setuptools-scm"
 version = "7.0.5"
 description = "the blessed package to manage your versions by scm tags"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=3.7"
 
@@ -2219,13 +2470,13 @@ python-versions = "*"
 name = "sortedcontainers"
 version = "2.4.0"
 description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = "*"
 
 [[package]]
 name = "sqlparse"
-version = "0.4.2"
+version = "0.4.3"
 description = "A non-validating SQL parser."
 category = "main"
 optional = true
@@ -2253,6 +2504,19 @@ python-versions = ">=3.8"
 [package.dependencies]
 pbr = ">=2.0.0,<2.1.0 || >2.1.0"
 
+[[package]]
+name = "StrEnum"
+version = "0.4.8"
+description = "An Enum that inherits from str."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"]
+release = ["twine"]
+test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
+
 [[package]]
 name = "sty"
 version = "1.0.4"
@@ -2274,11 +2538,14 @@ widechars = ["wcwidth"]
 
 [[package]]
 name = "termcolor"
-version = "1.1.0"
-description = "ANSII Color formatting for output in terminal."
+version = "2.0.1"
+description = "ANSI color formatting for output in terminal"
 category = "main"
 optional = true
-python-versions = "*"
+python-versions = ">=3.7"
+
+[package.extras]
+tests = ["pytest", "pytest-cov"]
 
 [[package]]
 name = "terminaltables"
@@ -2320,6 +2587,23 @@ category = "main"
 optional = true
 python-versions = ">=3.6,<4.0"
 
+[[package]]
+name = "tqdm"
+version = "4.64.1"
+description = "Fast, Extensible Progress Meter"
+category = "main"
+optional = true
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["py-make (>=0.1.0)", "twine", "wheel"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
 [[package]]
 name = "transitions"
 version = "0.9.0"
@@ -2335,6 +2619,19 @@ six = "*"
 diagrams = ["pygraphviz"]
 test = ["pytest"]
 
+[[package]]
+name = "tryceratops"
+version = "1.1.0"
+description = "A linter to manage your exception like a PRO!"
+category = "main"
+optional = true
+python-versions = ">=3.8,<4.0"
+
+[package.dependencies]
+click = ">=7"
+rich = ">=10.14.0"
+toml = ">=0.10.2"
+
 [[package]]
 name = "types-setuptools"
 version = "65.3.0"
@@ -2368,12 +2665,12 @@ name = "ujson"
 version = "5.5.0"
 description = "Ultra fast JSON encoder and decoder for Python"
 category = "main"
-optional = true
+optional = false
 python-versions = ">=3.7"
 
 [[package]]
 name = "unimport"
-version = "0.11.3"
+version = "0.12.1"
 description = "A linter, formatter for finding and removing unused import statements."
 category = "main"
 optional = true
@@ -2388,8 +2685,8 @@ pathspec = {version = ">=0.10.1,<1", markers = "python_version >= \"3.7\""}
 toml = ">=0.9.0,<1"
 
 [package.extras]
-dev = ["pytest (==6.2.4)", "pytest-cov (==2.12.1)", "semantic-version (==2.8.5)"]
 docs = ["mkdocs (==1.3.0)", "mkdocs-git-revision-date-localized-plugin (==1.0.1)", "mkdocs-markdownextradata-plugin (==0.2.5)", "mkdocs-material (==8.3.6)", "mkdocs-minify-plugin (==0.5.0)"]
+test = ["pytest (==6.2.4)", "pytest-cov (==2.12.1)", "semantic-version (==2.8.5)"]
 
 [[package]]
 name = "untokenize"
@@ -2416,8 +2713,8 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 name = "vcrpy"
 version = "4.2.1"
 description = "Automatically mock your HTTP interactions to simplify and speed up testing"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
@@ -2445,7 +2742,7 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7
 
 [[package]]
 name = "vulture"
-version = "2.5"
+version = "2.6"
 description = "Find dead code"
 category = "main"
 optional = true
@@ -2465,6 +2762,17 @@ python-versions = ">=3.6"
 [package.extras]
 watchmedo = ["PyYAML (>=3.10)"]
 
+[[package]]
+name = "wcmatch"
+version = "8.4.1"
+description = "Wildcard/glob file name matcher."
+category = "main"
+optional = true
+python-versions = ">=3.7"
+
+[package.dependencies]
+bracex = ">=2.1.1"
+
 [[package]]
 name = "wcwidth"
 version = "0.2.5"
@@ -2497,7 +2805,7 @@ name = "wrapt"
 version = "1.14.1"
 description = "Module for decorators, wrappers and monkey patching."
 category = "main"
-optional = true
+optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
@@ -2530,8 +2838,8 @@ setuptools = "*"
 name = "yarl"
 version = "1.8.1"
 description = "Yet another URL library"
-category = "main"
-optional = true
+category = "dev"
+optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
@@ -2551,15 +2859,15 @@ docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)
 testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
 
 [extras]
-commitizen_legacy = ["cz_legacy"]
-dev = ["better-exceptions", "commitizen", "mkdocs-git-revision-date-localized-plugin", "mkdocs-material", "pdbpp", "pdocs", "pyupgrade"]
-lint = ["absolufy-imports", "add-trailing-comma", "autoflake", "autopep8", "cohesion", "darglint", "dlint", "docformatter", "flake8", "flake8-2020", "flake8-aaa", "flake8-annotations", "flake8-assertive", "flake8-bandit", "flake8-blind-except", "flake8-breakpoint", "flake8-broken-line", "flake8-bugbear", "flake8-builtins", "flake8-cognitive-complexity", "flake8-comprehensions", "flake8-debugger", "flake8-docstrings", "flake8-eradicate", "flake8-expression-complexity", "flake8-fixme", "flake8-functions", "flake8-isort", "flake8-logging-format", "flake8-markdown", "flake8-pep3101", "flake8-plone-hasattr", "flake8-print", "flake8-printf-formatting", "flake8-pytest-style", "flake8-quotes", "flake8-return", "flake8-simplify", "flake8-SQL", "flake8-string-format", "flake8-tuple", "flake8-variables-names", "isort", "licensecheck", "pandas-vet", "pep8-naming", "pip-check", "proselint", "pylint", "unimport", "vulture", "xenon", "yamllint"]
-test = ["coverage", "diff-cover", "hypothesis", "lxml", "mypy", "nox-poetry", "pytest", "pytest-benchmark", "pytest-cache-assert", "pytest-html", "pytest-randomly", "pytest-recording", "pytest-subprocess", "pytest-watcher"]
+commitizen-legacy = ["cz_legacy"]
+dev = ["better-exceptions", "commitizen", "mdx_truly_sane_lists", "mkdocs-gen-files", "mkdocs-git-revision-date-localized-plugin", "mkdocs-include-markdown-plugin", "mkdocs-literate-nav", "mkdocs-material", "mkdocs-section-index", "mkdocstrings", "pdbpp", "pyupgrade"]
+lint = ["absolufy-imports", "add-trailing-comma", "autoflake", "autopep8", "cohesion", "darglint", "dlint", "docformatter", "flake8", "flake8-2020", "flake8-aaa", "flake8-annotations", "flake8-assertive", "flake8-bandit", "flake8-blind-except", "flake8-breakpoint", "flake8-broken-line", "flake8-bugbear", "flake8-builtins", "flake8-cognitive-complexity", "flake8-comprehensions", "flake8-debugger", "flake8-docstrings", "flake8-eradicate", "flake8-expression-complexity", "flake8-fixme", "flake8-functions", "flake8-isort", "flake8-logging-format", "flake8-markdown", "flake8-pep3101", "flake8-pie", "flake8-plone-hasattr", "flake8-print", "flake8-printf-formatting", "flake8-pytest-style", "flake8-quotes", "flake8-return", "flake8-simplify", "flake8-SQL", "flake8-string-format", "flake8-tuple", "flake8-typing-imports", "flake8-variables-names", "isort", "licensecheck", "pandas-vet", "pep8-naming", "pip-check", "proselint", "pylint", "semgrep", "tryceratops", "unimport", "vulture", "xenon", "yamllint"]
+test = ["diff-cover", "lxml", "mypy", "nox-poetry", "pytest", "pytest-cov", "pytest-html", "pytest-watcher"]
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8.4"
-content-hash = "1824ce24b7ab7db6b32a3de3baf65e6961de2ee23e9ab1a604584cb2221fa115"
+content-hash = "bc13f73dc4adb5df9c8effcae3d053e9054e0a8563a8d9a0174dabab57ce6cbe"
 
 [metadata.files]
 absolufy-imports = [
@@ -2567,12 +2875,12 @@ absolufy-imports = [
     {file = "absolufy_imports-0.3.1.tar.gz", hash = "sha256:c90638a6c0b66826d1fb4880ddc20ef7701af34192c94faf40b95d32b59f9793"},
 ]
 add-trailing-comma = [
-    {file = "add_trailing_comma-2.2.3-py2.py3-none-any.whl", hash = "sha256:e8d928f8a31fc06140fa99802df0789b5f58c9b45b2a32252d7d4b59c46a9d03"},
-    {file = "add_trailing_comma-2.2.3.tar.gz", hash = "sha256:d118070060a40be1f6b7f0bcc2f7be3b9f88d22eb9511b5d7a6535fa9fa74571"},
+    {file = "add_trailing_comma-2.3.0-py2.py3-none-any.whl", hash = "sha256:a63a495bae8f357c71792429cb0829fc7a7f93be989f43f9520a6fe08c94d035"},
+    {file = "add_trailing_comma-2.3.0.tar.gz", hash = "sha256:9ad133414966018bae5904ad9a116bdf1715c105815f374e103b0de15268dae0"},
 ]
 argcomplete = [
-    {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
-    {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
+    {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"},
+    {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"},
 ]
 arrow = [
     {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"},
@@ -2598,13 +2906,9 @@ attrs = [
     {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
     {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
-attrs-strict = [
-    {file = "attrs_strict-1.0.0-py3-none-any.whl", hash = "sha256:c7a4d9e4521d6706906ea96204f05b95acd45c3f0fc0130837f0a33bc9678c0f"},
-    {file = "attrs_strict-1.0.0.tar.gz", hash = "sha256:6326c1e51c4fd2fffc9dd607dec06c31add64eedbd37a651ea88f663a37c3f18"},
-]
 autoflake = [
-    {file = "autoflake-1.5.3-py2.py3-none-any.whl", hash = "sha256:90eb8d3f625bd72068eb670338ea7efcddbc5c6e822d3601e3dc9404c06ea8da"},
-    {file = "autoflake-1.5.3.tar.gz", hash = "sha256:44f7d7eb2c1c49505b513c0e93a5dfd3f7b4218283f50c5ca0af4df6b975d470"},
+    {file = "autoflake-1.6.1-py2.py3-none-any.whl", hash = "sha256:dfef4c851fb07e6111f9115d3e7c8c52d8564cbf71c12ade2d8b8a2a7b8bd176"},
+    {file = "autoflake-1.6.1.tar.gz", hash = "sha256:72bce741144ef6db26005d47dba242c1fd6a91ea53f7f4c5a90ad4b051e394c2"},
 ]
 autopep8 = [
     {file = "autopep8-1.7.0-py2.py3-none-any.whl", hash = "sha256:6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087"},
@@ -2619,8 +2923,8 @@ bandit = [
     {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"},
 ]
 beartype = [
-    {file = "beartype-0.10.4-py3-none-any.whl", hash = "sha256:1a65453bc25b39979bf5ad65fe5e73350551282956456d828fb5783468649e3e"},
-    {file = "beartype-0.10.4.tar.gz", hash = "sha256:24ec69f6a7f4e6e97af403d08de270def3248518060327095d23b1c4df64bf2a"},
+    {file = "beartype-0.11.0-py3-none-any.whl", hash = "sha256:d1ed5f97edebe909385190fac95ef9af3daf233eb1a5cd08b88b0d8260708ad7"},
+    {file = "beartype-0.11.0.tar.gz", hash = "sha256:3854b50eaaa98bb89490be57e73c69c777a0f304574e7043ac7da98ac6a735a6"},
 ]
 better-exceptions = [
     {file = "better_exceptions-0.3.3-py3-none-any.whl", hash = "sha256:9c70b1c61d5a179b84cd2c9d62c3324b667d74286207343645ed4306fdaad976"},
@@ -2656,13 +2960,21 @@ black = [
     {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
     {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
 ]
+boltons = [
+    {file = "boltons-21.0.0-py2.py3-none-any.whl", hash = "sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b"},
+    {file = "boltons-21.0.0.tar.gz", hash = "sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13"},
+]
+bracex = [
+    {file = "bracex-2.3.post1-py3-none-any.whl", hash = "sha256:351b7f20d56fb9ea91f9b9e9e7664db466eb234188c175fd943f8f755c807e73"},
+    {file = "bracex-2.3.post1.tar.gz", hash = "sha256:e7b23fc8b2cd06d3dec0692baabecb249dda94e06a617901ff03a6c56fd71693"},
+]
 cement = [
     {file = "cement-3.0.8-py3-none-any.whl", hash = "sha256:6bea33b89d3329c405ca4119b7da402b53b7c2c402e98cda371e882d5a2f485e"},
     {file = "cement-3.0.8.tar.gz", hash = "sha256:ad11a694665e2ad28457c560494f4f8c36a25fc58e500d608a9d91e04e1d3093"},
 ]
 certifi = [
-    {file = "certifi-2022.9.14-py3-none-any.whl", hash = "sha256:e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516"},
-    {file = "certifi-2022.9.14.tar.gz", hash = "sha256:36973885b9542e6bd01dea287b2b4b3b21236307c56324fcc3f1160f2d655ed5"},
+    {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+    {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
 ]
 cfgv = [
     {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
@@ -2680,6 +2992,10 @@ click = [
     {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
     {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
 ]
+click-option-group = [
+    {file = "click-option-group-0.5.3.tar.gz", hash = "sha256:a6e924f3c46b657feb5b72679f7e930f8e5b224b766ab35c91ae4019b4e0615e"},
+    {file = "click_option_group-0.5.3-py3-none-any.whl", hash = "sha256:9653a2297357335d7325a1827e71ac1245d91c97d959346a7decabd4a52d5354"},
+]
 cloudpickle = [
     {file = "cloudpickle-2.2.0-py3-none-any.whl", hash = "sha256:7428798d5926d8fcbfd092d18d01a2a03daf8237d8fcdc8095d256b8490796f0"},
     {file = "cloudpickle-2.2.0.tar.gz", hash = "sha256:3f4219469c55453cfe4737e564b67c2a149109dabf7f242478948b895f61106f"},
@@ -2699,8 +3015,8 @@ colorlog = [
     {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"},
 ]
 commitizen = [
-    {file = "commitizen-2.33.1-py3-none-any.whl", hash = "sha256:d2b61023954446ae6829378422e68f46f82b6d52689b3dc3942c193c3d31bee5"},
-    {file = "commitizen-2.33.1.tar.gz", hash = "sha256:b4c032516bd4afc446af5a15bb8a1d5ac1011af4314e3f3ffc5f0fe0363249e4"},
+    {file = "commitizen-2.35.0-py3-none-any.whl", hash = "sha256:ced3e161decf290c5263373dda440040405ed7f8b701b463d81e2ecc1e31d92c"},
+    {file = "commitizen-2.35.0.tar.gz", hash = "sha256:34a7462c2279fc4e22929c03a9bb89242ab45dc501c0f17d1174e65c7fb9d793"},
 ]
 commonmark = [
     {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
@@ -2834,8 +3150,8 @@ cycler = [
     {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
 ]
 cz-legacy = [
-    {file = "cz_legacy-0.1.6-py3-none-any.whl", hash = "sha256:6b1bc282d14a3834dc6321f7e2d97dc8e79512a0896bfa71637b6168fa17552d"},
-    {file = "cz_legacy-0.1.6.tar.gz", hash = "sha256:7c966d26a14afcbc639a89821c0274a7b1e77416db40ab459e373c43af1e6d14"},
+    {file = "cz_legacy-0.1.7-py3-none-any.whl", hash = "sha256:92b9d8adfe4eb64081dac9b344f9af9a314509fa9de85e5bbbfafe186f28eea4"},
+    {file = "cz_legacy-0.1.7.tar.gz", hash = "sha256:4145958f86308d9419baf1417f00551d128c94507ae8533e89c1f4e3c05ad8c0"},
 ]
 darglint = [
     {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"},
@@ -2849,13 +3165,17 @@ decorator = [
     {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
     {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
 ]
-dictdiffer = [
-    {file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
-    {file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
+deepdiff6 = [
+    {file = "deepdiff6-6.2.0-py3-none-any.whl", hash = "sha256:83d8bfcdc12c3d769d3337f0e91f49cf4ba6b040706ab8698112e306f909a102"},
+    {file = "deepdiff6-6.2.0.tar.gz", hash = "sha256:15fb1d93c1d6b605ffde588a1666fb212f9fb802cb1a499fb0db19b5acdb8426"},
+]
+defusedxml = [
+    {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
+    {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
 ]
 diff-cover = [
-    {file = "diff_cover-6.5.1-py3-none-any.whl", hash = "sha256:ad602868ff14969563216866fc249f068df54892bd6c989201110bbb837dab06"},
-    {file = "diff_cover-6.5.1.tar.gz", hash = "sha256:8c3bb13812e9667bc83f8c7606402511e9c2fe79d6786f129522e79693ee096b"},
+    {file = "diff_cover-7.0.1-py3-none-any.whl", hash = "sha256:16f2fe42885fee23400b886675d813ce72b9ef382856049fc1cd1a3f5b07357a"},
+    {file = "diff_cover-7.0.1.tar.gz", hash = "sha256:69258355daf8276057a92e42cec52594ad8cfe7dd505d6f0e56fe440bc4418d0"},
 ]
 dill = [
     {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
@@ -2872,9 +3192,6 @@ docformatter = [
     {file = "docformatter-1.5.0-py3-none-any.whl", hash = "sha256:ae56c64822c3184602ac83ec37650c9785e80dfec17b4eba4f49ad68815d71c0"},
     {file = "docformatter-1.5.0.tar.gz", hash = "sha256:9dc71659d3b853c3018cd7b2ec34d5d054370128e12b79ee655498cb339cc711"},
 ]
-docstring-parser = [
-    {file = "docstring_parser-0.7.3.tar.gz", hash = "sha256:cde5fbf8b846433dfbde1e0f96b7f909336a634d5df34a38cb75050c7346734a"},
-]
 doit = [
     {file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"},
     {file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"},
@@ -2887,21 +3204,9 @@ exceptiongroup = [
     {file = "exceptiongroup-1.0.0rc9-py3-none-any.whl", hash = "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337"},
     {file = "exceptiongroup-1.0.0rc9.tar.gz", hash = "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96"},
 ]
-falcon = [
-    {file = "falcon-2.0.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983"},
-    {file = "falcon-2.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b"},
-    {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389"},
-    {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936"},
-    {file = "falcon-2.0.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8"},
-    {file = "falcon-2.0.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986"},
-    {file = "falcon-2.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439"},
-    {file = "falcon-2.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4"},
-    {file = "falcon-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad"},
-    {file = "falcon-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494"},
-    {file = "falcon-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357"},
-    {file = "falcon-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9"},
-    {file = "falcon-2.0.0-py2.py3-none-any.whl", hash = "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53"},
-    {file = "falcon-2.0.0.tar.gz", hash = "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc"},
+face = [
+    {file = "face-20.1.1-py2-none-any.whl", hash = "sha256:3790311a7329e4b0d90baee346eecad54b337629576edf3a246683a5f0d24446"},
+    {file = "face-20.1.1.tar.gz", hash = "sha256:7d59ca5ba341316e58cf72c6aff85cca2541cf5056c4af45cb63af9a814bed3e"},
 ]
 fancycompleter = [
     {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"},
@@ -2951,8 +3256,8 @@ flake8-broken-line = [
     {file = "flake8_broken_line-0.5.0-py3-none-any.whl", hash = "sha256:daafb19b67eead0410ce7ba155d51a15b9d020ebe7630d87de9c2b93cedb6703"},
 ]
 flake8-bugbear = [
-    {file = "flake8-bugbear-22.9.11.tar.gz", hash = "sha256:39236c0e97160d1ab05d9f87422173d16e925a6220b3635bfc4aee766bf8194a"},
-    {file = "flake8_bugbear-22.9.11-py3-none-any.whl", hash = "sha256:e74350a4cfc670e184f3433c223b1e7378f1cf8345ded6c8f12ac1a50c5df22b"},
+    {file = "flake8-bugbear-22.9.23.tar.gz", hash = "sha256:17b9623325e6e0dcdcc80ed9e4aa811287fcc81d7e03313b8736ea5733759937"},
+    {file = "flake8_bugbear-22.9.23-py3-none-any.whl", hash = "sha256:cd2779b2b7ada212d7a322814a1e5651f1868ab0d3f24cc9da66169ab8fda474"},
 ]
 flake8-builtins = [
     {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"},
@@ -2974,8 +3279,8 @@ flake8-docstrings = [
     {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"},
 ]
 flake8-eradicate = [
-    {file = "flake8-eradicate-1.3.0.tar.gz", hash = "sha256:e4c98f00d17dc8653e3388cac2624cd81e9735de2fd4a8dcf99029633ebd7a63"},
-    {file = "flake8_eradicate-1.3.0-py3-none-any.whl", hash = "sha256:85a71e0c5f4e07f7c6c5fec520483561fd6bd295417d622855bdeade99242e3d"},
+    {file = "flake8-eradicate-1.4.0.tar.gz", hash = "sha256:3088cfd6717d1c9c6c3ac45ef2e5f5b6c7267f7504d5a74b781500e95cb9c7e1"},
+    {file = "flake8_eradicate-1.4.0-py3-none-any.whl", hash = "sha256:e3bbd0871be358e908053c1ab728903c114f062ba596b4d40c852fd18f473d56"},
 ]
 flake8-expression-complexity = [
     {file = "flake8_expression_complexity-0.0.11-py3-none-any.whl", hash = "sha256:b56bac37f7dd5d3d102a7111c89f6579c2cbd897b868147794c9ed12aadc627c"},
@@ -3004,6 +3309,10 @@ flake8-pep3101 = [
     {file = "flake8-pep3101-1.3.0.tar.gz", hash = "sha256:86e3eb4e42de8326dcd98ebdeaf9a3c6854203a48f34aeb3e7e8ed948107f512"},
     {file = "flake8_pep3101-1.3.0-py2.py3-none-any.whl", hash = "sha256:a5dae1caca1243b2b40108dce926d97cf5a9f52515c4a4cbb1ffe1ca0c54e343"},
 ]
+flake8-pie = [
+    {file = "flake8-pie-0.16.0.tar.gz", hash = "sha256:b8dcb7b92706fa33d05d92a4b3e49b7a9fd3f0041849166275b646ba50e515ba"},
+    {file = "flake8_pie-0.16.0-py3-none-any.whl", hash = "sha256:24cd7849b0eee22e2328b9e9d2a1dea40013b0a3106864bbadd06a4b05dbb71f"},
+]
 flake8-plone-hasattr = [
     {file = "flake8-plone-hasattr-0.2.post0.tar.gz", hash = "sha256:f72ef91a47de847f80749a3668aad89fb23f0e6dcf93a1100b0e909b9e378ec6"},
     {file = "flake8_plone_hasattr-0.2.post0-py2.py3-none-any.whl", hash = "sha256:cfabd900e3ac68c47f46e4fa82e5db23463ceb954fc91e2a3b209f3c810e0c3f"},
@@ -3047,13 +3356,17 @@ flake8-tuple = [
     {file = "flake8_tuple-0.4.1-py2.py3-none-any.whl", hash = "sha256:d828cc8e461c50cacca116e9abb0c9e3be565e8451d3f5c00578c63670aae680"},
     {file = "flake8_tuple-0.4.1.tar.gz", hash = "sha256:8a1b42aab134ef4c3fef13c6a8f383363f158b19fbc165bd91aed9c51851a61d"},
 ]
+flake8-typing-imports = [
+    {file = "flake8_typing_imports-1.13.0-py2.py3-none-any.whl", hash = "sha256:21e4d5bd0bf684d78363d35edf9859719f6620bfe30f24ed5a7afbc8745638cd"},
+    {file = "flake8_typing_imports-1.13.0.tar.gz", hash = "sha256:2078abc42d0c11ca50c2ed4c77198b844da8f7fd3901aef7e791e519c4be0c54"},
+]
 flake8-variables-names = [
     {file = "flake8_variables_names-0.0.5-py3-none-any.whl", hash = "sha256:e3277031696bbe10b5132b49938cde1d70fcae9561533b7bd7ab8e69cb27addb"},
     {file = "flake8_variables_names-0.0.5.tar.gz", hash = "sha256:30133e14ee2300e13a60393a00f74d98110c76070ac67d1ab91606f02824a7e1"},
 ]
 fonttools = [
-    {file = "fonttools-4.37.2-py3-none-any.whl", hash = "sha256:88d48ef24486137c864dc56707b4b54ef8a97ab9162c2721ec61434baf1c4d13"},
-    {file = "fonttools-4.37.2.zip", hash = "sha256:b6d86ffd0a5f83d3da6a34d5f99a90398638e423cd6a8d93c5808af703432c7f"},
+    {file = "fonttools-4.37.3-py3-none-any.whl", hash = "sha256:a5bc5f5d48faa4085310b8ebd4c5d33bf27c6636c5f10a7de792510af2745a81"},
+    {file = "fonttools-4.37.3.zip", hash = "sha256:f32ef6ec966cf0e7d2aa88601fed2e3a8f2851c26b5db2c80ccc8f82bee4eedc"},
 ]
 future = [
     {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
@@ -3070,13 +3383,17 @@ GitPython = [
     {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
     {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
 ]
-hug = [
-    {file = "hug-2.6.1-py2.py3-none-any.whl", hash = "sha256:31c8fc284f81377278629a4b94cbb619ae9ce829cdc2da9564ccc66a121046b4"},
-    {file = "hug-2.6.1.tar.gz", hash = "sha256:b0edace2acb618873779c9ce6ecf9165db54fef95c22262f5700fcdd9febaec9"},
+glom = [
+    {file = "glom-22.1.0-py2.py3-none-any.whl", hash = "sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772"},
+    {file = "glom-22.1.0.tar.gz", hash = "sha256:1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5"},
+]
+griffe = [
+    {file = "griffe-0.22.2-py3-none-any.whl", hash = "sha256:cea5415ac6a92f4a22638e3f1f2e661402bac09fb8e8266936d67185a7e0d0fb"},
+    {file = "griffe-0.22.2.tar.gz", hash = "sha256:1408e336a4155392bbd81eed9f2f44bf144e71b9c664e905630affe83bbc088e"},
 ]
 hypothesis = [
-    {file = "hypothesis-6.54.5-py3-none-any.whl", hash = "sha256:d8d2e18139be18e5c95593f1e2d87dbcd21534a32022a1db6e8ac2d458ee1d1a"},
-    {file = "hypothesis-6.54.5.tar.gz", hash = "sha256:8a9056825695f415bfad4e808ae719fc01383a9ab659775319724365afcc7ec7"},
+    {file = "hypothesis-6.54.6-py3-none-any.whl", hash = "sha256:e44833325f9a55f795596ceefd7ede7d626cfe45836025d2647cccaff7070e10"},
+    {file = "hypothesis-6.54.6.tar.gz", hash = "sha256:2d5e2d5ccd0efce4e0968a6164f4e4853f808e33f4d91490c975c98beec0c7c3"},
 ]
 identify = [
     {file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"},
@@ -3094,6 +3411,10 @@ importlib-metadata = [
     {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
     {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
 ]
+importlib-resources = [
+    {file = "importlib_resources-5.9.0-py3-none-any.whl", hash = "sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7"},
+    {file = "importlib_resources-5.9.0.tar.gz", hash = "sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681"},
+]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
@@ -3106,6 +3427,10 @@ Jinja2 = [
     {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
     {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
 ]
+jsonschema = [
+    {file = "jsonschema-4.16.0-py3-none-any.whl", hash = "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9"},
+    {file = "jsonschema-4.16.0.tar.gz", hash = "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23"},
+]
 kiwisolver = [
     {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
     {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
@@ -3321,17 +3646,13 @@ lxml = [
     {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"},
 ]
-Mako = [
-    {file = "Mako-1.2.2-py3-none-any.whl", hash = "sha256:8efcb8004681b5f71d09c983ad5a9e6f5c40601a6ec469148753292abc0da534"},
-    {file = "Mako-1.2.2.tar.gz", hash = "sha256:3724869b363ba630a272a5f89f68c070352137b8fd1757650017b7e06fda163f"},
-]
 mando = [
     {file = "mando-0.6.4-py2.py3-none-any.whl", hash = "sha256:4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c"},
     {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"},
 ]
 Markdown = [
-    {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"},
-    {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"},
+    {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
+    {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
 ]
 MarkupSafe = [
     {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
@@ -3422,26 +3743,58 @@ mccabe = [
     {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
     {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
 ]
+mdx-truly-sane-lists = [
+    {file = "mdx_truly_sane_lists-1.3-py3-none-any.whl", hash = "sha256:b9546a4c40ff8f1ab692f77cee4b6bfe8ddf9cccf23f0a24e71f3716fe290a37"},
+    {file = "mdx_truly_sane_lists-1.3.tar.gz", hash = "sha256:b661022df7520a1e113af7c355c62216b384c867e4f59fb8ee7ad511e6e77f45"},
+]
 mergedeep = [
     {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
     {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
 ]
 mkdocs = [
-    {file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"},
-    {file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"},
+    {file = "mkdocs-1.4.0-py3-none-any.whl", hash = "sha256:ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa"},
+    {file = "mkdocs-1.4.0.tar.gz", hash = "sha256:e5549a22d59e7cb230d6a791edd2c3d06690908454c0af82edc31b35d57e3069"},
+]
+mkdocs-autorefs = [
+    {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
+    {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
+]
+mkdocs-gen-files = [
+    {file = "mkdocs-gen-files-0.4.0.tar.gz", hash = "sha256:377bff8ee8e93515916689f483d971643f83a94eed7e92318854da8f344f0163"},
+    {file = "mkdocs_gen_files-0.4.0-py3-none-any.whl", hash = "sha256:3241a4c947ecd11763ca77cc645015305bf71a0e1b9b886801c114fcf9971e71"},
 ]
 mkdocs-git-revision-date-localized-plugin = [
     {file = "mkdocs-git-revision-date-localized-plugin-1.1.0.tar.gz", hash = "sha256:38517e2084229da1a1b9460e846c2748d238c2d79efd405d1b9174a87bd81d79"},
     {file = "mkdocs_git_revision_date_localized_plugin-1.1.0-py3-none-any.whl", hash = "sha256:4ba0e49abea3e9f6ee26e2623ff7283873da657471c61f1d0cfbb986f403316d"},
 ]
+mkdocs-include-markdown-plugin = [
+    {file = "mkdocs_include_markdown_plugin-3.8.1-py3-none-any.whl", hash = "sha256:168e092ba7ca04dfc1e8826eccb5cec8932332e2de6b450809e70dd31c82b852"},
+    {file = "mkdocs_include_markdown_plugin-3.8.1.tar.gz", hash = "sha256:88b3486552b7be031b0657d9d86ff56141ab989552346f7c83aec71746d5e8d0"},
+]
+mkdocs-literate-nav = [
+    {file = "mkdocs-literate-nav-0.5.0.tar.gz", hash = "sha256:c63d4385bb2b520baede25badad2d0936acebff3da175922c600d0a91902d8ff"},
+    {file = "mkdocs_literate_nav-0.5.0-py3-none-any.whl", hash = "sha256:b41149172af38c1a1df5e8d63292cbe7ed2a1431cd5daf884289036f2a9c7cca"},
+]
 mkdocs-material = [
-    {file = "mkdocs-material-8.5.1.tar.gz", hash = "sha256:2daf604d00d554d5496c02b4c4d14dfa57dd689c90f639da5020601baef4b235"},
-    {file = "mkdocs_material-8.5.1-py2.py3-none-any.whl", hash = "sha256:79e9b65e481edb539eda6a3a939a0e02609a5c54afc315c3b05f57ff40db3188"},
+    {file = "mkdocs_material-8.5.3-py3-none-any.whl", hash = "sha256:d194c38041d1e83560221022b3f85eec4604b35e44f5c3a488c24b88542074ed"},
+    {file = "mkdocs_material-8.5.3.tar.gz", hash = "sha256:43b0aa707d6f9acd836024cab2dce9330957c94a4e1e41c23ee6c8ce67b4d8c5"},
 ]
 mkdocs-material-extensions = [
     {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
     {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"},
 ]
+mkdocs-section-index = [
+    {file = "mkdocs-section-index-0.3.4.tar.gz", hash = "sha256:050151bfe7c0e374f197335e0ecb19c45b53dbafc0f817aa203f0cc24bcf7d10"},
+    {file = "mkdocs_section_index-0.3.4-py3-none-any.whl", hash = "sha256:214f7a6df9d35a5772e9577f3899ff3edd90044064589e6dd4d84615b72a8024"},
+]
+mkdocstrings = [
+    {file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"},
+    {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
+]
+mkdocstrings-python = [
+    {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
+    {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
+]
 mr-proper = [
     {file = "mr_proper-0.0.7-py3-none-any.whl", hash = "sha256:74a1b60240c46f10ba518707ef72811a01e5c270da0a78b5dd2dd923d99fdb14"},
     {file = "mr_proper-0.0.7.tar.gz", hash = "sha256:03b517b19e617537f711ce418b125e5f2efd82ec881539cdee83195c78c14a02"},
@@ -3508,29 +3861,30 @@ multidict = [
     {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
 ]
 mypy = [
-    {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"},
+    {file = "mypy-0.981-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4bc460e43b7785f78862dab78674e62ec3cd523485baecfdf81a555ed29ecfa0"},
+    {file = "mypy-0.981-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:756fad8b263b3ba39e4e204ee53042671b660c36c9017412b43af210ddee7b08"},
+    {file = "mypy-0.981-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16a0145d6d7d00fbede2da3a3096dcc9ecea091adfa8da48fa6a7b75d35562d"},
+    {file = "mypy-0.981-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce65f70b14a21fdac84c294cde75e6dbdabbcff22975335e20827b3b94bdbf49"},
+    {file = "mypy-0.981-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e35d764784b42c3e256848fb8ed1d4292c9fc0098413adb28d84974c095b279"},
+    {file = "mypy-0.981-cp310-cp310-win_amd64.whl", hash = "sha256:e53773073c864d5f5cec7f3fc72fbbcef65410cde8cc18d4f7242dea60dac52e"},
+    {file = "mypy-0.981-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ee196b1d10b8b215e835f438e06965d7a480f6fe016eddbc285f13955cca659"},
+    {file = "mypy-0.981-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad21d4c9d3673726cf986ea1d0c9fb66905258709550ddf7944c8f885f208be"},
+    {file = "mypy-0.981-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1debb09043e1f5ee845fa1e96d180e89115b30e47c5d3ce53bc967bab53f62d"},
+    {file = "mypy-0.981-cp37-cp37m-win_amd64.whl", hash = "sha256:9f362470a3480165c4c6151786b5379351b790d56952005be18bdbdd4c7ce0ae"},
+    {file = "mypy-0.981-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c9e0efb95ed6ca1654951bd5ec2f3fa91b295d78bf6527e026529d4aaa1e0c30"},
+    {file = "mypy-0.981-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e178eaffc3c5cd211a87965c8c0df6da91ed7d258b5fc72b8e047c3771317ddb"},
+    {file = "mypy-0.981-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06e1eac8d99bd404ed8dd34ca29673c4346e76dd8e612ea507763dccd7e13c7a"},
+    {file = "mypy-0.981-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa38f82f53e1e7beb45557ff167c177802ba7b387ad017eab1663d567017c8ee"},
+    {file = "mypy-0.981-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:64e1f6af81c003f85f0dfed52db632817dabb51b65c0318ffbf5ff51995bbb08"},
+    {file = "mypy-0.981-cp38-cp38-win_amd64.whl", hash = "sha256:e1acf62a8c4f7c092462c738aa2c2489e275ed386320c10b2e9bff31f6f7e8d6"},
+    {file = "mypy-0.981-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6ede64e52257931315826fdbfc6ea878d89a965580d1a65638ef77cb551f56d"},
+    {file = "mypy-0.981-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb3978b191b9fa0488524bb4ffedf2c573340e8c2b4206fc191d44c7093abfb7"},
+    {file = "mypy-0.981-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f8fcf7b4b3cc0c74fb33ae54a4cd00bb854d65645c48beccf65fa10b17882c"},
+    {file = "mypy-0.981-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64d2ce043a209a297df322eb4054dfbaa9de9e8738291706eaafda81ab2b362"},
+    {file = "mypy-0.981-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ee3dbc53d4df7e6e3b1c68ac6a971d3a4fb2852bf10a05fda228721dd44fae1"},
+    {file = "mypy-0.981-cp39-cp39-win_amd64.whl", hash = "sha256:8e8e49aa9cc23aa4c926dc200ce32959d3501c4905147a66ce032f05cb5ecb92"},
+    {file = "mypy-0.981-py3-none-any.whl", hash = "sha256:794f385653e2b749387a42afb1e14c2135e18daeb027e0d97162e4b7031210f8"},
+    {file = "mypy-0.981.tar.gz", hash = "sha256:ad77c13037d3402fbeffda07d51e3f228ba078d1c7096a73759c9419ea031bf4"},
 ]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
@@ -3578,32 +3932,38 @@ numpy = [
     {file = "numpy-1.23.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8"},
     {file = "numpy-1.23.3.tar.gz", hash = "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd"},
 ]
+ordered-set = [
+    {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"},
+    {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"},
+]
 packaging = [
     {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
     {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
 pandas = [
-    {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:799e6a25932df7e6b1f8dabf63de064e2205dc309abb75956126a0453fd88e97"},
-    {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd1d69a387f7d5e1a5a06a87574d9ef2433847c0e78113ab51c84d3a8bcaeaa"},
-    {file = "pandas-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:94f2ed1fd51e545ebf71da1e942fe1822ee01e10d3dd2a7276d01351333b7c6b"},
-    {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4591cadd06fbbbd16fafc2de6e840c1aaefeae3d5864b688004777ef1bbdede3"},
-    {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0022fe6a313df1c4869b5edc012d734c6519a6fffa3cf70930f32e6a1078e49"},
-    {file = "pandas-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:785e878a6e6d8ddcdb8c181e600855402750052497d7fc6d6b508894f6b8830b"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c4bb8b0ab9f94207d07e401d24baebfc63057246b1a5e0cd9ee50df85a656871"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51c424ca134fdaeac9a4acd719d1ab48046afc60943a489028f0413fdbe9ef1c"},
-    {file = "pandas-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ce35f947202b0b99c660221d82beb91d2e6d553d55a40b30128204e3e2c63848"},
-    {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6f1848148ed3204235967613b0a32be2d77f214e9623f554511047705c1e04"},
-    {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cc960959be28d064faefc0cb2aef854d46b827c004ebea7e79b5497ed83e7d"},
-    {file = "pandas-1.4.4-cp38-cp38-win32.whl", hash = "sha256:9d805bce209714b1c1fa29bfb1e42ad87e4c0a825e4b390c56a3e71593b7e8d8"},
-    {file = "pandas-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:afbddad78a98ec4d2ce08b384b81730de1ccc975b99eb663e6dac43703f36d98"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a08ceb59db499864c58a9bf85ab6219d527d91f14c0240cc25fa2c261032b2a7"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0959c41004e3d2d16f39c828d6da66ebee329836a7ecee49fb777ac9ad8a7501"},
-    {file = "pandas-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87b4194f344dcd14c0f885cecb22005329b38bda10f1aaf7b9596a00ec8a4768"},
-    {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d2a7a3c1fea668d56bd91edbd5f2732e0af8feb9d2bf8d9bfacb2dea5fa9536"},
-    {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a981cfabf51c318a562deb4ae7deec594c07aee7cf18b4594a92c23718ec8275"},
-    {file = "pandas-1.4.4-cp39-cp39-win32.whl", hash = "sha256:050aada67a5ec6699a7879e769825b510018a95fb9ac462bb1867483d0974a97"},
-    {file = "pandas-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:8d4d2fe2863ecddb0ba1979bdda26c8bc2ea138f5a979abe3ba80c0fa4015c91"},
-    {file = "pandas-1.4.4.tar.gz", hash = "sha256:ab6c0d738617b675183e5f28db32b5148b694ad9bba0a40c3ea26d96b431db67"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c"},
+    {file = "pandas-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3"},
+    {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be"},
+    {file = "pandas-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b"},
+    {file = "pandas-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a"},
+    {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060"},
+    {file = "pandas-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b"},
+    {file = "pandas-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761"},
+    {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9"},
+    {file = "pandas-1.5.0-cp38-cp38-win32.whl", hash = "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8"},
+    {file = "pandas-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009"},
+    {file = "pandas-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a"},
+    {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d"},
+    {file = "pandas-1.5.0-cp39-cp39-win32.whl", hash = "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488"},
+    {file = "pandas-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec"},
+    {file = "pandas-1.5.0.tar.gz", hash = "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977"},
 ]
 pandas-vet = [
     {file = "pandas-vet-0.2.3.tar.gz", hash = "sha256:58b64027a4c192b4b62272c1d8fdecc1733352452401282b697c1a32abe4656a"},
@@ -3621,32 +3981,8 @@ pdbpp = [
     {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"},
     {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"},
 ]
-pdocs = [
-    {file = "pdocs-1.1.1-py3-none-any.whl", hash = "sha256:4f5116cf5ce0fa9f13171cd74db224636d4d71370115eefce22d8945526fcfc0"},
-    {file = "pdocs-1.1.1.tar.gz", hash = "sha256:f148034970220c9e05d2e04d8eb3fcec3575cf480af0966123ef9d6621b46e4f"},
-]
-pendulum = [
-    {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"},
-    {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"},
-    {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"},
-    {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"},
-    {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"},
-    {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"},
-    {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"},
-    {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"},
-    {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"},
-    {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"},
-    {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"},
-    {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"},
-    {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"},
-    {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"},
-    {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"},
-    {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"},
-    {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"},
-    {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"},
-    {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"},
-    {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"},
-    {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"},
+peewee = [
+    {file = "peewee-3.15.3.tar.gz", hash = "sha256:cc934286d0c0842203abe66a3c6583d1463371e633b03d6da054d0f74e70706f"},
 ]
 pep8-naming = [
     {file = "pep8-naming-0.13.2.tar.gz", hash = "sha256:93eef62f525fd12a6f8c98f4dcc17fa70baae2f37fa1f73bec00e3e44392fa48"},
@@ -3720,6 +4056,10 @@ pip-check = [
     {file = "pip-check-2.7.tar.gz", hash = "sha256:17d1612af84fecfebdf8f8c60b9eaa9c389814a49affdcd88a1d4fd98fd17aee"},
     {file = "pip_check-2.7-py2.py3-none-any.whl", hash = "sha256:f14ba13b0a66cd9d4732f958fa79d0b0ed2ef7833239927ef4336266265b72fa"},
 ]
+pkgutil_resolve_name = [
+    {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"},
+    {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
+]
 platformdirs = [
     {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
     {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
@@ -3740,10 +4080,6 @@ proselint = [
     {file = "proselint-0.13.0-py3-none-any.whl", hash = "sha256:5799a26c79e52aed18a1b7671f74381c035b3f62a565b478306298fb134c80c4"},
     {file = "proselint-0.13.0.tar.gz", hash = "sha256:7dd2b63cc2aa390877c4144fcd3c80706817e860b017f04882fbcd2ab0852a58"},
 ]
-punq = [
-    {file = "punq-0.6.2-py3-none-any.whl", hash = "sha256:8d365bdc01038897dfcc0c9bf63bc8910a95afc46e5e427c2a7abbda7144502b"},
-    {file = "punq-0.6.2.tar.gz", hash = "sha256:4d824c92ca640feefb080c1ecdd034e37f79833b3cb88dacac38ed7e048f4dca"},
-]
 py = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
@@ -3806,8 +4142,8 @@ Pygments = [
     {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
 ]
 pylint = [
-    {file = "pylint-2.15.2-py3-none-any.whl", hash = "sha256:cc3da458ba810c49d330e09013dec7ced5217772dec8f043ccdd34dae648fde8"},
-    {file = "pylint-2.15.2.tar.gz", hash = "sha256:f63404a2547edb5247da263748771ac9a806ed1de4174cda01293c08ddbc2999"},
+    {file = "pylint-2.15.3-py3-none-any.whl", hash = "sha256:7f6aad1d8d50807f7bc64f89ac75256a9baf8e6ed491cc9bc65592bc3f462cf1"},
+    {file = "pylint-2.15.3.tar.gz", hash = "sha256:5fdfd44af182866999e6123139d265334267339f29961f00c89783155eacc60b"},
 ]
 pymdown-extensions = [
     {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"},
@@ -3818,7 +4154,7 @@ pyparsing = [
     {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
 ]
 pyrate-limiter = [
-    {file = "pyrate-limiter-2.8.1.tar.gz", hash = "sha256:0741b7db4b3facdce60bd836e0fcc43911bc52c443f674f924afba7e02e79c18"},
+    {file = "pyrate-limiter-2.8.2.tar.gz", hash = "sha256:083aa1732053e6b135487b8295989b7a24e7075964f076577422dfb3712be413"},
 ]
 pyreadline = [
     {file = "pyreadline-2.1.win-amd64.exe", hash = "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"},
@@ -3828,6 +4164,29 @@ pyreadline = [
 pyrepl = [
     {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"},
 ]
+pyrsistent = [
+    {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
+    {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
+    {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
+    {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
+    {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
+    {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
+    {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
+    {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
+    {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
+    {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
+    {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
+    {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
+    {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
+    {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
+    {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
+    {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
+    {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
+    {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
+    {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
+    {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
+    {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
+]
 pytest = [
     {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
     {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
@@ -3837,8 +4196,12 @@ pytest-benchmark = [
     {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"},
 ]
 pytest-cache-assert = [
-    {file = "pytest_cache_assert-2.0.0-py3-none-any.whl", hash = "sha256:79435945c3206f858349cfca02169b13112e6ab666f1d179ba09edb5666767c9"},
-    {file = "pytest_cache_assert-2.0.0.tar.gz", hash = "sha256:b316a51456f7a066814bbc51f7f0f895bc398e8a41f03503153f81cf73e9d14f"},
+    {file = "pytest_cache_assert-3.0.0-py3-none-any.whl", hash = "sha256:c9c328c1a0f7de1c741b6d919a3ca4a139c6812bbc6dc9d101bdaf7d83153d6b"},
+    {file = "pytest_cache_assert-3.0.0.tar.gz", hash = "sha256:e572630479b910a8775bb67bd4ea7b00a72598a0f5110f4a6eaee27f749c163d"},
+]
+pytest-cov = [
+    {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
+    {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
 ]
 pytest-html = [
     {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"},
@@ -3884,17 +4247,17 @@ python-dateutil = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
 ]
+python-lsp-jsonrpc = [
+    {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"},
+    {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"},
+]
 pytz = [
     {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
     {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
 ]
-pytzdata = [
-    {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"},
-    {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"},
-]
 pyupgrade = [
-    {file = "pyupgrade-2.38.0-py2.py3-none-any.whl", hash = "sha256:e68e033603de3b4b675c7bd733723fb2bb7930a1898b6da61fb503643322e2f9"},
-    {file = "pyupgrade-2.38.0.tar.gz", hash = "sha256:7d03766fb5d68e9b0ec86b7d48c3cc29462b0031ff48ceece34bd224708553c0"},
+    {file = "pyupgrade-2.38.2-py2.py3-none-any.whl", hash = "sha256:41bb9a9fd48fe57163b0dacffff433d6d5a63a0f7c2402918917b5f1a533342b"},
+    {file = "pyupgrade-2.38.2.tar.gz", hash = "sha256:a5d778c9de0b53975c6a9eac2d0df5adfad244a9f7d7993d8a114223ebbda367"},
 ]
 PyYAML = [
     {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
@@ -3955,9 +4318,51 @@ rich = [
     {file = "rich-12.5.1-py3-none-any.whl", hash = "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb"},
     {file = "rich-12.5.1.tar.gz", hash = "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca"},
 ]
+"ruamel.yaml" = [
+    {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
+    {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
+]
+"ruamel.yaml.clib" = [
+    {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
+    {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"},
+    {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
+    {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
+    {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
+    {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
+    {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
+    {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
+    {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"},
+    {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"},
+    {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"},
+    {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d3c620a54748a3d4cf0bcfe623e388407c8e85a4b06b8188e126302bcab93ea8"},
+    {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"},
+    {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"},
+    {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"},
+    {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"},
+    {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:210c8fcfeff90514b7133010bf14e3bad652c8efde6b20e00c43854bf94fa5a6"},
+    {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"},
+    {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"},
+    {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"},
+    {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"},
+    {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:61bc5e5ca632d95925907c569daa559ea194a4d16084ba86084be98ab1cec1c6"},
+    {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"},
+    {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"},
+    {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"},
+    {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"},
+    {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1b4139a6ffbca8ef60fdaf9b33dec05143ba746a6f0ae0f9d11d38239211d335"},
+    {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"},
+    {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"},
+    {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"},
+]
+semgrep = [
+    {file = "semgrep-0.115.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl", hash = "sha256:9b7b4cb1ab9b6d8fcdaf4212f69e5bc3c38341ed49e7418300e3fbfc3eda1658"},
+    {file = "semgrep-0.115.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl", hash = "sha256:d064f58e367f087b9be10a21fe826b8584a82c2fd0f9d7f0c867a2208917d550"},
+    {file = "semgrep-0.115.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_11_0_arm64.whl", hash = "sha256:3d3c19ef5c5c0e8b94781d26813ff2b3da440ed884db36c62352e9f282b1545e"},
+    {file = "semgrep-0.115.0.tar.gz", hash = "sha256:6b111884acc6da4c08543d4457bcbbfd1ecbc7306efb696311304e3b3f4902c1"},
+]
 setuptools = [
-    {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"},
-    {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"},
+    {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"},
+    {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"},
 ]
 setuptools-scm = [
     {file = "setuptools_scm-7.0.5-py3-none-any.whl", hash = "sha256:7930f720905e03ccd1e1d821db521bff7ec2ac9cf0ceb6552dd73d24a45d3b02"},
@@ -3980,8 +4385,8 @@ sortedcontainers = [
     {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
 ]
 sqlparse = [
-    {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
-    {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
+    {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
+    {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
 ]
 stdlib-list = [
     {file = "stdlib-list-0.8.0.tar.gz", hash = "sha256:a1e503719720d71e2ed70ed809b385c60cd3fb555ba7ec046b96360d30b16d9f"},
@@ -3991,6 +4396,10 @@ stevedore = [
     {file = "stevedore-4.0.0-py3-none-any.whl", hash = "sha256:87e4d27fe96d0d7e4fc24f0cbe3463baae4ec51e81d95fbe60d2474636e0c7d8"},
     {file = "stevedore-4.0.0.tar.gz", hash = "sha256:f82cc99a1ff552310d19c379827c2c64dd9f85a38bcd5559db2470161867b786"},
 ]
+StrEnum = [
+    {file = "StrEnum-0.4.8-py3-none-any.whl", hash = "sha256:36de425703f97511326b358dfd25939c9dabb4bee1ca15663570ba7cb8b5fa9c"},
+    {file = "StrEnum-0.4.8.tar.gz", hash = "sha256:08a7647f4fac2977470e5618cccd5e8b939768f02e5f5dd7864adaa329559949"},
+]
 sty = [
     {file = "sty-1.0.4-py3-none-any.whl", hash = "sha256:3e3352f05770a37e11c9dfc303cbccb1c6acc13710e15d181cb43be7df89f8ea"},
 ]
@@ -4000,7 +4409,8 @@ tabulate = [
     {file = "tabulate-0.8.10.tar.gz", hash = "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519"},
 ]
 termcolor = [
-    {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
+    {file = "termcolor-2.0.1-py3-none-any.whl", hash = "sha256:7e597f9de8e001a3208c4132938597413b9da45382b6f1d150cff8d062b7aaa3"},
+    {file = "termcolor-2.0.1.tar.gz", hash = "sha256:6b2cf769e93364a2676e1de56a7c0cff2cf5bd07f37e9cc80b0dd6320ebfe388"},
 ]
 terminaltables = [
     {file = "terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874"},
@@ -4022,10 +4432,18 @@ tomlkit = [
     {file = "tomlkit-0.11.4-py3-none-any.whl", hash = "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c"},
     {file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"},
 ]
+tqdm = [
+    {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"},
+    {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"},
+]
 transitions = [
     {file = "transitions-0.9.0-py2.py3-none-any.whl", hash = "sha256:5687ee8c6a3200830e44f988d16b0045f53293f7a873002d7bff70852331a078"},
     {file = "transitions-0.9.0.tar.gz", hash = "sha256:2f54d11bdb225779d7e729011e93a9fb717668ce3dc65f8d4f5a5d7ba2f48e10"},
 ]
+tryceratops = [
+    {file = "tryceratops-1.1.0-py3-none-any.whl", hash = "sha256:f4770960782a2ae87cad7a7fd1206476468d0f41383c9fd7a49c6d537534015e"},
+    {file = "tryceratops-1.1.0.tar.gz", hash = "sha256:58e56b33eeb7a9cfd643957069bb1872ebe26c1eeac7abb1877c9535b7e473db"},
+]
 types-setuptools = [
     {file = "types-setuptools-65.3.0.tar.gz", hash = "sha256:c26779cbba3947823c260354adaab4e91ca8c18888aa2b740f503844b88f1813"},
     {file = "types_setuptools-65.3.0-py3-none-any.whl", hash = "sha256:f69210049580939c70386252b776eb75ff4b6de488e7d766b9398669b7de68af"},
@@ -4106,7 +4524,8 @@ ujson = [
     {file = "ujson-5.5.0.tar.gz", hash = "sha256:b25077a971c7da47bd6846a912a747f6963776d90720c88603b1b55d81790780"},
 ]
 unimport = [
-    {file = "unimport-0.11.3.tar.gz", hash = "sha256:4182639dca2de4e9671f9bb3d1f5ee50d88047414ba5db007b53ef77bf321672"},
+    {file = "unimport-0.12.1-py3-none-any.whl", hash = "sha256:90c7eee542b3cef404ec9f39cc9bf5f14412b31b8d5654b38eba2ceb0ce47986"},
+    {file = "unimport-0.12.1.tar.gz", hash = "sha256:fb6238efdd9e1b1f2e433dfd9afe060ea4c7ce0056d530443abdd6b66a868e39"},
 ]
 untokenize = [
     {file = "untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2"},
@@ -4124,8 +4543,8 @@ virtualenv = [
     {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
 ]
 vulture = [
-    {file = "vulture-2.5-py2.py3-none-any.whl", hash = "sha256:a7c7e7a23b11e78840fdd821509d05a6134aa9fd60418fe39d60b3026fe698d9"},
-    {file = "vulture-2.5.tar.gz", hash = "sha256:2831694055eb2e36a09c3b7680934837102b9b6c0969206e3902d513612177c3"},
+    {file = "vulture-2.6-py2.py3-none-any.whl", hash = "sha256:e792e903ccc063ec4873a8979dcf11b51ea3d65a2d3b31c113d47be48f0cdcae"},
+    {file = "vulture-2.6.tar.gz", hash = "sha256:2515fa848181001dc8a73aba6a01a1a17406f5d372f24ec7f7191866f9f4997e"},
 ]
 watchdog = [
     {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
@@ -4154,6 +4573,10 @@ watchdog = [
     {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
     {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
 ]
+wcmatch = [
+    {file = "wcmatch-8.4.1-py3-none-any.whl", hash = "sha256:3476cd107aba7b25ba1d59406938a47dc7eec6cfd0ad09ff77193f21a964dee7"},
+    {file = "wcmatch-8.4.1.tar.gz", hash = "sha256:b1f042a899ea4c458b7321da1b5e3331e3e0ec781583434de1301946ceadb943"},
+]
 wcwidth = [
     {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
     {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
diff --git a/pyproject.toml b/pyproject.toml
index c0ca188b..4bc8d566 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ change_type_order = [
     "Change (Old)",
     "Fix (Old)",
 ]
-version = "0.17.0"
+version = "0.19.1"
 version_files = ["pyproject.toml", "calcipy/__init__.py"]
 
 [tool.commitizen.cz_legacy_map]
@@ -46,7 +46,7 @@ multi_line_output = 5
 
 [tool.poetry]
 name = "calcipy"
-version = "0.17.0"
+version = "0.19.1"
 description = "Python package to simplify development. Includes functionality for task running, testing, linting, documenting, and more"
 license = "MIT"
 authors = ["Kyle King <dev.act.kyle@gmail.com>"]
@@ -108,10 +108,16 @@ numpy = { version = ">=1.23.1", markers = "python_version > '3.7'" }
 # Development
 better-exceptions = { version = ">=0.3.3", optional = true }
 commitizen = { version = ">=2.28.0", optional = true }                                                           # Prevent install of 1.X
+# Docs
+mdx_truly_sane_lists = { version = ">=1.2", optional= true }
+mkdocs-gen-files = { version = ">=0.4.0", optional = true }
 mkdocs-git-revision-date-localized-plugin = { version = ">=1.0.1", optional = true }
+mkdocs-include-markdown-plugin = { version = ">=3.3.0", optional= true }
+mkdocs-literate-nav = { version = ">=0.5.0", optional = true }
 mkdocs-material = { version = ">=8.2.16", optional = true }
+mkdocs-section-index = { version = ">=0.3.4", optional = true }
+mkdocstrings = { extras = ["python"], version = ">=0.18.1", optional= true }
 pdbpp = { version = ">=0.10.3", optional = true, markers = "sys_platform != 'win32' or python_version < '3.9'" }
-pdocs = { version = ">=1.1.1", optional = true }
 pyupgrade = { version = ">=2.32.1", optional = true }
 
 # Lint
@@ -146,16 +152,18 @@ flake8-isort = { version = ">=4.1.1", optional = true }
 flake8-logging-format = { version = ">=0.6.0", optional = true }
 flake8-markdown = { version = ">=0.3.0", optional = true }
 flake8-pep3101 = { version = ">=1.3.0", optional = true }
+flake8-pie = { version = ">=0.16.0", optional = true }
 flake8-plone-hasattr = { version = ">=0.2.post0", optional = true, allow-prereleases = true }
 flake8-print = { version = ">=5.0.0", optional = true }
 flake8-printf-formatting = { version = ">=1.1.2", optional = true }
 flake8-pytest-style = { version = ">=1.6.0", optional = true }
 flake8-quotes = { version = ">=3.3.1", optional = true }
 flake8-return = { version = ">=1.1.3", optional = true }
-flake8-simplify = { version = ">=0.19.0", optional = true }
+flake8-simplify = { version = ">=0.19.1", optional = true }
 flake8-SQL = { version = ">=0.4.1", optional = true }
 flake8-string-format = { version = ">=0.3.0", optional = true }
 flake8-tuple = { version = ">=0.4.1", optional = true }
+flake8-typing-imports = { version = ">=1.13.0", optional = true }
 flake8-variables-names = { version = ">=0.0.5", optional = true }
 isort = { version = ">=5.10.1", optional = true }
 licensecheck = { extras = ["full"], version = ">=2022", optional = true }
@@ -164,27 +172,21 @@ pep8-naming = { version = ">=0.11.1", optional = true }
 pip-check = { version = ">=2.7", optional = true }
 proselint = { version = ">=0.13.0", optional = true }
 pylint = { version = ">=2.13.9", optional = true }
+semgrep = { version = ">=0.114.0", optional = true }
+tryceratops = { version = ">=1.1.0", optional = true }
 unimport = { version = ">=0.11.3", optional = true }
 vulture = { version = ">=2.4", optional = true }
 xenon = { version = ">=0.9.0", optional = true }
 yamllint = { version = ">=1.26.3", optional = true }
 
 # Test
-coverage = { version = ">=6.4", optional = true }
 diff-cover = { version = ">=6.5.0", optional = true }
-hypothesis = { extras = [
-    "cli",
-], version = ">=6.46.9", optional = true } # Use CLI with: "poetry run hypothesis write calcipy.dot_dict.ddict"
 lxml = { version = ">=4.8.0", optional = true } # required for the HTML mypy report format
-mypy = { version = ">=0.960", optional = true }
+mypy = { version = ">=0.981", optional = true }
 nox-poetry = { version = ">=1.0.0", optional = true }
 pytest = { version = ">=7.1.2", optional = true }
-pytest-benchmark = { version = ">=3.4.1", optional = true }
-pytest-cache-assert = { version = ">=2.0.0", optional = true }
+pytest-cov = { version = ">=3.0.0", optional = true }
 pytest-html = { version = ">=3.1.1", optional = true }
-pytest-randomly = { version = ">=3.12.0", optional = true }
-pytest-recording = { version = ">=0.12.0", optional = true }
-pytest-subprocess = { version = ">=1.4.1", optional = true }
 pytest-watcher = { version = ">=0.2.3", optional = true }
 
 # commitizen_legacy
@@ -194,10 +196,15 @@ cz_legacy = { version = ">=0.1.2", optional = true }
 dev = [
     "better-exceptions",
     "commitizen",
+    "mdx_truly_sane_lists",
+    "mkdocs-gen-files",
     "mkdocs-git-revision-date-localized-plugin",
+    "mkdocs-include-markdown-plugin",
+    "mkdocs-literate-nav",
     "mkdocs-material",
+    "mkdocs-section-index",
+    "mkdocstrings",
     "pdbpp",
-    "pdocs",
     "pyupgrade",
 ]
 lint = [
@@ -233,6 +240,7 @@ lint = [
     "flake8-logging-format",
     "flake8-markdown",
     "flake8-pep3101",
+    "flake8-pie",
     "flake8-plone-hasattr",
     "flake8-print",
     "flake8-printf-formatting",
@@ -243,6 +251,7 @@ lint = [
     "flake8-SQL",
     "flake8-string-format",
     "flake8-tuple",
+    "flake8-typing-imports",
     "flake8-variables-names",
     "isort",
     "licensecheck",
@@ -251,28 +260,32 @@ lint = [
     "pip-check",
     "proselint",
     "pylint",
+    "semgrep",
+    "tryceratops",
     "unimport",
     "vulture",
     "xenon",
     "yamllint",
 ]
 test = [
-    "coverage",
     "diff-cover",
-    "hypothesis",
     "lxml",
     "mypy",
     "nox-poetry",
     "pytest",
-    "pytest-benchmark",
-    "pytest-cache-assert",
+    "pytest-cov",
     "pytest-html",
-    "pytest-randomly",
-    "pytest-recording",
-    "pytest-subprocess",
     "pytest-watcher",
 ]
 commitizen_legacy = ["cz_legacy"]
 
 [tool.poetry.dev-dependencies]
 matplotlib = "^3.5.2"
+# FYI: Must be added manually because of the circular dependency when testing pca
+hypothesis = { extras = ["cli"], version = ">=6.46.9" }
+# ^ Use CLI with: "poetry run hypothesis write calcipy.dot_dict.ddict"
+pytest-cache-assert = ">=2.0.0"
+pytest-benchmark = ">=3.4.1"
+pytest-randomly = ">=3.12.0"
+pytest-recording = ">=0.12.0"
+pytest-subprocess = ">=1.4.1"
diff --git a/scripts/check_imports.py b/scripts/check_imports.py
index 203eb174..4efd9fb9 100644
--- a/scripts/check_imports.py
+++ b/scripts/check_imports.py
@@ -11,11 +11,11 @@
 
 from pprint import pprint
 
-from calcipy.dev.conftest import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
-from calcipy.dev.noxfile import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
-from calcipy.doit_tasks import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
-from calcipy.dot_dict import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
-from calcipy.file_helpers import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
-from calcipy.log_helpers import *  # noqa: F401, F403, H303 # skipcq: PYL-W0614
+from calcipy.dev.conftest import *  # noqa: F401, F403, H303
+from calcipy.dev.noxfile import *  # noqa: F401, F403, H303
+from calcipy.doit_tasks import *  # noqa: F401, F403, H303
+from calcipy.dot_dict import *  # noqa: F401, F403, H303
+from calcipy.file_helpers import *  # noqa: F401, F403, H303
+from calcipy.log_helpers import *  # noqa: F401, F403, H303
 
 pprint(locals())  # noqa: T003


Test patch:
diff --git a/.test_calcipy_project/dodo.py b/.test_calcipy_project/dodo.py
index 22bdcf32..99474d47 100644
--- a/.test_calcipy_project/dodo.py
+++ b/.test_calcipy_project/dodo.py
@@ -20,7 +20,7 @@
 
 from test_project import __pkg_name__
 
-from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks). skipcq: PYL-W0614
+from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks)
 from calcipy.doit_tasks import DOIT_CONFIG_RECOMMENDED
 from calcipy.log_helpers import activate_debug_logging
 
diff --git a/calcipy/dev/conftest.py b/calcipy/dev/conftest.py
index 2b86aa88..d03636b0 100644
--- a/calcipy/dev/conftest.py
+++ b/calcipy/dev/conftest.py
@@ -58,7 +58,7 @@ def pytest_html_results_table_row(report: Any, cells: Any) -> None:  # pragma: n
         except AttributeError:
             ...  # The test suite likely failed
 
-    @pytest.hookimpl(hookwrapper=True)
+    @pytest.hookimpl(hookwrapper=True)  # type: ignore[misc]
     def pytest_runtest_makereport(item: Any, call: Any) -> Generator:  # type: ignore[type-arg]  # pragma: no cover
         """Modify the pytest-html output.
 
diff --git a/calcipy/doit_tasks/test.py b/calcipy/doit_tasks/test.py
old mode 100755
new mode 100644
index ad604fb8..9f54852c
--- a/calcipy/doit_tasks/test.py
+++ b/calcipy/doit_tasks/test.py
@@ -163,10 +163,11 @@ def task_coverage() -> DoitTask:
     cov_dir = dg.test.path_coverage_index.parent
     test_html = f'--html="{dg.test.path_test_report}" --self-contained-html'
     diff_html = f'--html-report {dg.test.path_diff_test_report}'
+    min_cov = f'--cov-fail-under={dg.test.min_cov}' if dg.test.min_cov else ''
     return debug_task([
         Interactive(
             f'poetry run coverage run --source={dg.meta.pkg_name} --module'
-            + f' pytest "{path_tests}" {dg.test.args_pytest} {test_html}',
+            + f' pytest "{path_tests}" {dg.test.args_pytest} {min_cov} {test_html}',
         ),
         'poetry run python -m coverage report --show-missing',
         f'poetry run python -m coverage html --directory={cov_dir}',
@@ -189,7 +190,7 @@ def task_check_types() -> DoitTask:
 
     """
     return debug_task([
-        Interactive(f'poetry run mypy {get_dg().meta.pkg_name} --show-error-codes'),
+        Interactive(f'poetry run mypy {get_dg().meta.pkg_name}'),
     ])
 
 
@@ -241,7 +242,7 @@ def ptw_task(cli_args: str) -> DoitTask:
 def task_ptw_not_interactive() -> DoitTask:
     """Run pytest watch for failed first and skip the INTERACTIVE marker.
 
-    kwargs: `-m 'not INTERACTIVE' -vvv`
+    >  `-m 'not INTERACTIVE' -vvv`
 
     Returns:
         DoitTask: doit task
@@ -254,7 +255,7 @@ def task_ptw_not_interactive() -> DoitTask:
 def task_ptw_ff() -> DoitTask:
     """Run pytest watch for failed first and skip the INTERACTIVE marker.
 
-    kwargs: `--last-failed --new-first -m 'not INTERACTIVE' -vv`
+    >  `--last-failed --new-first -m 'not INTERACTIVE' -vv`
 
     Returns:
         DoitTask: doit task
@@ -267,7 +268,7 @@ def task_ptw_ff() -> DoitTask:
 def task_ptw_current() -> DoitTask:
     """Run pytest watch for only tests with the CURRENT marker.
 
-    kwargs: `-m 'CURRENT' -vv`
+    >  `-m 'CURRENT' -vv`
 
     Returns:
         DoitTask: doit task
diff --git a/tests/assert-cache/README.md b/tests/assert-cache/README.md
index 4929e2ea..182cc9df 100644
--- a/tests/assert-cache/README.md
+++ b/tests/assert-cache/README.md
@@ -2,5 +2,4 @@
 
 This folder is automatically generated by `pytest_cache_assert`.
 
-Files can be regenerated by deleting and allowing `pytest_cache_assert` to
-recreate them when next running the test suite.
+Files can be regenerated by deleting and allowing `pytest_cache_assert` to recreate them when next running the test suite.
diff --git a/tests/assert-cache/test_format_report.json b/tests/assert-cache/test_code_tag_collector/test_format_report.json
similarity index 100%
rename from tests/assert-cache/test_format_report.json
rename to tests/assert-cache/test_code_tag_collector/test_format_report.json
diff --git a/tests/assert-cache/test_search_lines.json b/tests/assert-cache/test_code_tag_collector/test_search_lines.json
similarity index 100%
rename from tests/assert-cache/test_search_lines.json
rename to tests/assert-cache/test_code_tag_collector/test_search_lines.json
diff --git a/tests/assert-cache/test_doit_tasks/test_code_tags/test_task_collect_code_tags.json b/tests/assert-cache/test_doit_tasks/test_code_tags/test_task_collect_code_tags.json
new file mode 100644
index 00000000..9f5b8bcc
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_code_tags/test_task_collect_code_tags.json
@@ -0,0 +1,69 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_code_tags.py",
+      "test_name": "test_task_collect_code_tags"
+    }
+  ],
+  "_json": [
+    [
+      "<function write_code_tag_file(..)>",
+      [],
+      {
+        "base_dir": "--/.test_calcipy_project",
+        "header": "# Task Summary\n\nAuto-Generated by `calcipy`",
+        "path_tag_summary": "--/.test_calcipy_project/docs_custom/docs/CODE_TAG_SUMMARY.md",
+        "paths_source": [
+          "--/.test_calcipy_project/.copier-answers.yml",
+          "--/.test_calcipy_project/.deepsource.toml",
+          "--/.test_calcipy_project/.flake8",
+          "--/.test_calcipy_project/.github/ISSUE_TEMPLATE/bug_report.md",
+          "--/.test_calcipy_project/.github/ISSUE_TEMPLATE/config.yml",
+          "--/.test_calcipy_project/.github/ISSUE_TEMPLATE/feature_request.md",
+          "--/.test_calcipy_project/.github/ISSUE_TEMPLATE/question.md",
+          "--/.test_calcipy_project/.github/PULL_REQUEST_TEMPLATE.md",
+          "--/.test_calcipy_project/.github/labels.yml",
+          "--/.test_calcipy_project/.github/workflows/labels.yml",
+          "--/.test_calcipy_project/.gitignore",
+          "--/.test_calcipy_project/.pre-commit-config.yaml",
+          "--/.test_calcipy_project/.pylintrc",
+          "--/.test_calcipy_project/.sourcery.yaml",
+          "--/.test_calcipy_project/LICENSE",
+          "--/.test_calcipy_project/README.md",
+          "--/.test_calcipy_project/appveyor.yml",
+          "--/.test_calcipy_project/docs_custom/README.md",
+          "--/.test_calcipy_project/docs_custom/_javascript/tables.js",
+          "--/.test_calcipy_project/docs_custom/adr/NNN-Template.md",
+          "--/.test_calcipy_project/docs_custom/adr/README.md",
+          "--/.test_calcipy_project/docs_custom/docs/CODE_OF_CONDUCT.md",
+          "--/.test_calcipy_project/docs_custom/docs/CONTRIBUTING.md",
+          "--/.test_calcipy_project/docs_custom/docs/DEVELOPER_GUIDE.md",
+          "--/.test_calcipy_project/docs_custom/docs/SECURITY.md",
+          "--/.test_calcipy_project/docs_custom/docs/STYLE_GUIDE.md",
+          "--/.test_calcipy_project/dodo.py",
+          "--/.test_calcipy_project/mkdocs.yml",
+          "--/.test_calcipy_project/mypy.ini",
+          "--/.test_calcipy_project/noxfile.py",
+          "--/.test_calcipy_project/poetry.lock",
+          "--/.test_calcipy_project/pyproject.toml",
+          "--/.test_calcipy_project/scripts/README.md",
+          "--/.test_calcipy_project/scripts/check_imports.py",
+          "--/.test_calcipy_project/scripts/sample-json-file.json",
+          "--/.test_calcipy_project/test_project/__init__.py",
+          "--/.test_calcipy_project/tests/__init__.py",
+          "--/.test_calcipy_project/tests/configuration.py",
+          "--/.test_calcipy_project/tests/conftest.py",
+          "--/.test_calcipy_project/tests/data/README.md",
+          "--/.test_calcipy_project/tests/test_zz_test_project.py"
+        ],
+        "regex_compiled": "re.compile('((^|\\\\s|\\\\(|\"|\\\\\\')(?P<tag>FIXME|TODO|PLANNED)(:| -)([^\\\\r\\\\n]))(?P<text>.+)')",
+        "tag_order": [
+          "FIXME",
+          "TODO",
+          "PLANNED"
+        ]
+      }
+    ]
+  ]
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump.json
new file mode 100644
index 00000000..4ae7a29d
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump.json
@@ -0,0 +1,23 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_cl_bump"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "poetry run cz changelog",
+      [
+        "<function _move_cl(..)>",
+        []
+      ],
+      "Cmd: poetry run cz bump --annotated-tag --no-verify",
+      "git push origin --tags --no-verify",
+      "which gh >> /dev/null && gh release create --generate-notes $(git tag --list --sort=-creatordate | head -n 1)"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump_pre.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump_pre.json
new file mode 100644
index 00000000..fb62a863
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_bump_pre.json
@@ -0,0 +1,32 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_cl_bump_pre"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "poetry run cz changelog",
+      [
+        "<function _move_cl(..)>",
+        []
+      ],
+      "Cmd: poetry run cz bump --prerelease %(prerelease)s --no-verify",
+      "git push origin --tags --no-verify",
+      "which gh >> /dev/null && gh release create --generate-notes $(git tag --list --sort=-creatordate | head -n 1) --prerelease"
+    ],
+    "params": [
+      {
+        "default": "",
+        "help": "Specify prerelease version for bump (alpha, beta, rc)",
+        "long": "prerelease",
+        "name": "prerelease",
+        "short": "p"
+      }
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_write.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_write.json
new file mode 100644
index 00000000..d537a9e4
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_cl_write.json
@@ -0,0 +1,20 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_cl_write"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "poetry run cz changelog",
+      [
+        "<function _move_cl(..)>",
+        []
+      ]
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_deploy_docs.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_deploy_docs.json
new file mode 100644
index 00000000..05c840ab
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_deploy_docs.json
@@ -0,0 +1,16 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_deploy_docs"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run mkdocs gh-deploy"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_document.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_document.json
new file mode 100644
index 00000000..833657f9
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_document.json
@@ -0,0 +1,40 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_document"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function write_autoformatted_md_sections(..)>",
+        []
+      ],
+      [
+        "<function delete_dir(..)>",
+        [
+          "--/.test_calcipy_project/docs_custom/modules"
+        ]
+      ],
+      [
+        "<function write_text(..)>",
+        [
+          "--/.test_calcipy_project/docs_custom/modules/test_project/_code_diagrams.md",
+          "# Code Diagrams\n\n> Auto-generated with `pylint-pyreverse`\n\n## Packages\n\n![./packages.svg](./packages.svg)\n\n[Full Size](./packages.svg)\n\n## Classes\n\n![./classes.svg](./classes.svg)\n\n[Full Size](./classes.svg)\n"
+        ]
+      ],
+      "Cmd: poetry run pyreverse test_project --output svg --output-directory /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/docs_custom/modules/test_project",
+      [
+        "<function _find_and_trim_trailing_whitespace(..)>",
+        [
+          "--/.test_calcipy_project/docs_custom/modules"
+        ]
+      ],
+      "Cmd: poetry run mkdocs build --site-dir /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/site"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_doc/test_task_open_docs.json b/tests/assert-cache/test_doit_tasks/test_doc/test_task_open_docs.json
new file mode 100644
index 00000000..58733760
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_doc/test_task_open_docs.json
@@ -0,0 +1,22 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_doc.py",
+      "test_name": "test_task_open_docs"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function open(..)>",
+        [
+          "http://localhost:8000"
+        ]
+      ],
+      "Cmd: poetry run mkdocs serve --dirtyreload"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_lint_python.json b/tests/assert-cache/test_doit_tasks/test_lint/test_lint_python.json
new file mode 100644
index 00000000..22aa9607
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_lint_python.json
@@ -0,0 +1,32 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_lint_python"
+    }
+  ],
+  "_json": [
+    [
+      "<function if_found_unlink(..)>",
+      [
+        "--/.test_calcipy_project/flake8.log"
+      ]
+    ],
+    "Cmd: poetry run python -m flake8 --config=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.flake8  --output-file=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/flake8.log --exit-zero \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_file.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_file_2.py\"",
+    [
+      "<function _check_linting_errors(..)>",
+      [
+        "--/.test_calcipy_project/flake8.log",
+        [
+          "F401",
+          "E800",
+          "I001",
+          "I003"
+        ]
+      ]
+    ],
+    "Cmd: echo \"poetry run diff-quality --violations=flake8 --compare-branch=origin/main --fail-under=80 --html-report /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/diff_lint_report.html\"",
+    "Cmd: poetry run python -m xenon test_project --max-absolute B --max-modules A --max-average A"
+  ]
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_auto_format.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_auto_format.json
new file mode 100644
index 00000000..fad77403
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_auto_format.json
@@ -0,0 +1,23 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_auto_format"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "poetry run pyupgrade \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --py38-plus --keep-runtime-typing",
+      "poetry run python -m autoflake \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --in-place --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports --remove-duplicate-keys",
+      "poetry run python -m unimport \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --include-star-import --remove",
+      "poetry run python -m autopep8 \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --in-place --aggressive",
+      "poetry run absolufy-imports \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --never",
+      "poetry run python -m isort \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --settings-path \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.isort.cfg\"",
+      "poetry run add-trailing-comma \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --py36-plus --exit-zero-even-if-changed",
+      "poetry run python -m docformatter \"dodo.py\" \"noxfile.py\" \"scripts/check_imports.py\" \"test_project/__init__.py\" \"tests/__init__.py\" \"tests/configuration.py\" \"tests/conftest.py\" \"tests/test_zz_test_project.py\" --blank --close-quotes-on-newline --in-place --wrap-summaries=120 --wrap-descriptions=120"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_critical_only.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_critical_only.json
new file mode 100644
index 00000000..bc2eb99f
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_critical_only.json
@@ -0,0 +1,36 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_lint_critical_only"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function if_found_unlink(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log"
+        ]
+      ],
+      "Cmd: poetry run python -m flake8 --config=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.flake8  --output-file=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/flake8.log --exit-zero \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py\"",
+      [
+        "<function _check_linting_errors(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log",
+          [
+            "T100",
+            "T101",
+            "T103"
+          ]
+        ]
+      ],
+      "Cmd: echo \"poetry run diff-quality --violations=flake8 --compare-branch=origin/main --fail-under=80 --html-report /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/diff_lint_report.html\"",
+      "Cmd: poetry run python -m xenon test_project --max-absolute C --max-modules A --max-average A",
+      "Cmd: poetry run yamllint \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.copier-answers.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/ISSUE_TEMPLATE/config.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/labels.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/workflows/labels.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/appveyor.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/mkdocs.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.pre-commit-config.yaml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.sourcery.yaml\""
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_project.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_project.json
new file mode 100644
index 00000000..08e74bed
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_project.json
@@ -0,0 +1,35 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_lint_project"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function if_found_unlink(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log"
+        ]
+      ],
+      "Cmd: poetry run python -m flake8 --config=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.flake8  --output-file=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/flake8.log --exit-zero \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py\"",
+      [
+        "<function _check_linting_errors(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log",
+          [
+            "T100",
+            "T101"
+          ]
+        ]
+      ],
+      "Cmd: echo \"poetry run diff-quality --violations=flake8 --compare-branch=origin/main --fail-under=80 --html-report /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/diff_lint_report.html\"",
+      "Cmd: poetry run python -m xenon test_project --max-absolute B --max-modules A --max-average A",
+      "Cmd: poetry run yamllint \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.copier-answers.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/ISSUE_TEMPLATE/config.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/labels.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.github/workflows/labels.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/appveyor.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/mkdocs.yml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.pre-commit-config.yaml\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.sourcery.yaml\""
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_python.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_python.json
new file mode 100644
index 00000000..ccce2f28
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_lint_python.json
@@ -0,0 +1,34 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_lint_python"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function if_found_unlink(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log"
+        ]
+      ],
+      "Cmd: poetry run python -m flake8 --config=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/.flake8  --output-file=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/flake8.log --exit-zero \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py\" \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py\"",
+      [
+        "<function _check_linting_errors(..)>",
+        [
+          "--/.test_calcipy_project/flake8.log",
+          [
+            "T100",
+            "T101"
+          ]
+        ]
+      ],
+      "Cmd: echo \"poetry run diff-quality --violations=flake8 --compare-branch=origin/main --fail-under=80 --html-report /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/diff_lint_report.html\"",
+      "Cmd: poetry run python -m xenon test_project --max-absolute B --max-modules A --max-average A"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_pre_commit_hooks.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_pre_commit_hooks.json
new file mode 100644
index 00000000..6345315b
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_pre_commit_hooks.json
@@ -0,0 +1,18 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_pre_commit_hooks"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run pre-commit install",
+      "Cmd: poetry run pre-commit autoupdate",
+      "Cmd: poetry run pre-commit run --all-files --hook-stage commit --hook-stage push"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_lint/test_task_radon_lint.json b/tests/assert-cache/test_doit_tasks/test_lint/test_task_radon_lint.json
new file mode 100644
index 00000000..eece8405
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_lint/test_task_radon_lint.json
@@ -0,0 +1,43 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_lint.py",
+      "test_name": "test_task_radon_lint"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function echo(..)>",
+        [
+          "# Radon with args: mi --show --min B"
+        ]
+      ],
+      "Cmd: poetry run radon mi --show --min B /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py",
+      [
+        "<function echo(..)>",
+        [
+          "# Radon with args: cc --total-average --min B"
+        ]
+      ],
+      "Cmd: poetry run radon cc --total-average --min B /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py",
+      [
+        "<function echo(..)>",
+        [
+          "# Radon with args: hal"
+        ]
+      ],
+      "Cmd: poetry run radon hal /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py",
+      [
+        "<function echo(..)>",
+        [
+          "# Radon with args: raw --summary"
+        ]
+      ],
+      "Cmd: poetry run radon raw --summary /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/dodo.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/noxfile.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/scripts/check_imports.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/test_project/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/__init__.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/configuration.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/conftest.py /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests/test_zz_test_project.py"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_packaging/test_task_check_for_stale_packages.json b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_check_for_stale_packages.json
new file mode 100644
index 00000000..4fb919a0
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_check_for_stale_packages.json
@@ -0,0 +1,32 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_packaging.py",
+      "test_name": "test_task_check_for_stale_packages"
+    }
+  ],
+  "_json": {
+    "actions": [
+      [
+        "<function find_stale_packages(..)>",
+        [
+          "--/.test_calcipy_project/poetry.lock",
+          "--/.calcipy_packaging.lock"
+        ],
+        {
+          "stale_months": 48
+        }
+      ],
+      "Cmd: poetry run pip-check --cmd=\"poetry run pip\" --hide-unchanged"
+    ],
+    "file_dep": [
+      "--/.test_calcipy_project/poetry.lock"
+    ],
+    "targets": [
+      "--/.calcipy_packaging.lock"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish.json b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish.json
new file mode 100644
index 00000000..7e291a07
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish.json
@@ -0,0 +1,17 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_packaging.py",
+      "test_name": "test_task_publish"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run nox --session build_dist build_check",
+      "poetry publish "
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish_test_pypi.json b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish_test_pypi.json
new file mode 100644
index 00000000..1e86cd06
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_packaging/test_task_publish_test_pypi.json
@@ -0,0 +1,17 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_packaging.py",
+      "test_name": "test_task_publish_test_pypi"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run nox --session build_dist build_check",
+      "poetry publish --repository testpypi"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_check_types.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_check_types.json
new file mode 100644
index 00000000..eb6f7619
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_check_types.json
@@ -0,0 +1,16 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_check_types"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run mypy test_project"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_coverage.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_coverage.json
new file mode 100644
index 00000000..43bdd657
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_coverage.json
@@ -0,0 +1,21 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_coverage"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run coverage run --source=test_project --module pytest \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests\" -x -l --ff --nf -vv --cov-fail-under=80 --html=\"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/test_report.html\" --self-contained-html",
+      "poetry run python -m coverage report --show-missing",
+      "poetry run python -m coverage html --directory=/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/cov_html",
+      "poetry run python -m coverage json",
+      "poetry run python -m coverage xml",
+      "Cmd: poetry run diff-cover coverage.xml --fail-under=95 --compare-branch=origin/release --html-report /Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/releases/tests/diff_test_report.html"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_open_test_docs.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_open_test_docs.json
new file mode 100644
index 00000000..70c38199
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_open_test_docs.json
@@ -0,0 +1,14 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_open_test_docs"
+    }
+  ],
+  "_json": {
+    "actions": [],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_current.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_current.json
new file mode 100644
index 00000000..af82dd85
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_current.json
@@ -0,0 +1,15 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_ptw_current"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run ptw \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project\" -m \"CURRENT\" -vv"
+    ],
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_ff.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_ff.json
new file mode 100644
index 00000000..cbe515e2
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_ff.json
@@ -0,0 +1,15 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_ptw_ff"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run ptw \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project\" --last-failed --new-first -m \"not INTERACTIVE\" -vv"
+    ],
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_marker.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_marker.json
new file mode 100644
index 00000000..538cdcde
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_marker.json
@@ -0,0 +1,24 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_ptw_marker"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run ptw \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project\" -vvv -m \"%(marker)s\""
+    ],
+    "params": [
+      {
+        "default": "",
+        "help": "Runs test with specified marker logic\nSee: https://docs.pytest.org/en/latest/example/markers.html?highlight=-m",
+        "long": "marker",
+        "name": "marker",
+        "short": "m"
+      }
+    ],
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_not_interactive.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_not_interactive.json
new file mode 100644
index 00000000..0a6b0132
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_ptw_not_interactive.json
@@ -0,0 +1,15 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_ptw_not_interactive"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run ptw \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project\" -m \"not INTERACTIVE\" -vvv"
+    ],
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_test.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_test.json
new file mode 100644
index 00000000..46d2d0dc
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_test.json
@@ -0,0 +1,16 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_test"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run python -m pytest \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests\" -x -l --ff --nf -vv"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_test_all.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_all.json
new file mode 100644
index 00000000..08c0a33c
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_all.json
@@ -0,0 +1,16 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_test_all"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run python -m pytest \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests\" --ff -vv"
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_test_keyword.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_keyword.json
new file mode 100644
index 00000000..c42bda55
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_keyword.json
@@ -0,0 +1,24 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_test_keyword"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run python -m pytest \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests\" -x -l --ff --nf -vv -k \"%(keyword)s\""
+    ],
+    "params": [
+      {
+        "default": "",
+        "help": "Runs only tests that match the string pattern\nSee: https://docs.pytest.org/en/latest/usage.html#specifying-tests-selecting-tests",
+        "long": "keyword",
+        "name": "keyword",
+        "short": "k"
+      }
+    ],
+    "verbosity": 2
+  }
+}
diff --git a/tests/assert-cache/test_doit_tasks/test_test/test_task_test_marker.json b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_marker.json
new file mode 100644
index 00000000..439d2456
--- /dev/null
+++ b/tests/assert-cache/test_doit_tasks/test_test/test_task_test_marker.json
@@ -0,0 +1,25 @@
+{
+  "_info": [
+    {
+      "func_args": {},
+      "test_file": "test_doit_tasks/test_test.py",
+      "test_name": "test_task_test_marker"
+    }
+  ],
+  "_json": {
+    "actions": [
+      "Cmd: poetry run python -m pytest \"/Users/kyleking/Developer/packages/calcipy/.test_calcipy_project/tests\" -x -l --ff --nf -vv -m \"%(marker)s\""
+    ],
+    "params": [
+      {
+        "default": "",
+        "help": "Runs test with specified marker logic\nSee: https://docs.pytest.org/en/latest/example/markers.html?highlight=-m",
+        "long": "marker",
+        "name": "marker",
+        "short": "m"
+      }
+    ],
+    "title": "<function _show_cmd(..)>",
+    "verbosity": 2
+  }
+}
diff --git a/tests/configuration.py b/tests/configuration.py
old mode 100755
new mode 100644
diff --git a/tests/conftest.py b/tests/conftest.py
index bec8839f..5a0a9f53 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,8 @@
-"""PyTest configuration."""
+"""PyTest configuration.
+
+Note: the calcipy imports are required for a nicer test HTML report
+
+"""
 
 import os
 from pathlib import Path
@@ -6,6 +10,8 @@
 import pytest
 from beartype.typing import Dict, Generator
 from decorator import contextmanager
+from doit.tools import CmdAction, Interactive
+from pytest_cache_assert import AssertConfig, Converter
 
 from calcipy.dev.conftest import pytest_configure  # noqa: F401
 from calcipy.dev.conftest import pytest_html_results_table_header  # noqa: F401
@@ -30,6 +36,12 @@ def vcr_config() -> Dict:
     }
 
 
+@pytest.fixture()
+def cache_assert_config() -> AssertConfig:
+    """Configure pytest_cache_assert using `AssertConfig`."""
+    return AssertConfig(converters=[Converter(types=[CmdAction, Interactive], func=str)])
+
+
 @contextmanager
 def __temp_chdir(path_tmp: Path) -> Generator[None, None, None]:
     """Temporarily change the working directory.
diff --git a/tests/test_doit_tasks/test_code_tags.py b/tests/test_doit_tasks/test_code_tags.py
index 76aad8dc..37ccecf8 100644
--- a/tests/test_doit_tasks/test_code_tags.py
+++ b/tests/test_doit_tasks/test_code_tags.py
@@ -1,13 +1,10 @@
 """Test code_tags.py."""
 
-from calcipy.code_tag_collector import write_code_tag_file
 from calcipy.doit_tasks.code_tags import task_collect_code_tags
 
 
-def test_task_collect_code_tags():
+def test_task_collect_code_tags(assert_against_cache):
     """Test task_collect_code_tags."""
     result = task_collect_code_tags()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert isinstance(actions[0][0], type(write_code_tag_file))
+    assert_against_cache(result['actions'])
diff --git a/tests/test_doit_tasks/test_doc.py b/tests/test_doit_tasks/test_doc.py
index 80fdffa9..72315c0d 100644
--- a/tests/test_doit_tasks/test_doc.py
+++ b/tests/test_doit_tasks/test_doc.py
@@ -2,14 +2,12 @@
 
 import json
 import shutil
-import webbrowser
 from copy import deepcopy
 from pathlib import Path
 
 import pytest
 from beartype.typing import List
 
-from calcipy.doit_tasks.base import write_text
 from calcipy.doit_tasks.doc import (
     _format_cov_table, _handle_coverage, _handle_source_file, _is_mkdocs_local,
     _move_cl, _parse_var_comment, task_cl_bump, task_cl_bump_pre, task_cl_write,
@@ -33,40 +31,25 @@ def test_move_cl():
     path_cl_dest.unlink()
 
 
-def test_task_cl_write():
+def test_task_cl_write(assert_against_cache):
     """Test task_cl_write."""
     result = task_cl_write()
 
-    actions = result['actions']
-    assert len(actions) == 2
-    assert actions[0] == 'poetry run cz changelog'
-    assert isinstance(actions[1][0], type(_move_cl))
+    assert_against_cache(result)
 
 
-def test_task_cl_bump():
+def test_task_cl_bump(assert_against_cache):
     """Test task_cl_bump."""
     result = task_cl_bump()
 
-    actions = result['actions']
-    assert len(actions) == 5
-    assert isinstance(actions[1][0], type(_move_cl))
-    assert 'poetry run cz bump --annotated-tag' in str(actions[2])
-    assert actions[3] == 'git push origin --tags --no-verify'
+    assert_against_cache(result)
 
 
-def test_task_cl_bump_pre():
+def test_task_cl_bump_pre(assert_against_cache):
     """Test task_cl_bump_pre."""
     result = task_cl_bump_pre()
 
-    actions = result['actions']
-    assert len(actions) == 5
-    assert isinstance(actions[1][0], type(_move_cl))
-    assert 'poetry run cz bump --prerelease' in str(actions[2])
-    assert actions[3] == 'git push origin --tags --no-verify'
-    params = result['params']
-    assert len(params) == 1
-    assert params[0]['name'] == 'prerelease'
-    assert params[0]['short'] == 'p'
+    assert_against_cache(result)
 
 
 _COVERAGE_SAMPLE_DATA = {
@@ -191,37 +174,24 @@ def test_write_autoformatted_md_sections_custom(fix_test_cache):
     get_dg().doc.handler_lookup = lookup_original
 
 
-def test_task_document():
+def test_task_document(assert_against_cache):
     """Test task_document."""
     result = task_document()
 
     assert _is_mkdocs_local() is False
-    actions = result['actions']
-    assert len(actions) == 7
-    assert isinstance(actions[0][0], type(write_autoformatted_md_sections))
-    assert str(actions[2]).startswith('Cmd: poetry run pdocs as_markdown')
-    assert isinstance(actions[3][0], type(write_text))
-    assert ' pyreverse ' in str(actions[4])
-    assert str(actions[-1]).startswith('Cmd: poetry run mkdocs build --site-dir')
+    assert_against_cache(result)
 
 
-def test_task_open_docs():
+def test_task_open_docs(assert_against_cache):
     """Test task_open_docs."""
     result = task_open_docs()
 
     assert _is_mkdocs_local() is False
-    actions = result['actions']
-    assert len(actions) == 2
-    assert isinstance(actions[0][0], type(webbrowser.open))
-    assert len(actions[0][1]) == 1
-    assert actions[0][1][0].startswith('http://localhost')
-    assert str(actions[1]).endswith('mkdocs serve --dirtyreload')
+    assert_against_cache(result)
 
 
-def test_task_deploy_docs():
+def test_task_deploy_docs(assert_against_cache):
     """Test task_deploy_docs."""
     result = task_deploy_docs()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('mkdocs gh-deploy')
+    assert_against_cache(result)
diff --git a/tests/test_doit_tasks/test_lint.py b/tests/test_doit_tasks/test_lint.py
index 726d2553..e2d583cf 100644
--- a/tests/test_doit_tasks/test_lint.py
+++ b/tests/test_doit_tasks/test_lint.py
@@ -1,21 +1,18 @@
 """Test doit_tasks/lint.py."""
 
-from itertools import zip_longest
 
 import pytest
 
-from calcipy.doit_tasks.base import echo
 from calcipy.doit_tasks.doit_globals import get_dg
 from calcipy.doit_tasks.lint import (
     _check_linting_errors, _lint_python, task_auto_format, task_lint_critical_only,
     task_lint_project, task_lint_python, task_pre_commit_hooks, task_radon_lint,
 )
-from calcipy.file_helpers import if_found_unlink
 
 from ..configuration import PATH_TEST_PROJECT
 
 
-def test_lint_python():
+def test_lint_python(assert_against_cache):
     """Test _lint_python."""
     result = _lint_python(
         lint_paths=[PATH_TEST_PROJECT / 'test_file.py', PATH_TEST_PROJECT / 'tests/test_file_2.py'],
@@ -23,12 +20,7 @@ def test_lint_python():
         ignore_errors=['F401', 'E800', 'I001', 'I003'],
     )
 
-    assert len(result) == 5
-    assert isinstance(result[0][0], type(if_found_unlink))
-    assert result[0][1][0].name == 'flake8.log'
-    assert str(result[1]).startswith('Cmd: poetry run python -m flake8 --config')
-    assert 'test_file.py' in str(result[1])
-    assert isinstance(result[2][0], type(_check_linting_errors))
+    assert_against_cache(result)
 
 
 FLAKE8_LOG = """doit_project/test_file.py:3:1: F401 'doit' imported but unused
@@ -60,89 +52,43 @@ def test_check_linting_errors_runtime_error(fix_test_cache):
     assert flake8_log_path.read_text() == FLAKE8_LOG
 
 
-def test_task_lint_python():
+def test_task_lint_python(assert_against_cache):
     """Test task_lint_python."""
     result = task_lint_python()
 
-    actions = result['actions']
-    assert len(actions) == 5
-    assert isinstance(actions[0][0], type(if_found_unlink))
-    assert len(actions[0][1]) == 1
-    assert actions[0][1][0].name == 'flake8.log'
-    assert str(actions[1]).startswith('Cmd: poetry run python -m flake8 --config')
-    assert 'dodo.py" ' in str(actions[1])
-    assert '.flake8 ' in str(actions[1])
-    assert 'flake8.log ' in str(actions[1])
-    assert isinstance(actions[2][0], type(_check_linting_errors))
-    assert len(actions[2][1]) == 2
-    assert actions[2][1][0].name == 'flake8.log'
-    assert actions[2][1][1] == ('T100', 'T101')
-
-
-def test_task_lint_project():
+    assert_against_cache(result)
+
+
+def test_task_lint_project(assert_against_cache):
     """Test task_lint_project."""
     result = task_lint_project()
 
-    actions = result['actions']
-    assert len(actions) == 6
-    assert 'poetry run yamllint ' in str(actions[5])
+    assert_against_cache(result)
 
 
-def test_task_lint_critical_only():
+def test_task_lint_critical_only(assert_against_cache):
     """Test task_lint_critical_only."""
     result = task_lint_critical_only()
 
-    actions = result['actions']
-    assert len(actions) == 6
-    assert 'T100' not in str(actions[1])
-    assert isinstance(actions[2][0], type(_check_linting_errors))
-    assert len(actions[2][1]) == 2
-    assert actions[2][1][0].name == 'flake8.log'
-    assert actions[2][1][1] == ['T100', 'T101', 'T103']  # Read from toml
-    assert 'T100' in actions[2][1][1]
-    assert '-m xenon ' in str(actions[4])
-    assert 'poetry run yamllint ' in str(actions[5])
+    assert_against_cache(result)
 
 
-def test_task_radon_lint():
+def test_task_radon_lint(assert_against_cache):
     """Test task_radon_lint."""
     result = task_radon_lint()
 
-    actions = result['actions']
-    assert len(actions) == 8
-    for action in actions:
-        if isinstance(action, tuple):
-            assert isinstance(action[0], type(echo))
-        else:
-            assert str(action).startswith('Cmd: poetry run radon ')
+    assert_against_cache(result)
 
 
-def test_task_auto_format():
+def test_task_auto_format(assert_against_cache):
     """Test task_auto_format."""
     result = task_auto_format()
 
-    actions = result['actions']
-    assert len(actions) == 8
-    cmd_seq = [
-        'run pyupgrade',
-        '-m autoflake',
-        '-m unimport',
-        '-m autopep8',
-        'run absolufy-imports',
-        '-m isort',
-        'run add-trailing-comma',
-        '-m docformatter',
-    ]
-    for cmd, action in zip_longest(cmd_seq, actions):
-        assert cmd in str(action)
-
-
-def test_task_pre_commit_hooks():
+    assert_against_cache(result)
+
+
+def test_task_pre_commit_hooks(assert_against_cache):
     """Test task_pre_commit_hooks."""
     result = task_pre_commit_hooks()
 
-    actions = result['actions']
-    assert len(actions) == 3
-    assert 'pre-commit install' in str(actions[0])
-    assert 'pre-commit autoupdate' in str(actions[1])
-    assert 'pre-commit run --all-files --hook-stage' in str(actions[2])
+    assert_against_cache(result)
diff --git a/tests/test_doit_tasks/test_packaging.py b/tests/test_doit_tasks/test_packaging.py
index ba571cb6..aac6ca77 100644
--- a/tests/test_doit_tasks/test_packaging.py
+++ b/tests/test_doit_tasks/test_packaging.py
@@ -26,27 +26,21 @@ def warning(self, message: str, **kwargs):
         self.logs['warnings'].append({'message': message, 'kwargs': kwargs})
 
 
-def test_task_publish():
+def test_task_publish(assert_against_cache):
     """Test task_publish."""
     result = task_publish()
 
-    actions = result['actions']
-    assert len(actions) == 2
-    assert 'poetry run nox --session build_dist build_check' in str(actions[0])
-    assert 'poetry publish' in str(actions[1])
+    assert_against_cache(result)
 
 
-def test_task_publish_test_pypi():
+def test_task_publish_test_pypi(assert_against_cache):
     """Test task_publish_test_pypi."""
     result = task_publish_test_pypi()
 
-    actions = result['actions']
-    assert len(actions) == 2
-    assert 'poetry run nox --session build_dist build_check' in str(actions[0])
-    assert 'poetry publish --repository testpypi' in str(actions[1])
+    assert_against_cache(result)
 
 
-def test_read_packages():
+def test_read_packages(assert_against_cache):
     """Test _read_packages."""
     path_lock = PATH_TEST_PROJECT / 'poetry.lock'
 
@@ -135,15 +129,8 @@ def test_check_for_stale_packages():
     assert _check_for_stale_packages(packages, stale_months=999) is None
 
 
-def test_task_check_for_stale_packages():
+def test_task_check_for_stale_packages(assert_against_cache):
     """Test task_check_for_stale_packages."""
     result = task_check_for_stale_packages()
 
-    actions = result['actions']
-    assert len(actions) == 2
-    assert isinstance(actions[0][0], type(find_stale_packages))
-    assert len(actions[0][1]) == 2
-    assert actions[0][1][0].name == 'poetry.lock'
-    assert actions[0][1][1].name == _PATH_PACK_LOCK.name
-    assert actions[0][2] == {'stale_months': 48}
-    assert 'poetry run pip-check' in str(actions[1])
+    assert_against_cache(result)
diff --git a/tests/test_doit_tasks/test_test.py b/tests/test_doit_tasks/test_test.py
index 03e685ac..bec78ac1 100644
--- a/tests/test_doit_tasks/test_test.py
+++ b/tests/test_doit_tasks/test_test.py
@@ -6,117 +6,81 @@
 )
 
 
-def test_task_test():
+def test_task_test(assert_against_cache):
     """Test task_test."""
     result = task_test()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('" -x -l --ff --nf -vv')
+    assert_against_cache(result)
 
 
-def test_task_test_all():
+def test_task_test_all(assert_against_cache):
     """Test task_test_all."""
     result = task_test_all()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('" --ff -vv')
+    assert_against_cache(result)
 
 
-def test_task_test_marker():
+def test_task_test_marker(assert_against_cache):
     """Test task_test_marker."""
     result = task_test_marker()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('" -x -l --ff --nf -vv -m "%(marker)s"')
-    params = result['params']
-    assert len(params) == 1
-    assert params[0]['name'] == 'marker'
-    assert params[0]['short'] == 'm'
+    assert_against_cache(result)
 
 
-def test_task_test_keyword():
+def test_task_test_keyword(assert_against_cache):
     """Test task_test_keyword."""
     result = task_test_keyword()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('" -x -l --ff --nf -vv -k "%(keyword)s"')
-    params = result['params']
-    assert len(params) == 1
-    assert params[0]['name'] == 'keyword'
-    assert params[0]['short'] == 'k'
+    assert_against_cache(result)
 
 
-def test_task_coverage():
+def test_task_coverage(assert_against_cache):
     """Test task_coverage."""
     result = task_coverage()
 
-    actions = result['actions']
-    assert len(actions) == 6
-    assert 'pytest' in str(actions[0])
-    for index, report_format in enumerate(['report', 'html', 'json', 'xml']):
-        assert f'poetry run python -m coverage {report_format}' in str(actions[1 + index])
-    assert 'diff-cover' in str(actions[-1])
+    assert_against_cache(result)
 
 
-def task_task_check_types():
+def test_task_check_types(assert_against_cache):
     """Test task_check_types."""
     result = task_check_types()
 
-    actions = result['actions']
-    assert 2 <= len(actions) > 1
-    assert str(actions[0]).startswitch('poetry run ')
+    # assert 2 <= len(actions) > 1
+    # assert str(actions[0]).startswitch('poetry run ')
+    assert_against_cache(result)  # FIXME: Revisit. Seems unstable
 
 
-def task_task_open_test_docs():
+def test_task_open_test_docs(assert_against_cache):
     """Test task_open_test_docs."""
     result = task_open_test_docs()
 
-    actions = result['actions']
-    assert 3 <= len(actions) > 2
+    # assert 3 <= len(actions) > 2
+    assert_against_cache(result)  # FIXME: Revisit. Seems unstable
 
 
-def task_task_ptw_not_interactive():
+def test_task_ptw_not_interactive(assert_against_cache):
     """Test task_ptw_not_interactive."""
     result = task_ptw_not_interactive()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).startswitch('poetry run ptw -- "')
-    assert str(actions[0]).endswitch('" -m "not INTERACTIVE" -vvv')
+    assert_against_cache(result)
 
 
-def task_task_ptw_ff():
+def test_task_ptw_ff(assert_against_cache):
     """Test task_ptw_ff."""
     result = task_ptw_ff()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).startswitch('poetry run ptw -- "')
-    assert str(actions[0]).endswitch('" --last-failed --new-first -m "not INTERACTIVE" -vv')
+    assert_against_cache(result)
 
 
-def task_task_ptw_current():
+def test_task_ptw_current(assert_against_cache):
     """Test task_ptw_current."""
     result = task_ptw_current()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).startswitch('poetry run ptw -- "')
-    assert str(actions[0]).endswitch('" -m "CURRENT" -vv')
+    assert_against_cache(result)
 
 
-def test_task_ptw_marker():
+def test_task_ptw_marker(assert_against_cache):
     """Test task_ptw_marker."""
     result = task_ptw_marker()
 
-    actions = result['actions']
-    assert len(actions) == 1
-    assert str(actions[0]).endswith('" -vvv -m "%(marker)s"')
-    params = result['params']
-    assert len(params) == 1
-    assert params[0]['name'] == 'marker'
-    assert params[0]['short'] == 'm'
+    assert_against_cache(result)
diff --git a/tests/test_doit_tasks/test_wildcard.py b/tests/test_doit_tasks/test_wildcard.py
index 93e73508..78b541f9 100644
--- a/tests/test_doit_tasks/test_wildcard.py
+++ b/tests/test_doit_tasks/test_wildcard.py
@@ -1,14 +1,14 @@
 """Test that the wildcard import works as expected."""
 
-from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks). # skipcq: PYL-W0614
+from calcipy.doit_tasks import *  # noqa: F401,F403,H303 (Run 'doit list' to see tasks)
 
 
 def test_doit_tasks_imports():
     """Test that the wildcard import for doit tasks only imports tasks."""
     suppress = ['@py_builtins', '@pytest_ar', 'test_doit_tasks_imports']
-    constants = ['DOIT_CONFIG_RECOMMENDED', 'TASKS_CI', 'TASKS_LOCAL']
+    constants = ['DOIT_CONFIG_RECOMMENDED', 'TASKS_FULL']
 
     wc_imports = [_g for _g in globals() if not _g.startswith('_') and _g not in suppress]  # act
 
     assert all(imp.startswith('task_') or imp in constants for imp in wc_imports)
-    assert len(wc_imports) == 39  # FYI: Update if the number of tasks change
+    assert len(wc_imports) == 38  # FYI: Update if the number of tasks change
diff --git a/tests/test_dot_dict.py b/tests/test_dot_dict.py
index 94960f35..d59975f4 100644
--- a/tests/test_dot_dict.py
+++ b/tests/test_dot_dict.py
@@ -2,6 +2,8 @@
 
 import arrow
 import pytest
+from hypothesis import given
+from hypothesis import strategies as st
 
 from calcipy.dot_dict import ddict
 
@@ -24,3 +26,21 @@ def test_ddict(key, value):
     assert result[key] == value
     assert isinstance(result, dict)
     assert result.get(f'--{key}--') is None
+
+
+_ST_ANY = st.booleans() | st.binary() | st.integers() | st.text()
+"""Broadest swatch of strategies for data input testing of dotdict."""
+
+
+@given(
+    key=st.text(),
+    value=(_ST_ANY | st.dictionaries(keys=_ST_ANY, values=_ST_ANY, max_size=10)),
+)
+def test_ddict_with_hypothesis(key, value):
+    """Test ddict."""
+    result = ddict(**{key: value})
+
+    assert getattr(result, key) == value
+    assert result[key] == value
+    assert isinstance(result, dict)
+    assert result.get(f'--{key}--') is None
diff --git a/tests/test_file_helpers.py b/tests/test_file_helpers.py
old mode 100755
new mode 100644
diff --git a/tests/test_zz_calcipy.py b/tests/test_zz_calcipy.py
old mode 100755
new mode 100644