Repository: pyapp-org/pyapp (language: python)

Issue:
Bump argcomplete from 1.12.2 to 1.12.3
[//]: # (dependabot-start)
⚠️  **Dependabot Preview has been deactivated** ⚠️

This pull request was created by Dependabot Preview, and you've upgraded to Dependabot. This means it won't respond to `dependabot` commands nor will it be automatically closed if a new version is found.

If you close this pull request, Dependabot will re-create it the next time it checks for updates and everything will work as expected.

---

[//]: # (dependabot-end)
Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 1.12.2 to 1.12.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/kislyuk/argcomplete/releases">argcomplete's releases</a>.</em></p>
<blockquote>
<h2>v1.12.3</h2>
<ul>
<li>
<p>Update importlib-metadata version pin (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/345">#345</a>)</p>
<ul>
<li>
<p>Display script debug output in tcsh (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/342">#342</a>)</p>
</li>
<li>
<p>Fish support improvements (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/338">#338</a>, <a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/339">#339</a>)</p>
</li>
<li>
<p>Print <code>warn()</code> message from beginning of line (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/335">#335</a>)</p>
</li>
<li>
<p>Test infrastructure improvements</p>
</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst">argcomplete's changelog</a>.</em></p>
<blockquote>
<h1>Changes for v1.12.3 (2021-04-19)</h1>
<ul>
<li>Update importlib-metadata version pin (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/345">#345</a>)</li>
<li>Display script debug output in tcsh (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/342">#342</a>)</li>
<li>Fish support improvements (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/338">#338</a>, <a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/339">#339</a>)</li>
<li>Print <code>warn()</code> message from beginning of line (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/335">#335</a>)</li>
<li>Test infrastructure improvements</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/kislyuk/argcomplete/commit/ce54b6dfd51f9f01b4ef85bb8692a9f804a11771"><code>ce54b6d</code></a> v1.12.3</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/89849e74b4bd6d8656f2ef8af495e79ad85d62b5"><code>89849e7</code></a> Update importlib-metadata version pin (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/345">#345</a>)</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/c61581f1462fcde76a1a27ed08b6a8b44c6269de"><code>c61581f</code></a> Use pip3 only</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/5ce4121b2877872fb39ec7a046a674db82aa84b5"><code>5ce4121</code></a> Display script debug output in tcsh (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/342">#342</a>)</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/9e2c6712c761d05f64f4d558a381d9be9fa13988"><code>9e2c671</code></a> Fix fish support (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/338">#338</a>)</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/3b921915adfbc804bd90af3872074262c31335fc"><code>3b92191</code></a> Use $version to detect fish version (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/339">#339</a>)</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/d4197606e3b342d49ec4a9ed51f2fe89a603b3f0"><code>d419760</code></a> Disable all console script tests on MacOS</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/121e6e553623017b7c9596e9bf38c6f43d62d0d4"><code>121e6e5</code></a> Add Python 3.9 to test matrix</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/e2c8e691218a1a6346954dc7f5b9a335967655c6"><code>e2c8e69</code></a> Fix failing test in Python 2.7 (<a href="https://github-redirect.dependabot.com/kislyuk/argcomplete/issues/336">#336</a>)</li>
<li><a href="https://github.com/kislyuk/argcomplete/commit/9da2ff4d9a59d66a2f1cc0be86b194f984f408fd"><code>9da2ff4</code></a> Skip tests where pip install fails on Python 2</li>
<li>Additional commits viewable in <a href="https://github.com/kislyuk/argcomplete/compare/v1.12.2...v1.12.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=argcomplete&package-manager=pip&previous-version=1.12.2&new-version=1.12.3)](https://dependabot.com/compatibility-score/?dependency-name=argcomplete&package-manager=pip&previous-version=1.12.2&new-version=1.12.3)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)



</details>


Discussion / hints:

Fix patch:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..62093b4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.py]
+charset = utf-8
+indent_style = space
+indent_size = 4
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..c6c2569
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+version: 2
+updates:
+- package-ecosystem: pip
+  directory: "/"
+  schedule:
+    interval: daily
+  open-pull-requests-limit: 10
+  labels:
+  - "\U0001F38B dependencies"
+  ignore:
+  - dependency-name: pytest-asyncio
+    versions:
+    - 0.15.0
+  - dependency-name: docutils
+    versions:
+    - "0.17"
+  - dependency-name: pyyaml
+    versions:
+    - 5.4.1
+  - dependency-name: sphinx
+    versions:
+    - 3.4.3
+    - 3.5.0
+  - dependency-name: argcomplete
+    versions:
+    - 1.12.2
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..d9b8653
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,54 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches: [development, master]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [development]
+  schedule:
+    - cron: '0 15 * * 1'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: ['python']
+        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/test-cases.yml b/.github/workflows/default.yml
similarity index 53%
rename from .github/workflows/test-cases.yml
rename to .github/workflows/default.yml
index dfd692b..c41c391 100644
--- a/.github/workflows/test-cases.yml
+++ b/.github/workflows/default.yml
@@ -7,41 +7,34 @@ on: [push]
 
 jobs:
   test:
-
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8]
+        python-version: [3.6, 3.7, 3.8, 3.9]
 
     name: Test Python ${{ matrix.python-version }}
     steps:
     - uses: actions/checkout@v2
-
+      with:
+          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
+          
     - name: Setup Python ${{ matrix.python-version }}
       uses: actions/setup-python@master
       with:
         python-version: ${{ matrix.python-version }}
 
-    - name: Setup coverage reporter
-      run: |
-        curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
-        chmod +x ./cc-test-reporter
-        GIT_BRANCH=$GITHUB_REF GIT_COMMIT_SHA=$GITHUB_SHA ./cc-test-reporter before-build
-
     - name: Install dependencies
       run: |
         python -m pip install -U poetry coverage
+        poetry config experimental.new-installer false
         poetry install
 
     - name: Test with pytest
       run: |
-        poetry run pytest
-
-    - name: Codecov
-      uses: codecov/codecov-action@v1.0.12
+        poetry run pytest --junit-xml=reports/xunit-result-${{ matrix.python-version }}.xml --cov-report=xml:reports/coverage-${{ matrix.python-version }}.xml tests
 
-    - name: Report coverage
+    - name: Analyze with SonarCloud
+      uses: sonarsource/sonarcloud-github-action@master
       env:
-        CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
-      run: |
-        GIT_BRANCH=$GITHUB_REF GIT_COMMIT_SHA=$GITHUB_SHA ./cc-test-reporter after-build -t coverage.py --exit-code $?
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 02056dd..58f6281 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,36 +34,37 @@ jobs:
           cd dist
           echo "::set-output name=source::$(ls *.tar.gz)"
           echo "::set-output name=wheel::$(ls *.whl)"
+          echo "::set-output name=version::$(poetry version | awk '{print $2}')"
       
-#      - name: Create a Release
-#        id: create_release
-#        uses: actions/create-release@v1
-#        env:
-#          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-#        with:
-#          tag_name: ${{ github.ref }}
-#          release_name: Pre-release ${{ github.ref }}
-#          prerelease: true
-#
-#      - name: Upload Release Wheel
-#        uses: actions/upload-release-asset@v1
-#        env:
-#          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-#        with:
-#          upload_url: ${{ steps.create_release.outputs.upload_url }}
-#          asset_path: ./dist/${{ steps.build_package.outputs.wheel }}
-#          asset_name: ${{ steps.build_package.outputs.wheel }}
-#          asset_content_type: application/x-pywheel+zip
-#
-#      - name: Upload Release Source
-#        uses: actions/upload-release-asset@v1
-#        env:
-#          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-#        with:
-#          upload_url: ${{ steps.create_release.outputs.upload_url }}
-#          asset_path: ./dist/${{ steps.build_package.outputs.source }}
-#          asset_name: ${{ steps.build_package.outputs.source }}
-#          asset_content_type: application/x-tar+gzip
+      - name: Create a Release
+        id: create_release
+        uses: actions/create-release@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          tag_name: ${{ steps.build_package.outputs.version }}
+          release_name: Release ${{ steps.build_package.outputs.version }}
+          prerelease: false
+
+      - name: Upload Release Wheel
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./dist/${{ steps.build_package.outputs.wheel }}
+          asset_name: ${{ steps.build_package.outputs.wheel }}
+          asset_content_type: application/x-pywheel+zip
+
+      - name: Upload Release Source
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./dist/${{ steps.build_package.outputs.source }}
+          asset_name: ${{ steps.build_package.outputs.source }}
+          asset_content_type: application/x-tar+gzip
 
       - name: Upload to PyPI
         id: upload_to_pypi
diff --git a/.gitignore b/.gitignore
index 2383875..d4765d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
 dist
 *.egg-info
 build
+reports
 _build
 .tox
 .eggs
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index def1332..77c20d2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,7 +14,7 @@ repos:
     rev: v2.4.4
     hooks:
       - id: pylint
-        files: pyapp/.*
+        files: src
         args: [
           --disable=import-error,
         ]
diff --git a/HISTORY b/HISTORY
index 55c37ca..4414964 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,35 @@
+4.6.0
+=====
+
+Features
+--------
+
+- Builtin feature flags support for enabling functionality at runtime
+
+Changes
+-------
+
+- Change to initial logger so early log commands are replayed to the entire
+  logging framework after logging has been configured. This could cause initial
+  log messages to be displayed subtly different however log messages will follow
+  the rules defined in your logging configuration.
+
+
+4.5.0
+=====
+
+Features
+--------
+
+- Add TOML support for remote configuration.
+
+
+Security Updates
+----------------
+
+- By default require TLS 1.2 for HTTPS connections to fetch remote configuration.
+
+
 4.4.0
 =====
 
diff --git a/README.rst b/README.rst
index 3666d13..7214197 100644
--- a/README.rst
+++ b/README.rst
@@ -6,42 +6,42 @@ pyApp - A python application framework
 
 As of pyApp 4.0, Python < 3.6 is no longer supported.
 
-+---------+---------------------------------------------------------------------------------------+
-| Docs    | .. image:: https://readthedocs.org/projects/pyapp/badge/?version=latest               |
-|         |    :target: https://docs.pyapp.info/                                                  |
-|         |    :alt: ReadTheDocs                                                                  |
-+---------+---------------------------------------------------------------------------------------+
-| Build   | .. image:: https://img.shields.io/travis/pyapp-org/pyapp.svg?style=flat               |
-|         |    :target: https://travis-ci.org/pyapp-org/pyapp                                     |
-|         |    :alt: Travis CI Status                                                             |
-|         | .. image:: https://api.dependabot.com/badges/status?host=github&repo=pyapp-org/pyapp  |
-|         |    :target: https://dependabot.com                                                    |
-|         |    :alt: Dependabot Status                                                            |
-+---------+---------------------------------------------------------------------------------------+
-| Quality | .. image:: https://api.codeclimate.com/v1/badges/58f9ffacb711c992610d/maintainability |
-|         |    :target: https://codeclimate.com/github/pyapp-org/pyapp/maintainability            |
-|         |    :alt: Maintainability                                                              |
-|         | .. image:: https://api.codeclimate.com/v1/badges/58f9ffacb711c992610d/test_coverage   |
-|         |    :target: https://codeclimate.com/github/pyapp-org/pyapp/test_coverage              |
-|         |    :alt: Test Coverage                                                                |
-|         | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg                 |
-|         |    :target: https://github.com/ambv/black                                             |
-|         |    :alt: Once you go Black...                                                         |
-+---------+---------------------------------------------------------------------------------------+
-| Package | .. image:: https://img.shields.io/pypi/v/pyapp                                        |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         |    :alt: Latest Version                                                               |
-|         | .. image:: https://img.shields.io/pypi/pyversions/pyapp                               |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         | .. image:: https://img.shields.io/pypi/l/pyapp                                        |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         | .. image:: https://img.shields.io/pypi/wheel/pyapp                                    |
-|         |    :alt: PyPI - Wheel                                                                 |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-+---------+---------------------------------------------------------------------------------------+
-
-pyApp takes care of the boring boilerplate code for building a CLI, manageing 
-settings and much more so you can focus on your buisness logic.
++---------+------------------------------------------------------------------------------------------------------------+
+| Docs    | .. image:: https://readthedocs.org/projects/pyapp/badge/?version=latest                                    |
+|         |    :target: https://docs.pyapp.info/                                                                       |
+|         |    :alt: ReadTheDocs                                                                                       |
++---------+------------------------------------------------------------------------------------------------------------+
+| Build   | .. image:: https://api.dependabot.com/badges/status?host=github&repo=pyapp-org/pyapp                       |
+|         |    :target: https://dependabot.com                                                                         |
+|         |    :alt: Dependabot Status                                                                                 |
++---------+------------------------------------------------------------------------------------------------------------+
+| Quality | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=sqale_rating    |
+|         |    :target: https://sonarcloud.io/dashboard?id=pyapp-org_pyapp                                             |
+|         |    :alt: Maintainability                                                                                   |
+|         | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=security_rating |
+|         |    :target: https://sonarcloud.io/project/security_hotspots                                                |
+|         |    :alt: Security                                                                                          |
+|         | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=coverage        |
+|         |    :target: https://sonarcloud.io/code?id=pyapp-org_pyapp                                                  |
+|         |    :alt: Test Coverage                                                                                     |
+|         | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg                                      |
+|         |    :target: https://github.com/ambv/black                                                                  |
+|         |    :alt: Once you go Black...                                                                              |
++---------+------------------------------------------------------------------------------------------------------------+
+| Package | .. image:: https://img.shields.io/pypi/v/pyapp                                                             |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         |    :alt: Latest Version                                                                                    |
+|         | .. image:: https://img.shields.io/pypi/pyversions/pyapp                                                    |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         | .. image:: https://img.shields.io/pypi/l/pyapp                                                             |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         | .. image:: https://img.shields.io/pypi/wheel/pyapp                                                         |
+|         |    :alt: PyPI - Wheel                                                                                      |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
++---------+------------------------------------------------------------------------------------------------------------+
+
+pyApp takes care of the boring boilerplate code for building a CLI, managing
+settings and much more so you can focus on your business logic.
 
 So what do we handle?
 =====================
diff --git a/docs/developers.rst b/docs/developers.rst
index 60692bd..07ffaf1 100644
--- a/docs/developers.rst
+++ b/docs/developers.rst
@@ -12,7 +12,7 @@ met:
 - Ensure your code has unit test coverage (using pyTest). Unittests should be
   designed to be as fast as possible.
 
-- Ensure your code passes the pyLint checks (this is part of the travis build).
+- Ensure your code passes the pyLint checks (this is part of the automated build).
 
 - Update the docs with the details if required.
 
diff --git a/docs/extensions.rst b/docs/extensions.rst
index 14aec53..8cfec1e 100644
--- a/docs/extensions.rst
+++ b/docs/extensions.rst
@@ -9,35 +9,60 @@ Available Extensions
 pyApp Developed
 ---------------
 
-Extensions available for use:
+🔌 SQLAlchemy - `pyapp.sqlalchemy`_
 
-- `pyapp.aiobotocore <https://github.com/pyapp-org/pyapp.aiobotocore>`_ -
-  Factory for `Async AWS client library <https://github.com/aio-libs/aiobotocore>`_
-- `pyapp.aio_pika <https://github.com/pyapp-org/pyapp.aio_pika>`_ -
-  Factory for `Async client for AMQP <https://github.com/mosquito/aio-pika/>`_
-- `pyapp.redis <https://github.com/pyapp-org/pyapp.redis>`_ -
-  Factory for `redis-py <https://github.com/andymccurdy/redis-py>`_
-- `pyapp.sqlalchemy <https://github.com/pyapp-org/pyapp.sqlalchemy>`_ -
-  Factory for `SQLALchemy <https://www.sqlalchemy.org>`_
+🔌 Redis - `pyapp.redis`_
 
+In Beta
+~~~~~~~
 
-Extensions in beta:
+🐛 Rollbar - `pyapp.rollbar`_
 
-- `pyapp-messaging <https://github.com/pyapp-org/pyapp-messaging>`_ -
-  Abstract messaging framework for interacting with Pub/Sub and Message Queues.
+📧 AIO SMTPlib - `pyapp.aiosmtplib`_ Extension for aiosmtplib
 
-Extensions in development:
+☁ Boto3 - `pyapp.boto3`_
 
-- `pyapp.boto3 <https://github.com/pyapp-org/pyapp.boto3>`_ -
-  Factory for `AWS client library <https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`_
-- `pyapp.smtp <https://github.com/pyapp-org/pyapp.SMTP>`_ -
-  Interface to `smtplib <https://docs.python.org/3/library/smtplib.html>`_
+☁ AIOBotocore - `pyapp.aiobotocore`_
+
+📨 Messaging - `pyapp.messaging`_ - Extension to provide abstract interfaces for Message Queues.
+
+- 📨 AWS Messaging - `pyapp.messaging-aws`_ - Messaging extension for AWS (SQS/SNS)
+
+In development
+~~~~~~~~~~~~~~
+
+📧 SMTP - `pyapp.SMTP`_
+
+📨 Aio-Pika - `pyapp.aiopika`_ - Messaging extension for pika (RabbitMQ/AMQP)
+
+🔌 PySpark - `pyapp.pyspark`_ - Extension for PySpark
+
+🔎 Elastic Search - `pyapp.elasticsearch`_ - Extension for Elasticsearch
+
+Coming soon
+-----------
+
+📨 AMQP Messaging - Messaging extension for AMQP (RabbitMQ)
+
+.. _pyapp.sqlalchemy: https://www.github.com/pyapp-org/pyapp.sqlalchemy
+.. _pyapp.redis: https://www.github.com/pyapp-org/pyapp.redis
+.. _pyapp.aiobotocore: https://www.github.com/pyapp-org/pyapp.aiobotocore
+.. _pyapp.SMTP: https://www.github.com/pyapp-org/pyapp.SMTP
+.. _pyapp.boto3: https://www.github.com/pyapp-org/pyapp.boto3
+.. _pyapp.rollbar: https://www.github.com/pyapp-org/pyapp.rollbar
+.. _pyapp.aiosmtplib: https://www.github.com/pyapp-org/pyapp.aiosmtplib
+.. _pyapp.messaging: https://www.github.com/pyapp-org/pyapp-messaging
+.. _pyapp.messaging-aws: https://www.github.com/pyapp-org/pyapp-messaging-aws
+.. _pyapp.aiopika: https://www.github.com/pyapp-org/pyapp.aiopika
+.. _pyapp.pyspark: https://www.github.com/pyapp-org/pyapp.pyspark
+.. _pyapp.elasticsearch: https://www.github.com/pyapp-org/pyapp.elasticsearch
 
 .. note::
     The development status of these projects may have changed from when this
     documentation was generated, see the repository (or PyPi) of the extension
     package for up to date status.
 
+
 Developing an Extension
 =======================
 
diff --git a/docs/getting-started.rst b/docs/getting-started.rst
index a490fe7..012f5b6 100644
--- a/docs/getting-started.rst
+++ b/docs/getting-started.rst
@@ -19,11 +19,8 @@ the ``pyApp`` Cookiecutter to generate an application.
 Install cookie-cutter from pip and use the template to start your initial
 project::
 
-    pip install cookiecutter
-    cookiecutter gh:pyapp-org/pyapp.cookiecutter
-
-
-.. tip:: You can skip this step if you want to just get the info.
+    > pip install cookiecutter
+    > cookiecutter gh:pyapp-org/pyapp.cookiecutter
 
 
 Project Structure
@@ -32,8 +29,8 @@ Project Structure
 The basic structure of a ``pyApp`` application package consists of the following::
 
     - myapp
-      |- __init__.py          Standard Python package initialisation
-      |- __main__.py          Standard Python main entry point
+      |- __init__.py          Python package initialisation
+      |- __main__.py          Python main entry point
       |- cli.py               The CLI (referenced from __main__)
       |- default_settings.py  Definition of default runtime configuration
       |- checks.py            Application specific checks
@@ -43,26 +40,14 @@ The basic structure of a ``pyApp`` application package consists of the following
 ---------------
 
 The presence of an ``__init__.py`` file in a folder makes Python use the folder
-as a package and contains initialisation code for the package. ``pyApp`` expects
-this file to contain a number of special variables that provide information to
-the framework on how to setup the application.
-
-The special variables are as follows:
+as a package and contains initialisation code for the package. ``pyApp`` also
+uses this file to find information for the application.
 
 ``__version__``
     The version number of the application, it is recommended that this be a
     semantic version. ``pyApp`` also provides tools for this to be fetched from
     the installed package list.
 
-``__default_settings__``
-    Either an absolute or relative reference (from the ``__init__`` file) to the
-    applications default settings. This setting is optional and the default
-    location is ``.default_settings``.
-
-``__checks__``
-    Either an absolute or relative reference (from the ``__init__`` file) to the
-    applications checks file. This setting is optional and the default location
-    is ``.checks``.
 
 ``__main__.py``
 ---------------
@@ -76,33 +61,100 @@ function in the *cli* module.
 
 This is where the ``pyApp`` ``CliApplication`` instance is defined. The
 ``CliApplication`` class handles initialisation of application services before
-handing over execution to the command handler of the command specified from the
+handing over execution to the command handler of the command requested by the
 CLI.
 
-Adding CLI Commands
-~~~~~~~~~~~~~~~~~~~
 
-CLI commands are defined
+Setting up the CLI
+~~~~~~~~~~~~~~~~~~
+
+Adding a CLI consists of two steps, defining a ``CliApplication`` instance and
+using the ``CliApplication.command`` decorator to functions to add commands to
+your application.
+
+First create a ``CliApplication`` instance:
+
+.. code-block:: python
+
+    from pyapp.app import CliApplication
+
+    # Define our application instance
+    app = CliApplication(
+        description="My Application"
+    )
+
+    # Define our main entry point
+    main = app.dispatch
+
+``CliApplication`` has many options to customise how it works, these are all
+provided with defaults but can be customised to change the behaviour, see the
+API documentation for more information on other options.
+
+Next CLI commands are created by applying the ``CliApplication.command`` argument
+to a python function.
+
+.. code-block:: python
+
+  @app.command
+  def greeting(name: str, *, greeting: str = "Hello"):
+      """
+      Provide a greeting
+      """
+      print(f"{greeting} {name}")
 
+This example adds the `greeting` command to the CLI that requires a name and
+allows for an optional greeting to be provided. It can be called using::
 
-``default_settings.py``
------------------------
+  > my_app greeting Guido --greeting Hallo
+  Hallo Guido
 
-The default runtime settings for your application. These provide the defaults on
-which environment/end user specific settings can be applied. This is a normal
-Python module allowing for complex behaviours to be defined. Any variable that
-consists of all upper-case characters (as well as `_`) is considered a setting
-and is imported into the settings container.
+
+Application Settings
+~~~~~~~~~~~~~~~~~~~~
+
+All of your applications settings are defined in the ``default_settings.py``
+file that is located in your applications main package. These settings are
+loaded by the CliApplication on startup and combine with settings from pyApp
+extensions as well as runtime settings.
+
+The default settings file is a normal python module allows for complex
+behaviours to be defined. Any variable that consists of all upper-case
+characters (including `_`) is considered a setting and is imported into the
+settings container.
 
 Once an application has been initialised the final set of runtime settings is
-available from the settings container ``pyapp.conf.settings``.
+available from the settings container ``pyapp.conf.settings``:
+
+.. code-block:: python
+
+    from pyapp.conf import settings
+
+    print(settings.MY_SETTING)
+
+
+Runtime Checks
+~~~~~~~~~~~~~~
+
+These are functions that are called by the checks report to perform a check
+against the current settings to assert that the current runtime environment is
+correct. This can include:
+
+- Checking settings are valid
+- Confirming connectivity/access to a database
+- Checking free space on the filesystem
+
+These checks can help to diagnose issues, perform basic validation of the
+runtime environment and provide application monitoring.
+
+A check is defined by:
+
+.. code-block:: python
+
+    from pyapp import checks
 
-``checks.py``
--------------
+    @checks.register
+    def debug_is_enabled(settings, **_):
+        if settings.DEBUG:
+            return checks.Warning("Debug mode is enabled")
 
-The location for any application specific run time checks are defined and
-registered. Checks allow your application to ensure configuration is correct or
-to confirm aspects of the runtime environment are as they are expected to be.
-This can included confirming access to a database or other services. These are
-invaluable post deployment to confirm a successful deployment or to diagnose
-problems.
+In this example a check returns a warning if DEBUG is ``True`` in settings.
diff --git a/docs/index.rst b/docs/index.rst
index 17ae6ee..d61b703 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -3,46 +3,51 @@
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
+
+
 Welcome to PyApp's documentation!
 =================================
 
 *Let us handle the boring stuff!*
 
-+---------+---------------------------------------------------------------------------------------+
-| Docs    | .. image:: https://readthedocs.org/projects/pyapp/badge/?version=latest               |
-|         |    :target: https://docs.pyapp.info/                                                  |
-|         |    :alt: ReadTheDocs                                                                  |
-+---------+---------------------------------------------------------------------------------------+
-| Build   | .. image:: https://img.shields.io/travis/pyapp-org/pyapp.svg?style=flat               |
-|         |    :target: https://travis-ci.org/pyapp-org/pyapp                                     |
-|         |    :alt: Travis CI Status                                                             |
-+---------+---------------------------------------------------------------------------------------+
-| Quality | .. image:: https://api.codeclimate.com/v1/badges/58f9ffacb711c992610d/maintainability |
-|         |    :target: https://codeclimate.com/github/pyapp-org/pyapp/maintainability            |
-|         |    :alt: Maintainability                                                              |
-|         | .. image:: https://api.codeclimate.com/v1/badges/58f9ffacb711c992610d/test_coverage   |
-|         |    :target: https://codeclimate.com/github/pyapp-org/pyapp/test_coverage              |
-|         |    :alt: Test Coverage                                                                |
-|         | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg                 |
-|         |    :target: https://github.com/ambv/black                                             |
-|         |    :alt: Once you go Black...                                                         |
-+---------+---------------------------------------------------------------------------------------+
-| Package | .. image:: https://img.shields.io/pypi/v/pyapp.svg                                    |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         |    :alt: Latest Version                                                               |
-|         | .. image:: https://img.shields.io/pypi/pyversions/pyapp.svg                           |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         | .. image:: https://img.shields.io/pypi/l/pyapp.svg                                    |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-|         | .. image:: https://img.shields.io/pypi/wheel/pyapp.svg                                |
-|         |    :target: https://pypi.io/pypi/pyapp/                                               |
-+---------+---------------------------------------------------------------------------------------+
-
-Many features inspired by Django, but modified to be more general for use
-outside of web applications.
-
-.. note::
-   Python < 3.6 is no longer supported
+As of pyApp 4.0, Python < 3.6 is no longer supported.
+
++---------+------------------------------------------------------------------------------------------------------------+
+| Docs    | .. image:: https://readthedocs.org/projects/pyapp/badge/?version=latest                                    |
+|         |    :target: https://docs.pyapp.info/                                                                       |
+|         |    :alt: ReadTheDocs                                                                                       |
++---------+------------------------------------------------------------------------------------------------------------+
+| Build   | .. image:: https://api.dependabot.com/badges/status?host=github&repo=pyapp-org/pyapp                       |
+|         |    :target: https://dependabot.com                                                                         |
+|         |    :alt: Dependabot Status                                                                                 |
++---------+------------------------------------------------------------------------------------------------------------+
+| Quality | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=sqale_rating    |
+|         |    :target: https://sonarcloud.io/dashboard?id=pyapp-org_pyapp                                             |
+|         |    :alt: Maintainability                                                                                   |
+|         | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=security_rating |
+|         |    :target: https://sonarcloud.io/project/security_hotspots                                                |
+|         |    :alt: Security                                                                                          |
+|         | .. image:: https://sonarcloud.io/api/project_badges/measure?project=pyapp-org_pyapp&metric=coverage        |
+|         |    :target: https://sonarcloud.io/code?id=pyapp-org_pyapp                                                  |
+|         |    :alt: Test Coverage                                                                                     |
+|         | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg                                      |
+|         |    :target: https://github.com/ambv/black                                                                  |
+|         |    :alt: Once you go Black...                                                                              |
++---------+------------------------------------------------------------------------------------------------------------+
+| Package | .. image:: https://img.shields.io/pypi/v/pyapp                                                             |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         |    :alt: Latest Version                                                                                    |
+|         | .. image:: https://img.shields.io/pypi/pyversions/pyapp                                                    |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         | .. image:: https://img.shields.io/pypi/l/pyapp                                                             |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
+|         | .. image:: https://img.shields.io/pypi/wheel/pyapp                                                         |
+|         |    :alt: PyPI - Wheel                                                                                      |
+|         |    :target: https://pypi.io/pypi/pyapp/                                                                    |
++---------+------------------------------------------------------------------------------------------------------------+
+
+pyApp takes care of the boring boilerplate code for building a CLI, managing
+settings and much more so you can focus on your business logic.
 
 
 So what do we handle?
@@ -59,6 +64,9 @@ So what do we handle?
 
 - Dependency Injection - Easy to use dependency injection without complicated setup.
 
+- Feature Flags - Simple methods to enable and disable features in your application
+  at runtime.
+
 - Checks - A framework for checking settings are correct and environment is
   operating correctly (your ops team will love you)?
 
@@ -67,11 +75,9 @@ So what do we handle?
   specific default settings.
 
 - Application - Provides a extensible and simple CLI interface for running
-  commands (including Async), comes with built-in commands to execute check, setting
+  commands (including async), comes with built-in commands to execute check, setting
   and extension reports.
 
-  - Automatic building of CLI arguments from a command signature *new in 4.4!*
-
 - Logging - Initialise and apply sane logging defaults.
 
 - Highly tested and ready for production use.
diff --git a/docs/recipes/integration-with-django.rst b/docs/recipes/integration-with-django.rst
index bba0ad1..815d43e 100644
--- a/docs/recipes/integration-with-django.rst
+++ b/docs/recipes/integration-with-django.rst
@@ -11,7 +11,9 @@ Django. There are two options here:
 1. Create a new Django App
 2. Use an appropriate existing app.
 
-Add to the ``ready`` method of your *App* class::
+Add to the ``ready`` method of your *App* class:
+
+.. code-block:: python
 
     class MyApp:
         def ready(self):
diff --git a/docs/recipes/single-script.rst b/docs/recipes/single-script.rst
index 0a497d8..c56f20e 100644
--- a/docs/recipes/single-script.rst
+++ b/docs/recipes/single-script.rst
@@ -21,4 +21,3 @@ Example:
 
     if __name__ == "__main__":
         app.dispatch()
-
diff --git a/docs/reference/feature-flags.rst b/docs/reference/feature-flags.rst
new file mode 100644
index 0000000..abac932
--- /dev/null
+++ b/docs/reference/feature-flags.rst
@@ -0,0 +1,1 @@
+.. automodule:: pyapp.feature_flags
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index e785ac7..e03f159 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -14,4 +14,5 @@ Contents:
    conf/helpers
    events
    injection
+   feature-flags
    extensions
diff --git a/noxfile.py b/noxfile.py
index 5227b1d..af71d68 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -2,7 +2,8 @@
 from nox.sessions import Session
 
 
-@nox.session(python=("3.6", "3.7", "3.8"), reuse_venv=True)
+@nox.session(python=("3.6", "3.7", "3.8", "3.9"), reuse_venv=True)
 def tests(session: Session):
+    session.install("poetry")
     session.run("poetry", "install")
     session.run("pytest")
diff --git a/poetry.lock b/poetry.lock
index d38b8b5..94f555d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,202 +1,198 @@
 [[package]]
-category = "dev"
-description = "A configurable sidebar-enabled Sphinx theme"
 name = "alabaster"
+version = "0.7.12"
+description = "A configurable sidebar-enabled Sphinx theme"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.7.12"
 
 [[package]]
-category = "dev"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.4.4"
 
 [[package]]
-category = "main"
-description = "Bash tab completion for argparse"
 name = "argcomplete"
+version = "1.12.3"
+description = "Bash tab completion for argparse"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.12.0"
 
 [package.dependencies]
-[package.dependencies.importlib-metadata]
-python = ">=3.6,<3.7"
-version = ">=0.23,<2"
+importlib-metadata = {version = ">=0.23,<5", markers = "python_version == \"3.6\" or python_version == \"3.7\""}
 
 [package.extras]
 test = ["coverage", "flake8", "pexpect", "wheel"]
 
 [[package]]
-category = "dev"
-description = "Atomic file writes."
-marker = "sys_platform == \"win32\""
 name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.4.0"
 
 [[package]]
-category = "dev"
-description = "Classes Without Boilerplate"
 name = "attrs"
+version = "20.3.0"
+description = "Classes Without Boilerplate"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.1.0"
 
 [package.extras]
-dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
-docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
 
 [[package]]
-category = "dev"
-description = "Internationalization utilities"
 name = "babel"
+version = "2.9.0"
+description = "Internationalization utilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.8.0"
 
 [package.dependencies]
 pytz = ">=2015.7"
 
 [[package]]
-category = "dev"
-description = "Python package for providing Mozilla's CA Bundle."
 name = "certifi"
+version = "2020.12.5"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "2020.6.20"
 
 [[package]]
-category = "dev"
-description = "Universal encoding detector for Python 2 and 3"
 name = "chardet"
+version = "4.0.0"
+description = "Universal encoding detector for Python 2 and 3"
+category = "dev"
 optional = false
-python-versions = "*"
-version = "3.0.4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
-category = "main"
-description = "Cross-platform colored terminal text."
 name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.4.3"
 
 [[package]]
-category = "dev"
-description = "Log formatting with colors!"
 name = "colorlog"
+version = "4.7.2"
+description = "Log formatting with colors!"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "4.2.1"
 
 [package.dependencies]
-colorama = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
 
 [[package]]
-category = "dev"
-description = "Code coverage measurement for Python"
 name = "coverage"
+version = "5.5"
+description = "Code coverage measurement for Python"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "5.2.1"
 
 [package.extras]
 toml = ["toml"]
 
 [[package]]
-category = "dev"
-description = "Distribution utilities"
 name = "distlib"
+version = "0.3.1"
+description = "Distribution utilities"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.3.1"
 
 [[package]]
-category = "dev"
-description = "Docutils -- Python Documentation Utilities"
 name = "docutils"
+version = "0.16"
+description = "Docutils -- Python Documentation Utilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.16"
 
 [[package]]
-category = "dev"
-description = "A platform independent file lock."
 name = "filelock"
+version = "3.0.12"
+description = "A platform independent file lock."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "3.0.12"
 
 [[package]]
-category = "main"
-description = "Internationalized Domain Names in Applications (IDNA)"
 name = "idna"
+version = "2.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.10"
 
 [[package]]
-category = "dev"
-description = "Getting image size from png/jpeg/jpeg2000/gif file"
 name = "imagesize"
+version = "1.2.0"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.2.0"
 
 [[package]]
-category = "main"
-description = "Read metadata from Python packages"
-marker = "python_version < \"3.8\""
 name = "importlib-metadata"
+version = "3.7.0"
+description = "Read metadata from Python packages"
+category = "main"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.7.0"
+python-versions = ">=3.6"
 
 [package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
-category = "dev"
-description = "Read resources from Python packages"
-marker = "python_version < \"3.7\""
 name = "importlib-resources"
+version = "5.1.2"
+description = "Read resources from Python packages"
+category = "dev"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "3.0.0"
+python-versions = ">=3.6"
 
 [package.dependencies]
-[package.dependencies.zipp]
-python = "<3.8"
-version = ">=0.4"
+zipp = {version = ">=0.4", markers = "python_version < \"3.8\""}
 
 [package.extras]
-docs = ["sphinx", "rst.linker", "jaraco.packaging"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [[package]]
-category = "dev"
-description = "iniconfig: brain-dead simple config-ini parsing"
 name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.0.1"
 
 [[package]]
-category = "dev"
-description = "A very fast and expressive template engine."
 name = "jinja2"
+version = "2.11.3"
+description = "A very fast and expressive template engine."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.11.2"
 
 [package.dependencies]
 MarkupSafe = ">=0.23"
@@ -205,227 +201,207 @@ MarkupSafe = ">=0.23"
 i18n = ["Babel (>=0.8)"]
 
 [[package]]
-category = "dev"
-description = "Safely add untrusted strings to HTML/XML markup."
 name = "markupsafe"
-optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
 version = "1.1.1"
-
-[[package]]
+description = "Safely add untrusted strings to HTML/XML markup."
 category = "dev"
-description = "More routines for operating on iterables, beyond itertools"
-name = "more-itertools"
 optional = false
-python-versions = ">=3.5"
-version = "8.5.0"
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
 
 [[package]]
-category = "main"
-description = "multidict implementation"
 name = "multidict"
+version = "5.1.0"
+description = "multidict implementation"
+category = "main"
 optional = false
-python-versions = ">=3.5"
-version = "4.7.6"
+python-versions = ">=3.6"
 
 [[package]]
-category = "dev"
-description = "Flexible test automation."
 name = "nox"
+version = "2020.12.31"
+description = "Flexible test automation."
+category = "dev"
 optional = false
-python-versions = ">=3.5"
-version = "2020.8.22"
+python-versions = ">=3.6"
 
 [package.dependencies]
 argcomplete = ">=1.9.4,<2.0"
 colorlog = ">=2.6.1,<5.0.0"
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 py = ">=1.4.0,<2.0.0"
 virtualenv = ">=14.0.0"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = "*"
-
 [package.extras]
 tox_to_nox = ["jinja2", "tox"]
 
 [[package]]
-category = "dev"
-description = "Core utilities for Python packages"
 name = "packaging"
+version = "20.9"
+description = "Core utilities for Python packages"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.4"
 
 [package.dependencies]
 pyparsing = ">=2.0.2"
-six = "*"
 
 [[package]]
-category = "dev"
-description = "plugin and hook calling mechanisms for python"
 name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.1"
 
 [package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 
 [package.extras]
 dev = ["pre-commit", "tox"]
 
 [[package]]
-category = "dev"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
 name = "py"
+version = "1.10.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.9.0"
 
 [[package]]
-category = "dev"
-description = "Pygments is a syntax highlighting package written in Python."
 name = "pygments"
+version = "2.8.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "2.6.1"
 
 [[package]]
-category = "dev"
-description = "Python parsing module"
 name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "dev"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.7"
 
 [[package]]
-category = "dev"
-description = "pytest: simple powerful testing with Python"
 name = "pytest"
+version = "6.2.2"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
 optional = false
-python-versions = ">=3.5"
-version = "6.0.1"
+python-versions = ">=3.6"
 
 [package.dependencies]
-atomicwrites = ">=1.0"
-attrs = ">=17.4.0"
-colorama = "*"
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 iniconfig = "*"
-more-itertools = ">=4.0.0"
 packaging = "*"
-pluggy = ">=0.12,<1.0"
+pluggy = ">=0.12,<1.0.0a1"
 py = ">=1.8.2"
 toml = "*"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
-
 [package.extras]
-checkqa_mypy = ["mypy (0.780)"]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
 [[package]]
-category = "dev"
-description = "Pytest support for asyncio."
 name = "pytest-asyncio"
+version = "0.15.1"
+description = "Pytest support for asyncio."
+category = "dev"
 optional = false
-python-versions = ">= 3.5"
-version = "0.14.0"
+python-versions = ">= 3.6"
 
 [package.dependencies]
 pytest = ">=5.4.0"
 
 [package.extras]
-testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"]
+testing = ["coverage", "hypothesis (>=5.7.1)"]
 
 [[package]]
-category = "dev"
-description = "Pytest plugin for measuring coverage."
 name = "pytest-cov"
+version = "2.11.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.10.1"
 
 [package.dependencies]
-coverage = ">=4.4"
+coverage = ">=5.2.1"
 pytest = ">=4.6"
 
 [package.extras]
-testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
+testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"]
 
 [[package]]
-category = "dev"
-description = "World timezone definitions, modern and historical"
 name = "pytz"
+version = "2021.1"
+description = "World timezone definitions, modern and historical"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "2020.1"
 
 [[package]]
-category = "main"
-description = "YAML parser and emitter for Python"
 name = "pyyaml"
+version = "5.4.1"
+description = "YAML parser and emitter for Python"
+category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "5.3.1"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 
 [[package]]
-category = "dev"
-description = "Python HTTP for Humans."
 name = "requests"
+version = "2.25.1"
+description = "Python HTTP for Humans."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.24.0"
 
 [package.dependencies]
 certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<4"
+chardet = ">=3.0.2,<5"
 idna = ">=2.5,<3"
-urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
 security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
 [[package]]
-category = "dev"
-description = "Python 2 and 3 compatibility utilities"
 name = "six"
+version = "1.15.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.15.0"
 
 [[package]]
-category = "dev"
-description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
 name = "snowballstemmer"
+version = "2.1.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "2.0.0"
 
 [[package]]
-category = "dev"
-description = "Python documentation generator"
 name = "sphinx"
+version = "3.5.3"
+description = "Python documentation generator"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "3.2.1"
 
 [package.dependencies]
-Jinja2 = ">=2.3"
-Pygments = ">=2.0"
 alabaster = ">=0.7,<0.8"
 babel = ">=1.3"
-colorama = ">=0.3.5"
+colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
 docutils = ">=0.12"
 imagesize = "*"
+Jinja2 = ">=2.3"
 packaging = "*"
+Pygments = ">=2.0"
 requests = ">=2.5.0"
-setuptools = "*"
 snowballstemmer = ">=1.1"
 sphinxcontrib-applehelp = "*"
 sphinxcontrib-devhelp = "*"
@@ -436,171 +412,162 @@ sphinxcontrib-serializinghtml = "*"
 
 [package.extras]
 docs = ["sphinxcontrib-websupport"]
-lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"]
-test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"]
+lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"]
+test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
 name = "sphinxcontrib-applehelp"
+version = "1.0.2"
+description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
 name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
 name = "sphinxcontrib-htmlhelp"
+version = "1.0.3"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.3"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest", "html5lib"]
 
 [[package]]
-category = "dev"
-description = "A sphinx extension which renders display math in HTML via JavaScript"
 name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.1"
 
 [package.extras]
 test = ["pytest", "flake8", "mypy"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
 name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.3"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
 name = "sphinxcontrib-serializinghtml"
+version = "1.1.4"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.1.4"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "Python Library for Tom's Obvious, Minimal Language"
 name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "main"
 optional = false
-python-versions = "*"
-version = "0.10.1"
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
-category = "main"
-description = "Backported and Experimental Type Hints for Python 3.5+"
-marker = "python_version < \"3.8\""
 name = "typing-extensions"
+version = "3.7.4.3"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.7.4.3"
 
 [[package]]
-category = "dev"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
 name = "urllib3"
+version = "1.26.4"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.10"
 
 [package.extras]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 brotli = ["brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
 
 [[package]]
-category = "dev"
-description = "Virtual Python Environment builder"
 name = "virtualenv"
+version = "20.4.2"
+description = "Virtual Python Environment builder"
+category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "20.0.31"
 
 [package.dependencies]
 appdirs = ">=1.4.3,<2"
 distlib = ">=0.3.1,<1"
 filelock = ">=3.0.0,<4"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""}
 six = ">=1.9.0,<2"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12,<2"
-
-[package.dependencies.importlib-resources]
-python = "<3.7"
-version = ">=1.0"
-
 [package.extras]
 docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
-testing = ["coverage (>=5)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
 
 [[package]]
-category = "main"
-description = "Yet another URL library"
 name = "yarl"
+version = "1.6.3"
+description = "Yet another URL library"
+category = "main"
 optional = false
-python-versions = ">=3.5"
-version = "1.5.1"
+python-versions = ">=3.6"
 
 [package.dependencies]
 idna = ">=2.0"
 multidict = ">=4.0"
-
-[package.dependencies.typing-extensions]
-python = "<3.8"
-version = ">=3.7.4"
+typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
 
 [[package]]
-category = "main"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-marker = "python_version < \"3.8\""
 name = "zipp"
+version = "3.4.1"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
 optional = false
 python-versions = ">=3.6"
-version = "3.1.0"
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["jaraco.itertools", "func-timeout"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [extras]
+toml = ["toml"]
 yaml = ["pyyaml"]
 
 [metadata]
-content-hash = "1e10703dc91784c261d79d52ad4bbfe119f1ca2c01e7c9fad3210f96c6d068ee"
+lock-version = "1.1"
 python-versions = "^3.6"
+content-hash = "b6b2503c3f9e5971888c035f7c52ce74ffcf7a06ef196f92fea271ba83d77e74"
 
 [metadata.files]
 alabaster = [
@@ -612,72 +579,90 @@ appdirs = [
     {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
 ]
 argcomplete = [
-    {file = "argcomplete-1.12.0-py2.py3-none-any.whl", hash = "sha256:91dc7f9c7f6281d5a0dce5e73d2e33283aaef083495c13974a7dd197a1cdc949"},
-    {file = "argcomplete-1.12.0.tar.gz", hash = "sha256:2fbe5ed09fd2c1d727d4199feca96569a5b50d44c71b16da9c742201f7cc295c"},
+    {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
+    {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
 ]
 atomicwrites = [
     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-20.1.0-py2.py3-none-any.whl", hash = "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"},
-    {file = "attrs-20.1.0.tar.gz", hash = "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a"},
+    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
+    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
 ]
 babel = [
-    {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
-    {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
+    {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"},
+    {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"},
 ]
 certifi = [
-    {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
-    {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
+    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
+    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
 ]
 chardet = [
-    {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
-    {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
+    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
 ]
 colorama = [
-    {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
-    {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
 colorlog = [
-    {file = "colorlog-4.2.1-py2.py3-none-any.whl", hash = "sha256:43597fd822ce705190fc997519342fdaaf44b9b47f896ece7aa153ed4b909c74"},
-    {file = "colorlog-4.2.1.tar.gz", hash = "sha256:75e55822c3a3387d721579241e776de2cf089c9ef9528b1f09e8b04d403ad118"},
+    {file = "colorlog-4.7.2-py2.py3-none-any.whl", hash = "sha256:0a9dcdba6cab68e8a768448b418a858d73c52b37b6e8dea2568296faece393bd"},
+    {file = "colorlog-4.7.2.tar.gz", hash = "sha256:18d05b616438a75762d7d214b9ec3b05d274466c9f3ddd92807e755840c88251"},
 ]
 coverage = [
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
-    {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
-    {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
-    {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
-    {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
-    {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
-    {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
-    {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
-    {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
-    {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
-    {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
-    {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
-    {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
-    {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
-    {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
-    {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
-    {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
-    {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
-    {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
+    {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
+    {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
+    {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
+    {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
+    {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
+    {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
+    {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
+    {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
+    {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
+    {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
+    {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
+    {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
+    {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
+    {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
+    {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
+    {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
+    {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
+    {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
+    {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
+    {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
+    {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
+    {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
+    {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
+    {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
 ]
 distlib = [
     {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
@@ -700,20 +685,20 @@ imagesize = [
     {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
-    {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
+    {file = "importlib_metadata-3.7.0-py3-none-any.whl", hash = "sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614"},
+    {file = "importlib_metadata-3.7.0.tar.gz", hash = "sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa"},
 ]
 importlib-resources = [
-    {file = "importlib_resources-3.0.0-py2.py3-none-any.whl", hash = "sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"},
-    {file = "importlib_resources-3.0.0.tar.gz", hash = "sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3"},
+    {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"},
+    {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"},
 ]
 iniconfig = [
-    {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"},
-    {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"},
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 jinja2 = [
-    {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
-    {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
+    {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
+    {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
 ]
 markupsafe = [
     {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
@@ -734,113 +719,166 @@ markupsafe = [
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
     {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
 ]
-more-itertools = [
-    {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"},
-    {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"},
-]
 multidict = [
-    {file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"},
-    {file = "multidict-4.7.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a"},
-    {file = "multidict-4.7.6-cp35-cp35m-win32.whl", hash = "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5"},
-    {file = "multidict-4.7.6-cp35-cp35m-win_amd64.whl", hash = "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3"},
-    {file = "multidict-4.7.6-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87"},
-    {file = "multidict-4.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2"},
-    {file = "multidict-4.7.6-cp36-cp36m-win32.whl", hash = "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7"},
-    {file = "multidict-4.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463"},
-    {file = "multidict-4.7.6-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"},
-    {file = "multidict-4.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255"},
-    {file = "multidict-4.7.6-cp37-cp37m-win32.whl", hash = "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507"},
-    {file = "multidict-4.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c"},
-    {file = "multidict-4.7.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b"},
-    {file = "multidict-4.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7"},
-    {file = "multidict-4.7.6-cp38-cp38-win32.whl", hash = "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d"},
-    {file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"},
-    {file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"},
+    {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"},
+    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"},
+    {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"},
+    {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"},
+    {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"},
+    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"},
+    {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"},
+    {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"},
+    {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"},
+    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"},
+    {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"},
+    {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"},
+    {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"},
+    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"},
+    {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"},
+    {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"},
+    {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"},
 ]
 nox = [
-    {file = "nox-2020.8.22-py3-none-any.whl", hash = "sha256:55f8cab16bcfaaea08b141c83bf2b7c779e943518d0de6cd9c38cd8da95d11ea"},
-    {file = "nox-2020.8.22.tar.gz", hash = "sha256:efa5adcf1134012f96bcd0a496ccebd4c9e9da53a831888a2a779462440eebcf"},
+    {file = "nox-2020.12.31-py3-none-any.whl", hash = "sha256:f179d6990f7a0a9cebad01b9ecea34556518b8d3340dfcafdc1d85f2c1a37ea0"},
+    {file = "nox-2020.12.31.tar.gz", hash = "sha256:58a662070767ed4786beb46ce3a789fca6f1e689ed3ac15c73c4d0094e4f9dc4"},
 ]
 packaging = [
-    {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
-    {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
+    {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
+    {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
 ]
 pluggy = [
     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
 ]
 py = [
-    {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
-    {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
 pygments = [
-    {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
-    {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
+    {file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"},
+    {file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"},
 ]
 pyparsing = [
     {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 ]
 pytest = [
-    {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"},
-    {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"},
+    {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"},
+    {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"},
 ]
 pytest-asyncio = [
-    {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"},
-    {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"},
+    {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"},
+    {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"},
 ]
 pytest-cov = [
-    {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
-    {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
+    {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"},
+    {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"},
 ]
 pytz = [
-    {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
-    {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
+    {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
+    {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
 ]
 pyyaml = [
-    {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
-    {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
-    {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
-    {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
-    {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
-    {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
-    {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
-    {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
-    {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
-    {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
-    {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
+    {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
+    {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
+    {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
+    {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
+    {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
+    {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
+    {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
+    {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
+    {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
+    {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
+    {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
 ]
 requests = [
-    {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
-    {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
+    {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
+    {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
 ]
 six = [
     {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
     {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
 ]
 snowballstemmer = [
-    {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
-    {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
+    {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},
+    {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
 ]
 sphinx = [
-    {file = "Sphinx-3.2.1-py3-none-any.whl", hash = "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"},
-    {file = "Sphinx-3.2.1.tar.gz", hash = "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8"},
+    {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"},
+    {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"},
 ]
 sphinxcontrib-applehelp = [
     {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
@@ -867,8 +905,8 @@ sphinxcontrib-serializinghtml = [
     {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"},
 ]
 toml = [
-    {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
-    {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 typing-extensions = [
     {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
@@ -876,33 +914,53 @@ typing-extensions = [
     {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
 ]
 urllib3 = [
-    {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
-    {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
+    {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
+    {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.0.31-py2.py3-none-any.whl", hash = "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b"},
-    {file = "virtualenv-20.0.31.tar.gz", hash = "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc"},
+    {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
+    {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
 ]
 yarl = [
-    {file = "yarl-1.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb"},
-    {file = "yarl-1.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593"},
-    {file = "yarl-1.5.1-cp35-cp35m-win32.whl", hash = "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409"},
-    {file = "yarl-1.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317"},
-    {file = "yarl-1.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511"},
-    {file = "yarl-1.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e"},
-    {file = "yarl-1.5.1-cp36-cp36m-win32.whl", hash = "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f"},
-    {file = "yarl-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2"},
-    {file = "yarl-1.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a"},
-    {file = "yarl-1.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8"},
-    {file = "yarl-1.5.1-cp37-cp37m-win32.whl", hash = "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8"},
-    {file = "yarl-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d"},
-    {file = "yarl-1.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02"},
-    {file = "yarl-1.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a"},
-    {file = "yarl-1.5.1-cp38-cp38-win32.whl", hash = "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6"},
-    {file = "yarl-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692"},
-    {file = "yarl-1.5.1.tar.gz", hash = "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6"},
+    {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"},
+    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"},
+    {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"},
+    {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"},
+    {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"},
+    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"},
+    {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"},
+    {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"},
+    {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"},
+    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"},
+    {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"},
+    {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"},
+    {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"},
+    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"},
+    {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"},
+    {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"},
+    {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
 ]
 zipp = [
-    {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
-    {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
+    {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
+    {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 96ab100..c30391f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "rtd_poetry"
 
 [tool.poetry]
 name = "pyapp"
-version = "4.4.0"
+version = "4.6.0"
 description = "A Python application framework - Let us handle the boring stuff!"
 authors = ["Tim Savage <tim@savage.company>"]
 license = "BSD-3-Clause"
@@ -23,13 +23,15 @@ classifiers = [
     "Programming Language :: Python :: 3.6",
     "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
 ]
 packages = [
-    { include = "pyapp" },
+    { include = "pyapp", from = "src" },
     { include = "tests", format = "sdist" },
 ]
+include = ["HISTORY"]
 
 [tool.poetry.dependencies]
 python = "^3.6"
@@ -38,17 +40,19 @@ colorama = "*"
 yarl = "*"
 
 pyyaml = {version = "*", optional = true }
+toml = {version = "*", optional = true }
 
 [tool.poetry.dev-dependencies]
-pytest = "^6.0.1"
-pytest-cov = "^2.10.1"
-pytest-asyncio = "^0.14.0"
+pytest = "^6.2.2"
+pytest-cov = "^2.11.1"
+pytest-asyncio = "^0.15.1"
 nox = "*"
 docutils = "^0.16"
-sphinx = "^3.2.1"
+sphinx = "^3.5.3"
 
 [tool.poetry.extras]
 yaml = ["pyyaml"]
+toml = ["toml"]
 
 [tool.poetry.urls]
 "Bug Tracker" = "https://github.com/pyapp-org/pyapp/issues"
diff --git a/samples/sample/default_settings.py b/samples/sample/default_settings.py
index 4135a7f..171edef 100644
--- a/samples/sample/default_settings.py
+++ b/samples/sample/default_settings.py
@@ -10,4 +10,29 @@
 INCLUDE_SETTINGS = [
     f"file://{here}/more-settings.json",
     f"file://{here}/more-settings.yaml",
+    f"file://{here}/more-settings.toml",
 ]
+
+LOGGING = {
+    "formatters": {
+        "default": {"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s"},
+    },
+    "handlers": {
+        "console": {
+            "class": "logging.StreamHandler",
+            "formatter": "default",
+            "stream": "ext://sys.stdout",
+        },
+        "file": {
+            "class": "logging.FileHandler",
+            "formatter": "default",
+            "filename": "out.log",
+            "level": "ERROR",
+        },
+    },
+    "loggers": {"pyapp.conf": {"handlers": ["file"]}},
+    "root": {
+        # 'level' : 'INFO',  # Set from command line arg parser.
+        "handlers": ["console"],
+    },
+}
diff --git a/samples/sample/more-settings.toml b/samples/sample/more-settings.toml
new file mode 100644
index 0000000..747f315
--- /dev/null
+++ b/samples/sample/more-settings.toml
@@ -0,0 +1,3 @@
+[TOML_MESSAGES]
+one = "Message from 1..."
+two = "Message from 2..."
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..ea53d1c
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,15 @@
+sonar.projectKey=pyapp-org_pyapp
+sonar.organization=pyapp-org
+
+# Path to sources
+sonar.sources=.
+sonar.exclusions=samples/**,docs/**
+#sonar.inclusions=
+
+# Path to tests
+sonar.tests=tests
+#sonar.test.exclusions=
+#sonar.test.inclusions=
+
+# Source encoding
+sonar.sourceEncoding=UTF-8
diff --git a/pyapp/__init__.py b/src/pyapp/__init__.py
similarity index 100%
rename from pyapp/__init__.py
rename to src/pyapp/__init__.py
diff --git a/pyapp/app/__init__.py b/src/pyapp/app/__init__.py
similarity index 91%
rename from pyapp/app/__init__.py
rename to src/pyapp/app/__init__.py
index 02513d5..4ca00eb 100644
--- a/pyapp/app/__init__.py
+++ b/src/pyapp/app/__init__.py
@@ -141,6 +141,7 @@ def my_command(
 import os
 import sys
 from argparse import ArgumentParser
+from argparse import FileType
 from argparse import Namespace as CommandOptions
 from typing import Optional
 from typing import Sequence
@@ -151,6 +152,7 @@ def my_command(
 from . import init_logger
 from .. import conf
 from .. import extensions
+from .. import feature_flags
 from ..app import builtin_handlers
 from ..injection import register_factory
 from ..utils.inspect import import_root_module
@@ -328,6 +330,11 @@ def _init_parser(self):
             help="Specify the log level to be used. "
             f"Defaults to env variable: {_key_help(self.env_loglevel_key)}",
         )
+        # arg_group.add_argument(
+        #     "--log-file",
+        #     type=FileType(mode="w", encoding="UTF-8"),
+        #     help="Optionally override log file output.",
+        # )
         arg_group.add_argument(
             "--log-color",
             "--log-colour",
@@ -363,6 +370,23 @@ def _init_parser(self):
             help="Minimum level of check message to display",
         )
 
+        # Feature flags
+        arg_group = self.argument_group(
+            title="feature flags", description="Enable/Disable feature flags"
+        )
+        arg_group.add_argument(
+            "--enable-flag",
+            dest="enable_feature_flags",
+            action="append",
+            help="Enable a named feature flag; this argument can be used multiple times",
+        )
+        arg_group.add_argument(
+            "--disable-flag",
+            dest="disable_feature_flags",
+            action="append",
+            help="Disable a named feature flag; this argument can be used multiple times",
+        )
+
     def register_builtin_handlers(self):
         """
         Register any built in handlers.
@@ -415,6 +439,19 @@ def configure_settings(self, opts: CommandOptions):
             application_settings, opts.settings, env_settings_key=self.env_settings_key
         )
 
+    @staticmethod
+    def configure_feature_flags(opts: CommandOptions):
+        """
+        Configure feature flags cache.
+        """
+        if opts.enable_feature_flags:
+            for flag in opts.enable_feature_flags:
+                feature_flags.DEFAULT.set(flag, True)
+
+        if opts.disable_feature_flags:
+            for flag in opts.disable_feature_flags:
+                feature_flags.DEFAULT.set(flag, False)
+
     def get_log_formatter(self, log_color) -> logging.Formatter:
         """
         Get log formatter
@@ -422,11 +459,12 @@ def get_log_formatter(self, log_color) -> logging.Formatter:
         log_handler = self.default_log_handler
 
         # Auto-detect colour mode
-        if log_color is None:
-            if isinstance(log_handler, logging.StreamHandler) and hasattr(
-                log_handler.stream, "isatty"
-            ):
-                log_color = log_handler.stream.isatty()
+        if (
+            log_color is None
+            and isinstance(log_handler, logging.StreamHandler)
+            and hasattr(log_handler.stream, "isatty")
+        ):
+            log_color = log_handler.stream.isatty()
 
         # Enable colour if specified.
         if log_color:
@@ -442,13 +480,14 @@ def configure_logging(self, opts: CommandOptions):
         if hasattr(self, "_init_logger"):
             self.default_log_handler.formatter = self.get_log_formatter(opts.log_color)
 
+            if conf.settings.LOGGING:
+                logger.info("Applying logging configuration.")
+
             # Replace root handler with the default handler
             logging.root.handlers.pop(0)
             logging.root.handlers.append(self.default_log_handler)
 
             if conf.settings.LOGGING:
-                logger.info("Applying logging configuration.")
-
                 # Set a default version if not supplied by settings
                 dict_config = conf.settings.LOGGING.copy()
                 dict_config.setdefault("version", 1)
@@ -458,7 +497,7 @@ def configure_logging(self, opts: CommandOptions):
             logging.root.setLevel(opts.log_level)
 
             # Replay initial entries and remove
-            self._init_logger.replay(self.default_log_handler)
+            self._init_logger.replay()
             del self._init_logger
 
     def checks_on_startup(self, opts: CommandOptions):
@@ -521,6 +560,7 @@ def dispatch(self, args: Sequence[str] = None) -> None:
 
         # Load settings and configure logger
         self.configure_settings(opts)
+        self.configure_feature_flags(opts)
         self.configure_logging(opts)
 
         handler_name = getattr(opts, ":handler", None)
diff --git a/pyapp/app/argument_actions.py b/src/pyapp/app/argument_actions.py
similarity index 100%
rename from pyapp/app/argument_actions.py
rename to src/pyapp/app/argument_actions.py
diff --git a/pyapp/app/argument_types.py b/src/pyapp/app/argument_types.py
similarity index 100%
rename from pyapp/app/argument_types.py
rename to src/pyapp/app/argument_types.py
diff --git a/pyapp/app/arguments.py b/src/pyapp/app/arguments.py
similarity index 98%
rename from pyapp/app/arguments.py
rename to src/pyapp/app/arguments.py
index 8f2be90..58ebb25 100644
--- a/pyapp/app/arguments.py
+++ b/src/pyapp/app/arguments.py
@@ -23,6 +23,7 @@
 
 from pyapp.compatability import async_run
 from pyapp.utils import cached_property
+
 from .argument_actions import EnumName
 from .argument_actions import KeyValueAction
 
@@ -198,7 +199,11 @@ def _handle_generics(
         """
         name = str(origin)
         if name == "typing.Union":
-            if len(type_.__args__) == 2 and type(None) in type_.__args__:
+            if (
+                len(type_.__args__) == 2
+                and type(None)  # pylint: disable=unidiomatic-typecheck
+                in type_.__args__
+            ):
                 if positional:
                     kwargs["nargs"] = "?"
                 else:
diff --git a/pyapp/app/builtin_handlers.py b/src/pyapp/app/builtin_handlers.py
similarity index 100%
rename from pyapp/app/builtin_handlers.py
rename to src/pyapp/app/builtin_handlers.py
diff --git a/pyapp/app/init_logger.py b/src/pyapp/app/init_logger.py
similarity index 92%
rename from pyapp/app/init_logger.py
rename to src/pyapp/app/init_logger.py
index df8d448..54167f8 100644
--- a/pyapp/app/init_logger.py
+++ b/src/pyapp/app/init_logger.py
@@ -25,13 +25,13 @@ def handle(self, record: logging.LogRecord) -> None:
         if record.levelno >= self.pass_through_level:
             super().handle(record)
 
-    def replay(self, handler: logging.Handler):
+    def replay(self):
         """
         Replay stored log records
         """
 
         for record in self._store:
-            handler.handle(record)
+            logging.getLogger(record.name).handle(record)
         self._store.clear()
 
     def emit(self, record: logging.LogRecord) -> None:
diff --git a/pyapp/app/logging_formatter.py b/src/pyapp/app/logging_formatter.py
similarity index 100%
rename from pyapp/app/logging_formatter.py
rename to src/pyapp/app/logging_formatter.py
diff --git a/pyapp/checks/__init__.py b/src/pyapp/checks/__init__.py
similarity index 100%
rename from pyapp/checks/__init__.py
rename to src/pyapp/checks/__init__.py
diff --git a/pyapp/checks/built_in.py b/src/pyapp/checks/built_in.py
similarity index 100%
rename from pyapp/checks/built_in.py
rename to src/pyapp/checks/built_in.py
diff --git a/pyapp/checks/messages.py b/src/pyapp/checks/messages.py
similarity index 100%
rename from pyapp/checks/messages.py
rename to src/pyapp/checks/messages.py
diff --git a/pyapp/checks/registry.py b/src/pyapp/checks/registry.py
similarity index 100%
rename from pyapp/checks/registry.py
rename to src/pyapp/checks/registry.py
diff --git a/pyapp/checks/report.py b/src/pyapp/checks/report.py
similarity index 99%
rename from pyapp/checks/report.py
rename to src/pyapp/checks/report.py
index 5267d45..a56bce7 100644
--- a/pyapp/checks/report.py
+++ b/src/pyapp/checks/report.py
@@ -17,7 +17,6 @@
 from colorama import Back
 from colorama import Fore
 from colorama import Style
-
 from pyapp.checks.registry import Check
 from pyapp.checks.registry import CheckMessage
 from pyapp.checks.registry import CheckRegistry
diff --git a/pyapp/compatability.py b/src/pyapp/compatability.py
similarity index 100%
rename from pyapp/compatability.py
rename to src/pyapp/compatability.py
diff --git a/pyapp/conf/__init__.py b/src/pyapp/conf/__init__.py
similarity index 100%
rename from pyapp/conf/__init__.py
rename to src/pyapp/conf/__init__.py
diff --git a/pyapp/conf/base_settings.py b/src/pyapp/conf/base_settings.py
similarity index 73%
rename from pyapp/conf/base_settings.py
rename to src/pyapp/conf/base_settings.py
index f39f9d0..3104f47 100644
--- a/pyapp/conf/base_settings.py
+++ b/src/pyapp/conf/base_settings.py
@@ -52,3 +52,22 @@
 """
 Locations to import to ensure checks are registered.
 """
+
+
+###############################################################################
+# Feature Flags
+
+FEATURE_FLAGS: Dict[str, bool] = {}
+"""
+Feature flags definition, this is a simple configuration of::
+
+    FEATURE_FLAGS = {
+        "flag-name": True,  # State of True, False
+    }
+
+"""
+
+FEATURE_FLAG_PREFIX: str = "PYAPP_FLAG_"
+"""
+Prefix applied to flag names for environment variables
+"""
diff --git a/pyapp/conf/helpers/__init__.py b/src/pyapp/conf/helpers/__init__.py
similarity index 100%
rename from pyapp/conf/helpers/__init__.py
rename to src/pyapp/conf/helpers/__init__.py
diff --git a/pyapp/conf/helpers/bases.py b/src/pyapp/conf/helpers/bases.py
similarity index 100%
rename from pyapp/conf/helpers/bases.py
rename to src/pyapp/conf/helpers/bases.py
diff --git a/pyapp/conf/helpers/plugins.py b/src/pyapp/conf/helpers/plugins.py
similarity index 96%
rename from pyapp/conf/helpers/plugins.py
rename to src/pyapp/conf/helpers/plugins.py
index c44a2f1..4677e76 100644
--- a/pyapp/conf/helpers/plugins.py
+++ b/src/pyapp/conf/helpers/plugins.py
@@ -283,15 +283,14 @@ def check_instance(self, instance_definitions, name, **_):
                     )
                 )
 
-            else:
-                if target_name not in instance_definitions:
-                    messages.append(
-                        checks.Critical(
-                            "Alias target not defined",
-                            hint="The target specified by the alias does not exist, check the `name` value.",
-                            obj=f"settings.{self.setting}[{name}][{target_name}]",
-                        )
+            elif target_name not in instance_definitions:
+                messages.append(
+                    checks.Critical(
+                        "Alias target not defined",
+                        hint="The target specified by the alias does not exist, check the `name` value.",
+                        obj=f"settings.{self.setting}[{name}][{target_name}]",
                     )
+                )
 
             if len(kwargs) > 1:
                 messages.append(
diff --git a/pyapp/conf/helpers/providers.py b/src/pyapp/conf/helpers/providers.py
similarity index 100%
rename from pyapp/conf/helpers/providers.py
rename to src/pyapp/conf/helpers/providers.py
diff --git a/pyapp/conf/loaders/__init__.py b/src/pyapp/conf/loaders/__init__.py
similarity index 99%
rename from pyapp/conf/loaders/__init__.py
rename to src/pyapp/conf/loaders/__init__.py
index 5cdad4f..078de9d 100644
--- a/pyapp/conf/loaders/__init__.py
+++ b/src/pyapp/conf/loaders/__init__.py
@@ -16,12 +16,11 @@
 from typing import Type
 from typing import Union
 
-from yarl import URL
-
 from pyapp.conf.loaders.base import Loader
 from pyapp.conf.loaders.file_loader import FileLoader
 from pyapp.conf.loaders.http_loader import HttpLoader
 from pyapp.exceptions import InvalidConfiguration
+from yarl import URL
 
 
 class ModuleLoader(Loader):
diff --git a/pyapp/conf/loaders/base.py b/src/pyapp/conf/loaders/base.py
similarity index 100%
rename from pyapp/conf/loaders/base.py
rename to src/pyapp/conf/loaders/base.py
diff --git a/pyapp/conf/loaders/content_types.py b/src/pyapp/conf/loaders/content_types.py
similarity index 83%
rename from pyapp/conf/loaders/content_types.py
rename to src/pyapp/conf/loaders/content_types.py
index 1b876d9..2e9119b 100644
--- a/pyapp/conf/loaders/content_types.py
+++ b/src/pyapp/conf/loaders/content_types.py
@@ -23,10 +23,23 @@
 except ImportError:  # pragma: no cover
     yaml_load = None
 
+try:
+    from toml import load as toml_load
+except ImportError:  # pragma: no cover
+    toml_load = None
+
 from pyapp.exceptions import UnsupportedContentType
 
+JSON_MIME_TYPE = "application/json"
+TOML_MIME_TYPE = "application/toml"  # Ref: https://toml.io/en/v1.0.0#mime-type
+YAML_MIME_TYPE = "application/x-yaml"
+
 # These are content types that are not registered but are in common use.
-UNOFFICIAL_CONTENT_TYPES = {".yaml": "application/x-yaml", ".yml": "application/x-yaml"}
+UNOFFICIAL_CONTENT_TYPES = {
+    ".toml": TOML_MIME_TYPE,
+    ".yaml": YAML_MIME_TYPE,
+    ".yml": YAML_MIME_TYPE,
+}
 
 
 def content_type_from_url(url: URL) -> str:
@@ -83,5 +96,5 @@ def register(
 
 
 registry = ContentTypeParserRegistry(  # pylint: disable=invalid-name
-    {"application/json": json_load, "application/x-yaml": yaml_load}
+    {JSON_MIME_TYPE: json_load, TOML_MIME_TYPE: toml_load, YAML_MIME_TYPE: yaml_load}
 )
diff --git a/pyapp/conf/loaders/file_loader.py b/src/pyapp/conf/loaders/file_loader.py
similarity index 99%
rename from pyapp/conf/loaders/file_loader.py
rename to src/pyapp/conf/loaders/file_loader.py
index 65111f4..fe13b0d 100644
--- a/pyapp/conf/loaders/file_loader.py
+++ b/src/pyapp/conf/loaders/file_loader.py
@@ -8,12 +8,11 @@
 from pathlib import Path
 from typing import Union
 
-from yarl import URL
-
 from pyapp.conf.loaders.base import Loader
 from pyapp.conf.loaders.content_types import content_type_from_url
 from pyapp.conf.loaders.content_types import registry
 from pyapp.exceptions import InvalidConfiguration
+from yarl import URL
 
 
 class FileLoader(Loader):
diff --git a/pyapp/conf/loaders/http_loader.py b/src/pyapp/conf/loaders/http_loader.py
similarity index 96%
rename from pyapp/conf/loaders/http_loader.py
rename to src/pyapp/conf/loaders/http_loader.py
index 18733c0..bb85e37 100644
--- a/pyapp/conf/loaders/http_loader.py
+++ b/src/pyapp/conf/loaders/http_loader.py
@@ -13,12 +13,11 @@
 from urllib.error import ContentTooShortError
 from urllib.request import urlopen
 
-from yarl import URL
-
 from pyapp.conf.loaders.base import Loader
 from pyapp.conf.loaders.content_types import content_type_from_url
 from pyapp.conf.loaders.content_types import registry
 from pyapp.exceptions import InvalidConfiguration
+from yarl import URL
 
 
 def retrieve_file(url: URL) -> Tuple[TextIO, str]:
@@ -31,7 +30,9 @@ def retrieve_file(url: URL) -> Tuple[TextIO, str]:
     if url.scheme not in ("http", "https"):
         raise InvalidConfiguration("Illegal scheme.")
 
-    context = ssl.SSLContext() if url.scheme == "https" else None
+    context = (
+        ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) if url.scheme == "https" else None
+    )
 
     with contextlib.closing(
         urlopen(url, context=context)  # nosec - There is a check above for SSL
diff --git a/pyapp/conf/report.py b/src/pyapp/conf/report.py
similarity index 99%
rename from pyapp/conf/report.py
rename to src/pyapp/conf/report.py
index f6aca5c..e632c2b 100644
--- a/pyapp/conf/report.py
+++ b/src/pyapp/conf/report.py
@@ -11,7 +11,6 @@
 
 from colorama import Fore
 from colorama import Style
-
 from pyapp import conf
 from pyapp.utils import wrap_text
 
diff --git a/pyapp/events.py b/src/pyapp/events.py
similarity index 100%
rename from pyapp/events.py
rename to src/pyapp/events.py
diff --git a/pyapp/exceptions.py b/src/pyapp/exceptions.py
similarity index 100%
rename from pyapp/exceptions.py
rename to src/pyapp/exceptions.py
diff --git a/pyapp/extensions/__init__.py b/src/pyapp/extensions/__init__.py
similarity index 100%
rename from pyapp/extensions/__init__.py
rename to src/pyapp/extensions/__init__.py
diff --git a/pyapp/extensions/registry.py b/src/pyapp/extensions/registry.py
similarity index 99%
rename from pyapp/extensions/registry.py
rename to src/pyapp/extensions/registry.py
index 640c446..81ef7af 100644
--- a/pyapp/extensions/registry.py
+++ b/src/pyapp/extensions/registry.py
@@ -13,7 +13,6 @@
 from typing import Sequence
 
 import pkg_resources
-
 from pyapp.app.arguments import CommandGroup
 
 __all__ = ("registry", "ExtensionEntryPoints", "ExtensionDetail")
diff --git a/pyapp/extensions/report.py b/src/pyapp/extensions/report.py
similarity index 99%
rename from pyapp/extensions/report.py
rename to src/pyapp/extensions/report.py
index 2f00b80..b00409a 100644
--- a/pyapp/extensions/report.py
+++ b/src/pyapp/extensions/report.py
@@ -9,7 +9,6 @@
 
 from colorama import Fore
 from colorama import Style
-
 from pyapp.extensions.registry import ExtensionDetail
 from pyapp.extensions.registry import registry
 
diff --git a/src/pyapp/feature_flags.py b/src/pyapp/feature_flags.py
new file mode 100644
index 0000000..a95d10d
--- /dev/null
+++ b/src/pyapp/feature_flags.py
@@ -0,0 +1,185 @@
+"""
+Feature Flags
+~~~~~~~~~~~~~
+
+Runtime configurable feature flags.
+
+Define flags in code either in a code block:
+
+.. code-block:: python
+
+    if feature_flags.get("MY-FLAG"):
+        pass
+
+Or as an A/B switch:
+
+.. code-block:: python
+
+    instance = feature_flags.a_or_b("MY-FLAG", option_a="foo", option_b="bar")
+
+.. tip:: ``option_a`` and ``option_b`` parameters can also be callables.
+
+Or by using as a decorator:
+
+.. code-block:: python
+
+    @feature_flags.if_enabled("MY-FLAG", default=True)
+    def my_feature(arg):
+        pass
+
+
+Define flags in the applications ``default_settings``:
+
+.. code-block:: python
+
+    FEATURE_FLAGS = {
+        "MY-FLAG": True,
+        "OTHER-FLAG": False,
+    }
+
+The flag can then be enabled/disabled in the environment::
+
+    PYAPP_FLAG_MY_FLAG = yes
+    PYAPP_FLAG_OTHER_FLAG = off
+
+.. note:: The flag name is translated into upper-case with underscores
+
+Or from the command line::
+
+    my_app --enable-flag MY-FLAG --disable-flag OTHER-FLAG
+
+"""
+import logging
+from os import getenv
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Optional
+from typing import Union
+
+from pyapp.conf import settings
+from pyapp.utils import text_to_bool
+
+LOGGER = logging.getLogger(__name__)
+ENV_TRANSLATE = str.maketrans(
+    " -abcdefghijklmnopqrstuvwxyz", "__ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+)
+
+
+class FeatureFlags:
+    """
+    Feature flags object that caches resolved flags.
+
+    Flags are resolved in the following order:
+
+    - CLI
+    - Environment Variables
+    - Settings
+
+    """
+
+    __slots__ = ("_cache",)
+
+    def __init__(self):
+        self._cache: Dict[str, bool] = {}
+
+    @staticmethod
+    def _resolve_from_environment(flag: str) -> Optional[bool]:
+        """
+        Attempt to resolve from environment.
+        """
+        key = f"{settings.FEATURE_FLAG_PREFIX}{flag.translate(ENV_TRANSLATE)}"
+        LOGGER.debug("Resolving flag %r from environment variable %s", flag, key)
+        value = getenv(key, None)
+        if value:
+            return text_to_bool(value)
+        return None
+
+    @staticmethod
+    def _resolve_from_settings(flag: str) -> Optional[bool]:
+        LOGGER.debug("Resolving flag %r from settings", flag)
+        return settings.FEATURE_FLAGS.get(flag, None)
+
+    def _resolve(self, flag: str, default: bool) -> bool:
+        value = self._resolve_from_environment(flag)
+        if value is not None:
+            LOGGER.info("Flag %r resolved from environment: %s", flag, value)
+            return value
+
+        value = self._resolve_from_settings(flag)
+        if value is not None:
+            LOGGER.info("Flag %r resolved from settings: %s", flag, value)
+            return value
+
+        return default
+
+    def _get(self, flag: str, default: bool) -> bool:
+        try:
+            return self._cache[flag]
+        except KeyError:
+            value = self._cache[flag] = self._resolve(flag, default)
+            return value
+
+    def set(self, flag: str, value: bool):
+        """
+        Set the state of the flag
+        """
+        self._cache[flag] = value
+
+    def get(self, flag: str, *, default: bool = False):
+        """
+        Get the state of a flag
+
+        :param flag: Name of flag
+        :param default: Default flag state
+
+        """
+        return self._get(flag, default)
+
+    def a_or_b(
+        self,
+        flag: str,
+        option_a: Union[Any, Callable[[], Any]],
+        option_b: Union[Any, Callable[[], Any]],
+        *,
+        default: bool = False,
+    ):
+        """
+        Use one of two values (or results of callables),
+
+        :param flag: Name of flag
+        :param option_a: A value or callable that provides a value; used if flag is True
+        :param option_b: B value or callable that provides a value; used if flag is False
+        :param default: Default flag state
+
+        """
+        if self._get(flag, default):
+            return option_a() if callable(option_a) else option_a
+        return option_b() if callable(option_b) else option_b
+
+    def if_enabled(
+        self, flag: str, *, default: bool = False, disabled_return: Any = None
+    ):
+        """
+        Decorator that is enabled by flag.
+
+        :param flag: Name of flag
+        :param default: Default flag state
+        :param disabled_return: Return value if flag is disabled
+
+        """
+
+        def decorator(func):
+            def wrapper(*args, **kwargs):
+                if self._get(flag, default):
+                    return func(*args, **kwargs)
+                return disabled_return
+
+            return wrapper
+
+        return decorator
+
+
+DEFAULT = FeatureFlags()
+get = DEFAULT.get  # pylint: disable=invalid-name
+if_enabled = DEFAULT.if_enabled  # pylint: disable=invalid-name
diff --git a/pyapp/injection.py b/src/pyapp/injection.py
similarity index 100%
rename from pyapp/injection.py
rename to src/pyapp/injection.py
diff --git a/pyapp/py.typed b/src/pyapp/py.typed
similarity index 100%
rename from pyapp/py.typed
rename to src/pyapp/py.typed
diff --git a/pyapp/utils/__init__.py b/src/pyapp/utils/__init__.py
similarity index 83%
rename from pyapp/utils/__init__.py
rename to src/pyapp/utils/__init__.py
index df29737..b45c612 100644
--- a/pyapp/utils/__init__.py
+++ b/src/pyapp/utils/__init__.py
@@ -5,6 +5,7 @@
 import importlib
 import textwrap
 from typing import Any
+from typing import Container
 
 
 def is_iterable(obj: Any) -> bool:
@@ -66,3 +67,15 @@ def wrap_text(
         text, width - (padding * 2), initial_indent=indent, subsequent_indent=indent
     )
     return line_sep.join(f"{l}{' ' * (width - len(l))}" for l in lines)
+
+
+TRUE_VALUES = ("TRUE", "T", "YES", "Y", "ON", "1")
+
+
+def text_to_bool(value: Any, *, true_values: Container[str] = TRUE_VALUES) -> bool:
+    """
+    Resolve a string into a bool eg "yes" -> True
+    """
+    if isinstance(value, str):
+        return value.upper() in true_values
+    return False
diff --git a/pyapp/utils/compatibility.py b/src/pyapp/utils/compatibility.py
similarity index 100%
rename from pyapp/utils/compatibility.py
rename to src/pyapp/utils/compatibility.py
diff --git a/pyapp/utils/inspect.py b/src/pyapp/utils/inspect.py
similarity index 100%
rename from pyapp/utils/inspect.py
rename to src/pyapp/utils/inspect.py
diff --git a/pyapp/versioning.py b/src/pyapp/versioning.py
similarity index 100%
rename from pyapp/versioning.py
rename to src/pyapp/versioning.py


Test patch:
diff --git a/tests/app/test_app.py b/tests/app/test_app.py
index 491b212..50f7ac9 100644
--- a/tests/app/test_app.py
+++ b/tests/app/test_app.py
@@ -1,12 +1,13 @@
 import pytest
-
-import tests.sample_app
-import tests.sample_app_simple
+from pyapp import feature_flags
 from pyapp.app import _key_help
 from pyapp.app import argument
 from pyapp.app import CliApplication
 from pyapp.app.logging_formatter import ColourFormatter
 
+import tests.sample_app
+import tests.sample_app_simple
+
 
 @pytest.mark.parametrize("key, expected", (("FOO", "FOO [eek]"), ("BAR", "BAR")))
 def test_key_help(monkeypatch, key, expected):
@@ -81,6 +82,33 @@ def test_dispatch__exception(self):
 
         assert str(ex.value) == "Grrrr"
 
+    def test_dispatch__set_feature_flags__where_no_flag_set(self):
+        target = tests.sample_app.__main__.app
+        feature_flags.DEFAULT._cache.clear()
+
+        with pytest.raises(SystemExit) as ex:
+            target.dispatch(args=("undecided",))
+
+        assert ex.value.code == 30
+
+    def test_dispatch__set_feature_flags__where_happy_enabled(self):
+        target = tests.sample_app.__main__.app
+        feature_flags.DEFAULT._cache.clear()
+
+        with pytest.raises(SystemExit) as ex:
+            target.dispatch(args=("--enable-flag", "happy", "undecided",))
+
+        assert ex.value.code == 10
+
+    def test_dispatch__set_feature_flags__where_sad_disabled(self):
+        target = tests.sample_app.__main__.app
+        feature_flags.DEFAULT._cache.clear()
+
+        with pytest.raises(SystemExit) as ex:
+            target.dispatch(args=("--disable-flag", "sad", "undecided",))
+
+        assert ex.value.code == 20
+
     def test_get_log_formatter__force_colour(self):
         target = tests.sample_app.__main__.app
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 4be254d..7fd9914 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,10 +1,16 @@
+import sys
 from pathlib import Path
 
 import pytest
 
-from pyapp.conf import settings
+HERE = Path(__file__).parent.absolute()
+SRC_PATH = HERE.parent / "src"
+
+sys.path.insert(0, SRC_PATH.as_posix())
 
 # Ensure settings are configured
+from pyapp.conf import settings
+
 settings.configure(["tests.settings"])
 
 
diff --git a/tests/sample_app/__main__.py b/tests/sample_app/__main__.py
index 2028921..1668558 100644
--- a/tests/sample_app/__main__.py
+++ b/tests/sample_app/__main__.py
@@ -1,7 +1,9 @@
-import tests.sample_app
 from pyapp import checks
+from pyapp import feature_flags
 from pyapp.app import CliApplication
 
+import tests.sample_app
+
 app = CliApplication(tests.sample_app)
 
 
@@ -28,6 +30,21 @@ def angry(opts):
     raise Exception("Grrrr")
 
 
+@app.command
+def undecided(opts):
+    if feature_flags.get("happy"):
+        print("=o)")
+        return 10
+
+    elif feature_flags.get("sad", default=True):
+        print("=o(")
+        return 30
+
+    else:
+        print("=o|")
+        return 20
+
+
 @checks.register
 def critical_check(**_):
     return checks.Critical(
diff --git a/tests/test_feature_flags.py b/tests/test_feature_flags.py
new file mode 100644
index 0000000..7ffec72
--- /dev/null
+++ b/tests/test_feature_flags.py
@@ -0,0 +1,132 @@
+from unittest.mock import Mock
+
+import pytest
+from pyapp import feature_flags
+from pyapp.conf import settings
+
+
+class FeatureFlagsWrapper(feature_flags.FeatureFlags):
+    pass
+
+
+class TestFeatureFlags:
+    @pytest.mark.parametrize(
+        "flag, expected", (("enable-a", True), ("Enable b", False), ("ENABLE_C", None),)
+    )
+    def test_resolve_from_environment(self, monkeypatch, flag, expected):
+        monkeypatch.setenv("PYAPP_FLAG_ENABLE_A", "On")
+        monkeypatch.setenv("PYAPP_FLAG_ENABLE_B", "false")
+
+        actual = feature_flags.DEFAULT._resolve_from_environment(flag)
+
+        assert actual is expected
+
+    @pytest.mark.parametrize(
+        "flag, expected", (("enable-a", True), ("Enable b", False), ("ENABLE_C", None),)
+    )
+    def test_resolve_from_settings(self, flag, expected):
+        with settings.modify() as patch:
+            patch.FEATURE_FLAGS = {
+                "enable-a": True,
+                "Enable b": False,
+            }
+
+            actual = feature_flags.DEFAULT._resolve_from_settings(flag)
+
+        assert actual is expected
+
+    @pytest.mark.parametrize(
+        "flag, expected",
+        (
+            ("enable-a", True),
+            ("Enable b", True),
+            ("Enable_C", True),
+            ("Enable-D", False),
+        ),
+    )
+    def test_resolve(self, monkeypatch, flag, expected):
+        monkeypatch.setenv("PYAPP_FLAG_ENABLE_A", "On")
+        monkeypatch.setenv("PYAPP_FLAG_ENABLE_B", "true")
+
+        with settings.modify() as patch:
+            patch.FEATURE_FLAGS = {
+                "enable-b": False,
+                "Enable_C": True,
+            }
+
+            actual = feature_flags.DEFAULT._resolve(flag, False)
+
+        assert actual is expected
+
+    def test_get__where_feature_flag_is_cached(self):
+        target = FeatureFlagsWrapper()
+        target._cache["EnableD"] = "Mock_Value"
+        target._resolve = Mock(return_value="Mock_Value")
+
+        actual = target.get("EnableD")
+
+        assert actual == "Mock_Value"
+        target._resolve.assert_not_called()
+
+    def test_get__where_feature_flag_is_not_cached(self):
+        target = FeatureFlagsWrapper()
+        target._resolve = Mock(return_value="Mock_Value")
+
+        actual = target.get("EnableD")
+
+        assert actual == "Mock_Value"
+        target._resolve.assert_called_once_with("EnableD", False)
+
+    def test_set(self):
+        target = feature_flags.FeatureFlags()
+
+        target.set("EnableA", True)
+
+        assert target._cache["EnableA"] is True
+
+    @pytest.mark.parametrize(
+        "option_a, option_b, state, expected",
+        (
+            ("A-Value", "B-Value", True, "A-Value"),
+            ("A-Value", "B-Value", False, "B-Value"),
+            (lambda: "A-Value", lambda: "B-Value", True, "A-Value"),
+            (lambda: "A-Value", lambda: "B-Value", False, "B-Value"),
+        ),
+    )
+    def test_a_or_b(self, option_a, option_b, state, expected):
+        target = FeatureFlagsWrapper()
+        target._get = Mock(return_value=state)
+
+        actual = target.a_or_b("EnableE", option_a, option_b)
+
+        assert actual == expected
+
+    @pytest.mark.parametrize(
+        "state, expected", ((True, "ValueA"), (False, None),),
+    )
+    def test_if_enabled__where_return_is_default(self, state, expected):
+        target = FeatureFlagsWrapper()
+        target._get = Mock(return_value=state)
+
+        @target.if_enabled("EnableF")
+        def _sample(arg_a):
+            return arg_a
+
+        actual = _sample("ValueA")
+
+        assert actual == expected
+
+    @pytest.mark.parametrize(
+        "state, expected", ((True, "ValueA"), (False, "ValueB"),),
+    )
+    def test_if_enabled__where_return_is_customised(self, state, expected):
+        target = FeatureFlagsWrapper()
+        target._get = Mock(return_value=state)
+
+        @target.if_enabled("EnableF", disabled_return="ValueB")
+        def _sample(arg_a):
+            return arg_a
+
+        actual = _sample("ValueA")
+
+        assert actual == expected
diff --git a/tests/utils/test_.py b/tests/utils/test_.py
index 1afe1cd..5041927 100644
--- a/tests/utils/test_.py
+++ b/tests/utils/test_.py
@@ -1,5 +1,4 @@
 import pytest
-
 from pyapp import utils
 
 
@@ -33,3 +32,24 @@ def test_cached_property():
     assert target.a == "foo"
     del target.a
     assert target.a == "bar"
+
+
+@pytest.mark.parametrize(
+    "value, expected",
+    (
+        ("yes", True),
+        ("True", True),
+        ("y", True),
+        ("t", True),
+        ("ON", True),
+        ("1", True),
+        ("", False),
+        ("FALSE", False),
+        ("Off", False),
+        (None, False),
+    ),
+)
+def test_text_to_bool(value, expected):
+    actual = utils.text_to_bool(value)
+
+    assert actual == expected