Repository: invokermain/pymssql-utils (language: python)

Issue:
Improve type hints with respect to static type checkers
The typing in this package can be improved two-fold:

1. Make sure this library passes `pyright`'s basic level of checking. Strict would be very difficult due to lack of stubs for pymssql.
2. Make sure this library works well when being used in another project with static type checkers.

To achieve:

1. Work through the errors, this will probably require some level of internal design change.
2. Replace places where type hints are effectively useless with `any`, as this is more flexible. e.g. SQLParameter's large Union of types.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0683737..e10a041 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.2.0] - 2022-01-21
+### Added
+- Added some more tests, coverage now at 92%.
+### Changed
+- BREAKING: DatabaseResult's `source_types`, `columns`, `raw_data` and `data`,
+  attributes/properties now cannot be `None`, in order to decrease type ambiguity. To 
+  facilitate this change, they will raise a ValueError if they are called when the 
+  DatabaseResult instance errored, or fetch was false.
+- The SQLParameter type hint (used in various places) is now just an alias for Any.
+- Miscellaneous refactoring and type hinting/guarding throughout the library. The 
+  library now passes a basic `pyright` check with no errors. 
+
+
 ## [0.1.5] - 2021-09-21
 ### Fixed 
 - Handle specific length byte strings raising exceptions when parsing database result, [closes #15](https://github.com/invokermain/pymssql-utils/issues/15).
diff --git a/README.md b/README.md
index ae9d9f2..f53f3db 100644
--- a/README.md
+++ b/README.md
@@ -29,11 +29,9 @@ Please raise any suggestions or issues via GitHub.
 
 ## Status
 
-This library has been in production use in various projects for over a year,
-so should be considered largely tested and stable.
-There will not be any breaking changes to the public API without a major version release.
-However, there might still be a few bugs to be found. There is also scope for expanding the library
-if new features are requested.
+This library is tested and stable. There will not be any breaking changes to the 
+public API without a major version release.  There is also scope for expanding the 
+library if new features are requested.
 
 ## Changes
 
@@ -177,7 +175,7 @@ One big difference between this library and _pymssql_ is that here
 This class holds the returned data, if there is any, and provides
 various useful attributes and methods to work with the result.
 
-#### Attributes
+#### Attributes & Properties
  * `ok`: True if the execution did not error, else False. Only useful if using `raise_errors = False`,
    see below section on Error Handling.
  * `error`: Populated by the error raised during execution (if applicable). Only useful if using `raise_errors = False`.
@@ -387,6 +385,5 @@ These tests will then be run (not-skipped), e.g. `pytest . --envfile .test.env`
 There are other minor reasons someone might prefer _pymssql_, e.g.:
 1. _pymssql_ supports for MSSQL specific data types such as `Datetimeoffset`.
 2. _pymssql's_ parameter subsitution is done client-side improving debug/logging ability.
-3. _pymssql_ are older and more stable, meaning that it has supported by more OS's
-(you shouldn't have to [deal with this](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15)
-just to run your code in another environment!)
+3. _pymssql's_ drivers are easily installed, meaning that your code 'just works' in 
+   more environments without extra steps, [e.g. this](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15)!
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index 3699484..7456711 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -8,21 +8,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "attrs"
-version = "21.2.0"
+version = "21.4.0"
 description = "Classes Without Boilerplate"
 category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
 docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
 
 [[package]]
 name = "black"
-version = "21.9b0"
+version = "21.12b0"
 description = "The uncompromising code formatter."
 category = "dev"
 optional = false
@@ -33,9 +33,8 @@ click = ">=7.1.2"
 mypy-extensions = ">=0.4.3"
 pathspec = ">=0.9.0,<1"
 platformdirs = ">=2"
-regex = ">=2020.1.8"
 tomli = ">=0.2.6,<2.0.0"
-typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""}
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
 typing-extensions = [
     {version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
     {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
@@ -43,14 +42,14 @@ typing-extensions = [
 
 [package.extras]
 colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
+d = ["aiohttp (>=3.7.4)"]
 jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-python2 = ["typed-ast (>=1.4.2)"]
+python2 = ["typed-ast (>=1.4.3)"]
 uvloop = ["uvloop (>=0.15.2)"]
 
 [[package]]
 name = "click"
-version = "8.0.1"
+version = "8.0.3"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
@@ -68,23 +67,34 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
+[[package]]
+name = "coverage"
+version = "6.2"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+toml = ["tomli"]
+
 [[package]]
 name = "flake8"
-version = "3.9.2"
+version = "4.0.1"
 description = "the modular source code checker: pep8 pyflakes and co"
 category = "dev"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.6"
 
 [package.dependencies]
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""}
 mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.7.0,<2.8.0"
-pyflakes = ">=2.3.0,<2.4.0"
+pycodestyle = ">=2.8.0,<2.9.0"
+pyflakes = ">=2.4.0,<2.5.0"
 
 [[package]]
 name = "importlib-metadata"
-version = "4.8.1"
+version = "4.2.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
@@ -96,8 +106,7 @@ zipp = ">=0.5"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "iniconfig"
@@ -109,7 +118,7 @@ python-versions = "*"
 
 [[package]]
 name = "isort"
-version = "5.9.3"
+version = "5.10.1"
 description = "A Python utility / library to sort Python imports."
 category = "dev"
 optional = false
@@ -137,6 +146,14 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "nodeenv"
+version = "1.6.0"
+description = "Node.js virtual environment builder"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "numpy"
 version = "1.21.1"
@@ -147,7 +164,7 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "orjson"
-version = "3.6.3"
+version = "3.6.5"
 description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
 category = "main"
 optional = true
@@ -155,14 +172,14 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "packaging"
-version = "21.0"
+version = "21.3"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
-pyparsing = ">=2.0.2"
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
 [[package]]
 name = "pandas"
@@ -190,11 +207,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
 name = "platformdirs"
-version = "2.3.0"
+version = "2.4.1"
 description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.extras]
 docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
@@ -217,23 +234,23 @@ testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "py"
-version = "1.10.0"
+version = "1.11.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.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pycodestyle"
-version = "2.7.0"
+version = "2.8.0"
 description = "Python style guide checker"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pyflakes"
-version = "2.3.1"
+version = "2.4.0"
 description = "passive checker of Python programs"
 category = "dev"
 optional = false
@@ -241,7 +258,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pymssql"
-version = "2.2.2"
+version = "2.2.3"
 description = "DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)"
 category = "main"
 optional = false
@@ -249,11 +266,30 @@ python-versions = "*"
 
 [[package]]
 name = "pyparsing"
-version = "2.4.7"
+version = "3.0.7"
 description = "Python parsing module"
 category = "dev"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+python-versions = ">=3.6"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pyright"
+version = "0.0.13"
+description = "Command line wrapper for pyright"
+category = "dev"
+optional = false
+python-versions = ">=3"
+
+[package.dependencies]
+nodeenv = ">=1.6.0"
+typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""}
+
+[package.extras]
+all = ["twine (>=3.4.1)"]
+dev = ["twine (>=3.4.1)"]
 
 [[package]]
 name = "pytest"
@@ -316,7 +352,7 @@ six = ">=1.5"
 
 [[package]]
 name = "python-dotenv"
-version = "0.19.0"
+version = "0.19.2"
 description = "Read key-value pairs from a .env file and set them as environment variables"
 category = "dev"
 optional = false
@@ -327,20 +363,12 @@ cli = ["click (>=5.0)"]
 
 [[package]]
 name = "pytz"
-version = "2021.1"
+version = "2021.3"
 description = "World timezone definitions, modern and historical"
 category = "main"
 optional = true
 python-versions = "*"
 
-[[package]]
-name = "regex"
-version = "2021.8.28"
-description = "Alternative regular expression module, to replace re."
-category = "dev"
-optional = false
-python-versions = "*"
-
 [[package]]
 name = "six"
 version = "1.16.0"
@@ -359,7 +387,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tomli"
-version = "1.2.1"
+version = "1.2.3"
 description = "A lil' TOML parser"
 category = "dev"
 optional = false
@@ -367,31 +395,31 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "typed-ast"
-version = "1.4.3"
+version = "1.5.1"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "typing-extensions"
-version = "3.10.0.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
+version = "4.0.1"
+description = "Backported and Experimental Type Hints for Python 3.6+"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "zipp"
-version = "3.5.0"
+version = "3.7.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [extras]
 all = ["orjson", "pandas"]
@@ -401,7 +429,7 @@ pandas = ["pandas"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "55593f028c79cb25ed4ee70d818bc20287a1ae3cc4bf026c71256643025ad70f"
+content-hash = "e74cc9a2ef713d89b1b59dd71748664aa94067b7ba706c3d215d6e31f2f9465a"
 
 [metadata.files]
 atomicwrites = [
@@ -409,36 +437,85 @@ atomicwrites = [
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
-    {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+    {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
+    {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
 black = [
-    {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"},
-    {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"},
+    {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
+    {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
 ]
 click = [
-    {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
-    {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
+    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
+    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
+coverage = [
+    {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
+    {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
+    {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
+    {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
+    {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
+    {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
+    {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
+    {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
+    {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
+    {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
+    {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
+    {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
+    {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
+    {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
+    {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
+    {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
+    {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
+    {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
+    {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
+    {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
+]
 flake8 = [
-    {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
-    {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
+    {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
+    {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
-    {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
+    {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"},
+    {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 isort = [
-    {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
-    {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
+    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
 ]
 mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
@@ -448,6 +525,10 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
+nodeenv = [
+    {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
+    {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
+]
 numpy = [
     {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"},
     {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"},
@@ -479,27 +560,34 @@ numpy = [
     {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
 ]
 orjson = [
-    {file = "orjson-3.6.3-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:a99f310960e3acdda72ba1e98df8bf8c9145d90a0f72719786f43f4ea6937846"},
-    {file = "orjson-3.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:8a5e46418f51f03060f91d743b59aed70c8d02a5012428365cfa20b7f670e903"},
-    {file = "orjson-3.6.3-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:084de43ca9b19ad58c618c9f1ff93784e0190df2d88a02ae24c3cdebe9f2e9f7"},
-    {file = "orjson-3.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b68a601f49c0328bf16498309e56ab87c1d6c2bb0287abf70329eb958d565c62"},
-    {file = "orjson-3.6.3-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:5eb9d7f2f45e12cbc7500da4176f2d3221a73891b4be505fe79c52cbb800e872"},
-    {file = "orjson-3.6.3-cp37-none-win_amd64.whl", hash = "sha256:39aa7d42c9760fba36c37adb1d9c6752696ce9443c5dcb65222dd0994b5735e1"},
-    {file = "orjson-3.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:8d4430e0cc390c1d745aea3827fd0c6fd7aa5f0690de30a2fe25c406aa5efa20"},
-    {file = "orjson-3.6.3-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:be79e0ddea7f3a47332ec9573365c0b8a8cce4357e9682050f53c1bc75c1571f"},
-    {file = "orjson-3.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fce5ada0f8dd7c9e16c675626a29dfc5cc766e1eb67d8021b1e77d0861e4e850"},
-    {file = "orjson-3.6.3-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:c3beff02a339f194274ec1fcf03e2c1563e84f297b568eb3d45751722454a52e"},
-    {file = "orjson-3.6.3-cp38-none-win_amd64.whl", hash = "sha256:8f105e9290f901a618a0ced87f785fce2fcf6ab753699de081d82ee05c90f038"},
-    {file = "orjson-3.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7936bef5589c9955ebee3423df51709d5f3b37ef54b830239bddb9fa5ead99f4"},
-    {file = "orjson-3.6.3-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82e3afbf404cb91774f894ed7bf52fd83bb1cc6bd72221711f4ce4e7774f0560"},
-    {file = "orjson-3.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c702c78c33416fc8a138c5ec36eef5166ecfe8990c8f99c97551cd37c396e4d"},
-    {file = "orjson-3.6.3-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:4ebb464b8b557a1401a03da6f41761544886db95b52280e60d25549da7427453"},
-    {file = "orjson-3.6.3-cp39-none-win_amd64.whl", hash = "sha256:720a7d7ba1dcf32bbd8fb380370b1fdd06ed916caea48403edd64f2ccf7883c1"},
-    {file = "orjson-3.6.3.tar.gz", hash = "sha256:353cc079cedfe990ea2d2186306f766e0d47bba63acd072e22d6df96c67be993"},
+    {file = "orjson-3.6.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6c444edc073eb69cf85b28851a7a957807a41ce9bb3a9c14eefa8b33030cf050"},
+    {file = "orjson-3.6.5-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:432c6da3d8d4630739f5303dcc45e8029d357b7ff8e70b7239be7bd047df6b19"},
+    {file = "orjson-3.6.5-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:0fa32319072fadf0732d2c1746152f868a1b0f83c8cce2cad4996f5f3ca4e979"},
+    {file = "orjson-3.6.5-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:0d65cc67f2e358712e33bc53810022ef5181c2378a7603249cd0898aa6cd28d4"},
+    {file = "orjson-3.6.5-cp310-none-win_amd64.whl", hash = "sha256:fa8e3d0f0466b7d771a8f067bd8961bc17ca6ea4c89a91cd34d6648e6b1d1e47"},
+    {file = "orjson-3.6.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:470596fbe300a7350fd7bbcf94d2647156401ab6465decb672a00e201af1813a"},
+    {file = "orjson-3.6.5-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d2680d9edc98171b0c59e52c1ed964619be5cb9661289c0dd2e667773fa87f15"},
+    {file = "orjson-3.6.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001962a334e1ab2162d2f695f2770d2383c7ffd2805cec6dbb63ea2ad96bf0ad"},
+    {file = "orjson-3.6.5-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:522c088679c69e0dd2c72f43cd26a9e73df4ccf9ed725ac73c151bbe816fe51a"},
+    {file = "orjson-3.6.5-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:d2b871a745a64f72631b633271577c99da628a9b63e10bd5c9c20706e19fe282"},
+    {file = "orjson-3.6.5-cp37-none-win_amd64.whl", hash = "sha256:51ab01fed3b3e21561f21386a2f86a0415338541938883b6ca095001a3014a3e"},
+    {file = "orjson-3.6.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:fc7e62edbc7ece95779a034d9e206d7ba9e2b638cc548fd3a82dc5225f656625"},
+    {file = "orjson-3.6.5-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0720d60db3fa25956011a573274a269eb37de98070f3bc186582af1222a2d084"},
+    {file = "orjson-3.6.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169a8876aed7a5bff413c53257ef1fa1d9b68c855eb05d658c4e73ed8dff508"},
+    {file = "orjson-3.6.5-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:331f9a3bdba30a6913ad1d149df08e4837581e3ce92bf614277d84efccaf796f"},
+    {file = "orjson-3.6.5-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:ece5dfe346b91b442590a41af7afe61df0af369195fed13a1b29b96b1ba82905"},
+    {file = "orjson-3.6.5-cp38-none-win_amd64.whl", hash = "sha256:6a5e9eb031b44b7a429c705ca48820371d25b9467c9323b6ae7a712daf15fbef"},
+    {file = "orjson-3.6.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:206237fa5e45164a678b12acc02aac7c5b50272f7f31116e1e08f8bcaf654f93"},
+    {file = "orjson-3.6.5-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d5aceeb226b060d11ccb5a84a4cfd760f8024289e3810ec446ef2993a85dbaca"},
+    {file = "orjson-3.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dba3dbc0563c49719e8cc7d1568a5cf738accfcd1aa6ca5e8222b57436e75e"},
+    {file = "orjson-3.6.5-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:443f39bc5e7966880142430ce091e502aea068b38cb9db5f1ffdcfee682bc2d4"},
+    {file = "orjson-3.6.5-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:a06f2dd88323a480ac1b14d5829fb6cdd9b0d72d505fabbfbd394da2e2e07f6f"},
+    {file = "orjson-3.6.5-cp39-none-win_amd64.whl", hash = "sha256:82cb42dbd45a3856dbad0a22b54deb5e90b2567cdc2b8ea6708e0c4fe2e12be3"},
+    {file = "orjson-3.6.5.tar.gz", hash = "sha256:eb3a7d92d783c89df26951ef3e5aca9d96c9c6f2284c752aa3382c736f950597"},
 ]
 packaging = [
-    {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
-    {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
+    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
 pandas = [
     {file = "pandas-1.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bf23a3b54d128b50f4f9d4675b3c1857a688cc6731a32f931837d72effb2698d"},
@@ -532,72 +620,79 @@ pathspec = [
     {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
-    {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
+    {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
+    {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
 ]
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
     {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
 ]
 py = [
-    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
-    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
 pycodestyle = [
-    {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
-    {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+    {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
+    {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
 ]
 pyflakes = [
-    {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
-    {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
+    {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
+    {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
 ]
 pymssql = [
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0fc1723898fba044cd7e63741eb2dfbd09083aca5b9365b88e1757786bb559ac"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:645e0b90bf969464e1f6868270c81bb1ac2f7b00bc793dc06eed15068f39dfd6"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03469104efd5e5469b6c1892a0393e1fb4cd3169422e88537bec1b4294c98528"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab97afa1a5bd7bceb45fb0ab3c6387dfa138752115441babfa4e7988036d4d7"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_24_i686.whl", hash = "sha256:3655b20f08efa9d5127c1d9d2da5d5c7a0206a55e37a0b5e45620c30d7461948"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_24_x86_64.whl", hash = "sha256:494471e3df2f27668f65025be2a62807ea7df4776d5a1717710ce7b5b42a1b6d"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66b121b0ccb38ec240701fd0fd04d8d5923385b03950d930eff1936ec455afe9"},
-    {file = "pymssql-2.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:642b423d0f78ec2a5c2fb31ed248399ac43dce2a8abec11b4a9fe0e7f1a38c5d"},
-    {file = "pymssql-2.2.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:c3d860cf0e5685675bf06146eb5a0457589580dd04c41571efe940cbc0c2de4e"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd00d3fab48fc931db305e378f706ce73abc8aba0969fdf8fb2ac6df00d6e4ad"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9912b240ca776d7e842b44ed858b4946aaeb1863da9410f3cd4d241afcf0a99f"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a563cccc730cf25af262a767746451e3b11c243c9fa2c141f23d7a9b3c808ce6"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9987a0af5bc66dd9bd9391ec2f24cbc53bf0bd1ca3f581bc183bfd4ca5ddc204"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_24_i686.whl", hash = "sha256:26f365b03893179dee76bb337315d99f4605c1634dd5538ed85dd1822eb41e71"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:0423cc3b3fc6ea249b13794d727e6e727bc070a14a7840977ae92117d3d091bd"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50e81c18aceaf2e254e0085d503451429210896f0d27912fb928329b1eda00cd"},
-    {file = "pymssql-2.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:553e6b7f6fbb1da6167b58a6ed7f8ff5261a073fa04d7b72a8384659c9bcb06f"},
-    {file = "pymssql-2.2.2-cp37-cp37m-win32.whl", hash = "sha256:70a68a95a072199522433b84fe4996157ddb64a1d008435aa54d4b9f004831b7"},
-    {file = "pymssql-2.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6e37b57d1f0c40c4cee9a75fb55964fc6685d2223dbedf9bd1b87f0274e118cc"},
-    {file = "pymssql-2.2.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:41fbb522decbd7f225f72ac1e2ccf7bb343c58f516ea3165855245b7e22877d2"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9f28819181b386c45de261c289bdec15f87807d3c03e61b00fa85beffbed621"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f7eedf5b8bd55115a2cab130068b4dccb526fbe8c7bab54468ec31feeb6513d3"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab9ecb7cec689177955728d116788be27fc65b9d4001ba8e6d8c6e5a5ddf677"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f4eeb68a12981095b62e3170c94a5414f3c428aa90fd7bd3b4ce751d578912"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_24_i686.whl", hash = "sha256:463308e52e978d6b7301bbcdf47f866163a57f0783ec0b581e196d7752d05811"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:7432101f86151a93e77b00c5f36cef0ecd2ac6029d1f9751656647c05ab9ee93"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6988e0e0683fb46b59349927453afc7d75a745aff71d801ebf9ba699e4d815f5"},
-    {file = "pymssql-2.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:16d2bd236ef02052b96025d0ca0de436182e36cd95afe7617d8106d2ff7f9485"},
-    {file = "pymssql-2.2.2-cp38-cp38-win32.whl", hash = "sha256:16907b8d272945ee8861ef13c9657348d2aee1e06434e4a0487988eb1b6938c8"},
-    {file = "pymssql-2.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:846a90124e4798a5957996653d71b5ec49931c99fe91b5d9f0e73cdca51e85d3"},
-    {file = "pymssql-2.2.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b243405d6e24a35f123d8a4a3258659fc920a23b2f2b3dbaace097c093f45dba"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0440a138eaac30a449cab497757a849b40c2d0b180b48a8c4151de9bf3bf7e61"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:27c4d80c3086ed56ccf93dc03262db7a823d8979dd17e84c8f629a0eeef0526d"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:182c6f3a3ba67bf7f750cfce0b3c20abb588b5ae0ffa9387645f0dfd76791b7a"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5c4f582d45fed36bbdc1ebf960f6dbe227c6666e3a927f8c99b76e9e18de7b"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_24_i686.whl", hash = "sha256:9755267acc83568adc4e89cc5668424d34ef651d0a2e827304e7f995b5dcbca8"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:0932ed12474cb926a33ba5b623506d29cf1c170e2ea7ca75f342bb5fe3a67906"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8c3bb372a6609f5e46a6702f18bc8370e0f4a53f73ef8559e4a7df58ae4afa"},
-    {file = "pymssql-2.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4df67d7c6e2cd99cbf8a1c2d4236a3b81c3bbd7602b95a211ff4da7460ffa83c"},
-    {file = "pymssql-2.2.2-cp39-cp39-win32.whl", hash = "sha256:00b5d8d0e6e99dd07677f663a42fe3b53285db02682f9ea8271dab0e30e4404f"},
-    {file = "pymssql-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:213fc0f970324ed5eac25d9442e5b516724f65ed1a96ed6c0a474dd7419b6a97"},
-    {file = "pymssql-2.2.2.tar.gz", hash = "sha256:db3b31b1e73a856aa5a5181ff9f2b0c595611f5661aaeffee7c12a4a57b62fc8"},
+    {file = "pymssql-2.2.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:852fe689b4630aa453eb02b091240d98e07eb60fd7a3c150396d0eb7fa10780a"},
+    {file = "pymssql-2.2.3-cp310-cp310-win32.whl", hash = "sha256:2bffb7d07a3914b73086a17583f0a0ca68dfc93f7c70b3e214beeacbd2339e12"},
+    {file = "pymssql-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:75e65e53f5014f24dae8c150f532e9313ce0c166123b0951a334d61ada0aaece"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d90eb4dd06f1b8eb4805d137e8a0747665ec15c46736a4f1cc01afe6da4d07b7"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:52a8163fcbdb1ebe2756d5a9f439ea8f30b9748855e782c1f98465050e8bab61"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:873d026423768f4e1ec0b452ff9fe951beb4632e3f314c19b22a30586db1de02"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cb52bf5d0eabf510b1ebc8b38e71cd3ca877e69e0e424f15e1f7b319e49b10e"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_24_i686.whl", hash = "sha256:d402ed357f5da445a29a6a844ac4359872d3870e6ee034ec049e83095d6cd00e"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_24_x86_64.whl", hash = "sha256:538b5a4ca9b4bd3f0719acb608e08e1d586a08ed9ed0d7ea1ffa3ef03518df68"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bc2e51739d932fc7af44354c00297baa451f99a3e3d7a59a6aa3be5b2534076"},
+    {file = "pymssql-2.2.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3f7def19af5af2d83f2f7559b67ae37315d76807e4e6d7957d53755f982d1d8b"},
+    {file = "pymssql-2.2.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:0a1ccec4eb9bb35ad9f211428793fd29e59f3902279ad2903133d6c2081550c1"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5cc68a295207ced45c0155e263ecdd249cbcfac2d77a58d2dbda5c54a532044f"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8eaba0e0fe013dd727f960e7d025071d3b03123eb41b9fc11f225728e00cc2b4"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d06c950d13c198a6a70f846298d3f4ba820b998341b61c487049c3e5dd19223"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b13bc9d7c82ef24fd2df690747e99160bd6611d97b875518757836ab3c3fea0"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_24_i686.whl", hash = "sha256:2c08ed13f090b9df251ba2590a1316939dea1744679ec3f164968e4b51263b53"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:6bfd0859201095b4a8913e88ec9999c72053e2f92aa05413b32ac5855970b726"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:860979466f9dd6d7d20fb83db2498aa67bab172a85e660a93a93c9d716b8e268"},
+    {file = "pymssql-2.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7b7d04c3abf78961d04c5c620118e5b34589619cd9f7a95a4dbc439186095bc4"},
+    {file = "pymssql-2.2.3-cp37-cp37m-win32.whl", hash = "sha256:bd6b49ad64026eac1b23c2ab8d237b81d0eb83fc3872562b9101cc970581735d"},
+    {file = "pymssql-2.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:7d3ccb1fe4fa957ad0d430c0efa342c3ead9fd3d546cc867042b146620d524ee"},
+    {file = "pymssql-2.2.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8f81d90d711a151a72960e3ad1dd8cb71b9d288979e1ead913e1e654e7ee504d"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a09fb8456279acd87615708a60bc299640e0be149a117508eba6f1c4ad0fb3e"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec76e9773d7af22f39cc902e92946f453efec1af3037e8b95dcdf568d3bdbee2"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d995b0e20961e4366ecbef34faf2ef8a05a963780fb5f7ca35c07c61369c889"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6aa047efe4ba438261477ebcb11bba6d913bbaae114d55d95f04760c3be6df"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_24_i686.whl", hash = "sha256:6ee59e594c10bd7dab7776ff6278f318625b79d7c8e9ef9813af6bd953d7d748"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:fc103b304a9bf2a6f92f2dee12b6a9212000781c17a9aa280621cd09f0f5eda0"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e83e049b1d7a64d5aab9fc31477ebeb7ae4a0dcd40f273ee5cfd22b3ed87004d"},
+    {file = "pymssql-2.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:46a8ad501353de0539b35d4a8b6a0f27430e9e240377673b42e9aceedb5313e4"},
+    {file = "pymssql-2.2.3-cp38-cp38-win32.whl", hash = "sha256:df3c039679df3ce1ad3e55a8daccfd5afd65d2e2aed738515ecad6ad778fb160"},
+    {file = "pymssql-2.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:aab0a93cab37273228b859cf674449b0781df605a26739b72e5bb029682bd618"},
+    {file = "pymssql-2.2.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8a526ae0ce3f3c9acdfc4db1e61cf82bac528ee2848496c7d7eb25881e4824af"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b21db3e4e946b947076b915cc2a91e5c052ff1c81409fb33f1d954554600e866"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cc13762966ddd1e06d324662bee967fdbc8924872cde4a891556f0401ef9de0"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deab7a7e9dfaf11f04234d60faa821244fb820db104dc5c47aedf8c4751d9c6b"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ded7fca8bf196b86c1cccb833960b48d946b437849a08a8a25c476ab1b335fb"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_24_i686.whl", hash = "sha256:f53203f91baa569606c1731d2ac4fb5d79cbce28fa7d902a2938c31a96b984e5"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:d4f6ae1888fc41e2fabfad16806699c0b0ffdc1ac22c2962f5918c69b5d58ca6"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8ab926fc78108e9e76e844794b5de5fca20ed2f0326d788ecc529072ce9bd6c0"},
+    {file = "pymssql-2.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9cc4ef9ce01f14ab435e72914719a0f37999bae60b82d50bd1f0ce865083011"},
+    {file = "pymssql-2.2.3-cp39-cp39-win32.whl", hash = "sha256:ddd9c6ddf8966292d3a4f1eff27757d91d7f572f8b8c34ec3aa0eebb1c1f5da2"},
+    {file = "pymssql-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:496ad5ee139f6d15c5006f614e602891f89740205e58b3aee67d9a22e6bc941c"},
+    {file = "pymssql-2.2.3.tar.gz", hash = "sha256:b94fddc6d5f168205d14294d36969025967ff1837adcc1664c19968de61dca80"},
 ]
 pyparsing = [
-    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
-    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+    {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
+    {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
+]
+pyright = [
+    {file = "pyright-0.0.13-py3-none-any.whl", hash = "sha256:520458c87dc456ed0d405a08a565066aaf5b76281d23861759cfaa3c73f5bda0"},
+    {file = "pyright-0.0.13.tar.gz", hash = "sha256:ca97f9121927847d0aea5d2e757a0bf6d3c25c116c90a3ce6263b4167a12dfc4"},
 ]
 pytest = [
     {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
@@ -616,55 +711,12 @@ python-dateutil = [
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
 ]
 python-dotenv = [
-    {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
-    {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
+    {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
+    {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
 ]
 pytz = [
-    {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
-    {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
-]
-regex = [
-    {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"},
-    {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"},
-    {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"},
-    {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"},
-    {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"},
-    {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"},
-    {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"},
-    {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"},
-    {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"},
-    {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"},
-    {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"},
-    {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"},
-    {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"},
-    {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"},
-    {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"},
-    {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"},
-    {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"},
-    {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"},
-    {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"},
-    {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"},
-    {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"},
-    {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"},
-    {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"},
-    {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"},
-    {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"},
-    {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"},
-    {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"},
-    {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"},
-    {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"},
-    {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"},
-    {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"},
-    {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"},
-    {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"},
-    {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"},
-    {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"},
-    {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"},
-    {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"},
-    {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"},
-    {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"},
-    {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"},
-    {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"},
+    {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
+    {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
 ]
 six = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -675,47 +727,35 @@ toml = [
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tomli = [
-    {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"},
-    {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"},
+    {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
+    {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
 ]
 typed-ast = [
-    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
-    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
-    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
-    {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
-    {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
-    {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
-    {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
-    {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
-    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
-    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
-    {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
-    {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
-    {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
-    {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
-    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
-    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
-    {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
-    {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
-    {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
-    {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
+    {file = "typed_ast-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8314c92414ce7481eee7ad42b353943679cf6f30237b5ecbf7d835519e1212"},
+    {file = "typed_ast-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b53ae5de5500529c76225d18eeb060efbcec90ad5e030713fe8dab0fb4531631"},
+    {file = "typed_ast-1.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:24058827d8f5d633f97223f5148a7d22628099a3d2efe06654ce872f46f07cdb"},
+    {file = "typed_ast-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a6d495c1ef572519a7bac9534dbf6d94c40e5b6a608ef41136133377bba4aa08"},
+    {file = "typed_ast-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de4ecae89c7d8b56169473e08f6bfd2df7f95015591f43126e4ea7865928677e"},
+    {file = "typed_ast-1.5.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:256115a5bc7ea9e665c6314ed6671ee2c08ca380f9d5f130bd4d2c1f5848d695"},
+    {file = "typed_ast-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7c42707ab981b6cf4b73490c16e9d17fcd5227039720ca14abe415d39a173a30"},
+    {file = "typed_ast-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:71dcda943a471d826ea930dd449ac7e76db7be778fcd722deb63642bab32ea3f"},
+    {file = "typed_ast-1.5.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4f30a2bcd8e68adbb791ce1567fdb897357506f7ea6716f6bbdd3053ac4d9471"},
+    {file = "typed_ast-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ca9e8300d8ba0b66d140820cf463438c8e7b4cdc6fd710c059bfcfb1531d03fb"},
+    {file = "typed_ast-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9caaf2b440efb39ecbc45e2fabde809cbe56272719131a6318fd9bf08b58e2cb"},
+    {file = "typed_ast-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9bcad65d66d594bffab8575f39420fe0ee96f66e23c4d927ebb4e24354ec1af"},
+    {file = "typed_ast-1.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:591bc04e507595887160ed7aa8d6785867fb86c5793911be79ccede61ae96f4d"},
+    {file = "typed_ast-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:a80d84f535642420dd17e16ae25bb46c7f4c16ee231105e7f3eb43976a89670a"},
+    {file = "typed_ast-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:38cf5c642fa808300bae1281460d4f9b7617cf864d4e383054a5ef336e344d32"},
+    {file = "typed_ast-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b6ab14c56bc9c7e3c30228a0a0b54b915b1579613f6e463ba6f4eb1382e7fd4"},
+    {file = "typed_ast-1.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2b8d7007f6280e36fa42652df47087ac7b0a7d7f09f9468f07792ba646aac2d"},
+    {file = "typed_ast-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b6d17f37f6edd879141e64a5db17b67488cfeffeedad8c5cec0392305e9bc775"},
+    {file = "typed_ast-1.5.1.tar.gz", hash = "sha256:484137cab8ecf47e137260daa20bafbba5f4e3ec7fda1c1e69ab299b75fa81c5"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
-    {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
-    {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
+    {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
+    {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
 ]
 zipp = [
-    {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
-    {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
+    {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
+    {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
 ]
diff --git a/pymssqlutils/databaseresult.py b/pymssqlutils/databaseresult.py
index aab2ec3..74c3175 100644
--- a/pymssqlutils/databaseresult.py
+++ b/pymssqlutils/databaseresult.py
@@ -4,13 +4,29 @@
 import warnings
 from datetime import date, datetime, time, timedelta, timezone
 from decimal import Decimal
-from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    List,
+    NoReturn,
+    Optional,
+    Tuple,
+    TypeVar,
+    Union,
+)
 
 import pymssql as sql
+from pymssql import Cursor
 
 from pymssqlutils.helpers import SQLParameter
 
+if TYPE_CHECKING:
+    from pandas import DataFrame
+
 logger = logging.getLogger(__name__)
+T = TypeVar("T")
 
 
 def _parse_datetimeoffset_from_bytes(item: bytes) -> datetime:
@@ -21,11 +37,15 @@ def _parse_datetimeoffset_from_bytes(item: bytes) -> datetime:
     ) + timedelta(days=days, minutes=tz, microseconds=microseconds / 10)
 
 
-def identity(x):
+def _identity(x: T) -> T:
     return x
 
 
-def cursor_generator(cursor_, size=10000):
+def _unset(x: T) -> T:
+    return x
+
+
+def _cursor_generator(cursor_: Cursor, size: int = 10000):
     while True:
         out = cursor_.fetchmany(size=size)
         if not out:
@@ -36,11 +56,11 @@ def cursor_generator(cursor_, size=10000):
 def _get_data_mapper(sql_type_hint: int, item: Any) -> Callable[[Any], SQLParameter]:
     if sql_type_hint == 1:  # STRING: str
         if isinstance(item, str):
-            return identity
+            return _identity
 
     if sql_type_hint == 2:  # BINARY: bytes, datetime, time, date
         if isinstance(item, (datetime, date, time)):
-            return identity
+            return _identity
         if isinstance(item, uuid.UUID):
             return str
         if isinstance(item, bytes):
@@ -49,15 +69,15 @@ def _get_data_mapper(sql_type_hint: int, item: Any) -> Callable[[Any], SQLParame
                 return _parse_datetimeoffset_from_bytes
             except (ValueError, struct.error):
                 # it's not a datetime
-                return identity
+                return _identity
 
     if sql_type_hint == 3:  # NUMBER: int, float
         if isinstance(item, (int, float)):
-            return identity
+            return _identity
 
     if sql_type_hint == 4:  # DATETIME: datetime
         if isinstance(item, datetime):
-            return identity
+            return _identity
 
     if sql_type_hint == 5:  # DECIMAL: float (forced), int
         # warning: bigint is returned as Decimal type
@@ -67,7 +87,33 @@ def _get_data_mapper(sql_type_hint: int, item: Any) -> Callable[[Any], SQLParame
     warnings.warn(
         f"unhandled type ({sql_type_hint}, {item}, {type(item)})", RuntimeWarning
     )
-    return identity
+    return _identity
+
+
+def _get_cleaned_data(
+    cursor: Cursor, source_types: Tuple[int, ...]
+) -> List[Tuple[Any, ...]]:
+    data_mappers: List[Callable[[Any], Any]] = [_unset] * len(source_types)
+
+    def clean_item(idx: int, item: Any) -> Any:
+        if item is None:
+            return None
+
+        data_mapper = data_mappers[idx]
+
+        if data_mapper is _unset:
+            data_mapper = _get_data_mapper(source_types[idx], item)
+            data_mappers[idx] = data_mapper
+            return data_mapper(item)
+
+        return data_mapper(item)
+
+    def clean_batch(items: Tuple[Tuple[Any, ...]]) -> Tuple[Tuple[Any, ...]]:
+        return tuple(
+            tuple(clean_item(e, item) for e, item in enumerate(row)) for row in items
+        )
+
+    return [item for items in _cursor_generator(cursor) for item in clean_batch(items)]
 
 
 class DatabaseError(Exception):
@@ -78,10 +124,9 @@ class DatabaseResult:
     ok: bool
     fetch: bool
     commit: bool
-    columns: Optional[Tuple[str, ...]]
-    error: sql.Error
-    source_types: Optional[Tuple[int, ...]]
-    _data_mappers: Optional[List[Optional[Callable[[Any], SQLParameter]]]]
+    error: Optional[sql.Error]
+    _columns: Optional[Tuple[str, ...]]
+    _source_types: Optional[Tuple[int, ...]]
     _data: Optional[List[Tuple[SQLParameter, ...]]]
 
     def __init__(
@@ -96,59 +141,47 @@ def __init__(
         self.fetch = fetch
         self.commit = commit
         self.error = error
-        self.columns = None
-        self.source_types = None
-        self._data_mappers = None
+        self._columns = None
+        self._source_types = None
         self._data = None
 
         if self.error:
             return
 
         if fetch:
-            self.columns = tuple(x[0] for x in cursor.description)
-            self.source_types = tuple(x[1] for x in cursor.description)
-            self._data_mappers = [None] * len(self.source_types)
-            self._data = [
-                item
-                for items in cursor_generator(cursor)
-                for item in self._clean_batch(items)
-            ]
+            if cursor is None:
+                raise ValueError("cursor must be passed to fetch")
 
-    def _clean_batch(self, items) -> Tuple[Tuple[SQLParameter, ...]]:
-        return tuple(
-            tuple(self._clean_item(e, item) for e, item in enumerate(row))
-            for row in items
-        )
-
-    def _clean_item(self, idx: int, item: Any) -> SQLParameter:
-        if item is None:
-            return None
-
-        if self._data_mappers[idx] is not None:
-            return self._data_mappers[idx](item)
+            self._columns = tuple(x[0] for x in cursor.description)
+            self._source_types = tuple(x[1] for x in cursor.description)
+            self._data = _get_cleaned_data(cursor, self._source_types)
 
-        data_mapper = _get_data_mapper(self.source_types[idx], item)
-
-        if data_mapper is None:
-            return item
+    @property
+    def source_types(self) -> Tuple[int, ...]:
+        if self._source_types is not None:
+            return self._source_types
+        self._raise_no_data_error()
 
-        self._data_mappers[idx] = data_mapper
-        return self._data_mappers[idx](item)
+    @property
+    def columns(self) -> Tuple[str, ...]:
+        if self._columns is not None:
+            return self._columns
+        self._raise_no_data_error()
 
     @property
-    def data(self) -> Optional[List[Dict[str, SQLParameter]]]:
+    def data(self) -> List[Dict[str, Any]]:
         if self._data is not None:
             return [
                 {self.columns[e]: item for e, item in enumerate(row)}
                 for row in self._data
             ]
-        return None
+        self._raise_no_data_error()
 
     @property
-    def raw_data(self) -> Optional[List[Tuple[SQLParameter, ...]]]:
+    def raw_data(self) -> List[Tuple[Any, ...]]:
         if self._data is not None:
             return self._data
-        return None
+        self._raise_no_data_error()
 
     def write_error_to_logger(self, name: str = "unknown") -> None:
         """
@@ -159,6 +192,8 @@ def write_error_to_logger(self, name: str = "unknown") -> None:
         """
         if self.ok:
             raise ValueError("This execution did not error")
+        if self.error is None:
+            raise ValueError("This execution did not provide an error message")
         error_text = str(
             self.error.args[1] if len(self.error.args) >= 2 else self.error
         )
@@ -169,20 +204,25 @@ def write_error_to_logger(self, name: str = "unknown") -> None:
 
     def raise_error(self, name: str = "unknown") -> None:
         """
-        Raises a pymssql DatabaseError with an optional name to help identify the operation.
+        Raises a pymssql DatabaseError with an optional name to help identify
+        the operation.
+
         :param name: str, an optional name to show in the error string.
         :return: None
         """
         if self.ok:
             raise ValueError("This execution did not error")
+        if self.error is None:
+            raise ValueError("This execution did not provide an error instance")
         raise DatabaseError(
             f"<{name}|fetch={self.fetch},commit={self.commit}> bad execution"
         ) from self.error
 
-    def to_dataframe(self, *args, **kwargs):
+    def to_dataframe(self, *args, **kwargs) -> "DataFrame":
         """
         Return the data as a Pandas DataFrame, all args and kwargs are passed to
         the DataFrame initiation method.
+
         :return: a DataFrame
         """
         if self.data is None:
@@ -192,14 +232,17 @@ def to_dataframe(self, *args, **kwargs):
         try:
             from pandas import DataFrame
         except ImportError:
-            raise RuntimeError("Pandas must be installed to use this method")
+            raise ImportError(
+                "Pandas must be installed to use this method"
+            ) from ImportError
 
         return DataFrame(data=self.data, *args, **kwargs)
 
     def to_json(self, as_bytes=False) -> Union[bytes, str]:
         """
         returns the serialized data as a JSON format string.
-        :params as_bytes: bool, if True returns the JSON object as UTF-8 encoded bytes instead of string
+        :params as_bytes: bool, if True returns the JSON object as UTF-8 encoded bytes
+                          instead of string
         :return: Union[bytes, str]
         """
 
@@ -209,13 +252,25 @@ def to_json(self, as_bytes=False) -> Union[bytes, str]:
         # noinspection PyUnresolvedReferences
         try:
             from orjson import dumps
-        except ImportError:
-            raise RuntimeError(
+        except ImportError as err:
+            raise ImportError(
                 "ORJSON must be installed to use this method, you can install "
                 + "this by running `pip install --upgrade pymssql-utils[json]`"
-            )
+            ) from err
 
         if as_bytes:
-            return dumps(self.data)
+            return dumps(self._data)
         else:
-            return dumps(self.data).decode("UTF-8")
+            return dumps(self._data).decode("UTF-8")
+
+    def _raise_no_data_error(self) -> NoReturn:
+        if not self.fetch:
+            raise ValueError(
+                "This DatabaseResult was initialised with fetch=False, "
+                "and therefore has no data."
+            )
+        if not self.ok:
+            raise ValueError(
+                "This DatabaseResult was not successful, " "and therefore has no data."
+            )
+        raise ValueError("This DatabaseResult has no data.")
diff --git a/pymssqlutils/helpers.py b/pymssqlutils/helpers.py
index fcbaf72..351de40 100644
--- a/pymssqlutils/helpers.py
+++ b/pymssqlutils/helpers.py
@@ -1,5 +1,4 @@
-from datetime import date, datetime, time
-from typing import Dict, Optional, Tuple, Union
+from typing import Any, Dict, Tuple, Union
 
-SQLParameter = Optional[Union[str, int, float, date, datetime, time, bool, bytes]]
-SQLParameters = Union[Tuple[SQLParameter, ...], Dict[str, SQLParameter], SQLParameter]
+SQLParameter = Any
+SQLParameters = Union[Tuple[Any, ...], Dict[str, Any], Any]
diff --git a/pymssqlutils/methods.py b/pymssqlutils/methods.py
index b60232d..abe5e4b 100644
--- a/pymssqlutils/methods.py
+++ b/pymssqlutils/methods.py
@@ -18,8 +18,8 @@
 
 def substitute_parameters(operation: str, parameters: SQLParameters) -> str:
     """
-    This function returns the SQL operation that would be executed on the server (i.e. after
-    parsing and substituting of parameters). Useful for logging & debugging.
+    This function returns the SQL operation that would be executed on the server (i.e.
+    after parsing and substituting of parameters). Useful for logging & debugging.
 
     :param operation: The SQL operation requiring substitution
     :param parameters: The parameters to substitute in
@@ -49,10 +49,14 @@ def set_connection_details(
 ) -> None:
     """
     Sets the relevant environment variable for each passed parameter
-    :param server: the network address of the SQL server to connect to, sets 'MSSQL_SERVER' in the environment
-    :param database: the default database to use on the SQL server, sets 'MSSQL_DATABASE' in the environment
-    :param user: the user to authenticate against the SQL server with, sets 'MSSQL_USER' in the environment
-    :param password: the password to authenticate against the SQL server with, sets 'MSSQL_PASSWORD' in the environment
+    :param server: the network address of the SQL server to connect to,
+                   sets 'MSSQL_SERVER' in the environment
+    :param database: the default database to use on the SQL server,
+                     sets 'MSSQL_DATABASE' in the environment
+    :param user: the user to authenticate against the SQL server with,
+                 sets 'MSSQL_USER' in the environment
+    :param password: the password to authenticate against the SQL server with,
+                     sets 'MSSQL_PASSWORD' in the environment
     :return:
     """
     if server:
@@ -76,7 +80,8 @@ def _with_conn_details(kwargs: Dict) -> Dict:
 
     if not kwargs["server"]:
         raise ValueError(
-            "server must be passed as a parameter or MSSQL_SERVER must be set in the environment"
+            "`server` must be passed as a parameter or `MSSQL_SERVER` "
+            "must be set in the environment."
         )
 
     return kwargs
@@ -97,7 +102,8 @@ def query(
     :type operation: str
     :param parameters: parameters to substitute into the operation.
     :type parameters: SQLParameters
-    :param raise_errors: if True raises errors, else DatabaseResult class will contain the error details
+    :param raise_errors: if True raises errors, else DatabaseResult class will
+                         contain the error details
     :type raise_errors: bool, optional
     :return: a DatabaseResult class.
     :rtype: DatabaseResult
@@ -124,35 +130,44 @@ def execute(
     **kwargs,
 ) -> DatabaseResult:
     """
-    Used for a SQL Operation/s which COMMIT the transaction. There are two ways of using this function:
+    Used for a SQL Operation/s which COMMIT the transaction.
+    There are two ways of using this function:
 
     Passing in a single operation (str) to operations:
-        * If parameters is singular, this calls pymssql.execute() and executes a single operation
-        * If parameters is plural, this calls pymssql.execute_many() and executes one execution per parameter set
+        * If parameters is singular, this calls pymssql.execute()
+          and executes a single operation
+        * If parameters is plural, this calls pymssql.execute_many()
+          and executes one execution per parameter set
 
     Passing in multiple operations (List[str]) to operations:
-        * If parameters is None, this calls pymssql.execute_many() and executes one execution per operation
-        * If parameters is the same length as operations, this calls pymssql.execute() multiple times
-          and executes one execution per operation.
+        * If parameters is None, this calls pymssql.execute_many()
+          and executes one execution per operation
+        * If parameters is the same length as operations, this calls pymssql.execute()
+          multiple times and executes one execution per operation.
 
-    Optionally batch_size can be specified to use string concatenation to batch the operations, this can
-    provide significant performance gains if executing 100+ small operations. This is similar to fast_executemany
-    found in pyodbc package. A value of 500-1000 is a good default.
+    Optionally batch_size can be specified to use string concatenation to batch
+    the operations, this can provide significant performance gains if executing 100+
+    small operations. This is similar to fast_executemany found in pyodbc package. A
+    value of 500-1000 is a good default.
 
     Fetch can be set to True, this will only return the last result set.
 
-    :param operations: the SQL Operation/s to execute. If this is a list then parameters needs to be None
-        or a list of the same length.
+    :param operations: the SQL Operation/s to execute. If this is a list then
+                       parameters needs to be None or a list of the same length.
     :type operations: Union[str, List[str]]
-    :param parameters: parameters to substitute into the operation. These can be a single value, tuple or dictionary.
-        If operations is a list this parameter needs to either be None or a list of the same length.
+    :param parameters: parameters to substitute into the operation. These can be a
+                       single value, tuple or dictionary. If operations is a list
+                       this parameter needs to either be None or a list of the
+                       same length.
     :type parameters: Union[SQLParameters, List[SQLParameters]], optional
-    :param batch_size: If specified concatenate the operations together according to the batch_size,
-        raises an error if set to True and both operations and parameters are singular
+    :param batch_size: If specified concatenate the operations together
+                       according to the batch_size, raises an error if set to True
+                       and both operations and parameters are singular
     :type batch_size: int, optional
     :param fetch: return the LAST result of the execution
     :type fetch: bool, optional
-    :param raise_errors: if True raises errors, else DatabaseResult class will contain the error details
+    :param raise_errors: if True raises errors, else DatabaseResult class will
+                         contain the error details
     :type raise_errors: bool, optional
     :return: a DatabaseResult class
     :rtype: DatabaseResult
@@ -169,7 +184,8 @@ def execute(
     if batch_size is not None:
         if singular_operations and singular_parameters:
             raise ValueError(
-                "batch_size cannot be used if both operations and parameters are singular"
+                "batch_size cannot be used if both operations "
+                "and parameters are singular"
             )
         if batch_size <= 0:
             raise ValueError("batch_size cannot be negative")
@@ -179,7 +195,8 @@ def execute(
             parameters
         ):
             raise ValueError(
-                "parameters must be the same length as operations if they are both lists"
+                "parameters must be the same length as "
+                "operations if they are both lists"
             )
 
     try:
@@ -214,8 +231,9 @@ def _get_connection(**kwargs) -> Connection:
         if tds_major < 7 or tds_minor < 3:
             message = (
                 f"Your connection is trying to use TDS Protocol {tds_major}.{tds_minor}"
-                + ", this protocol does not support time, date, datetime2 or datetimeoffset. "
-                + "It is strongly suggested you upgrade pymssql to the latest version."
+                ", this protocol does not support time, date, "
+                "datetime2 or datetimeoffset. "
+                "It is strongly suggested you upgrade pymssql to the latest version."
             )
             warnings.warn(message, RuntimeWarning)
         TDS_PROTOCOL_CHECKED = True
@@ -316,20 +334,26 @@ def model_to_values(
     append: List[Tuple[str, str]] = None,
 ) -> str:
     """
-    Transforms a Dict or Mapping into a string of the form: '([attr1], [attr2], ...) VALUES (val1, val2, ...)'.
+    Transforms a Dict or Mapping into a string of the form:
+    '([attr1], [attr2], ...) VALUES (val1, val2, ...)'.
     Intended to be used when creating dynamic SQL INSERT statements.
 
     Prepend and append can be used to add a SQL column with a static or variable value
     at the beginning or end of the values list.
 
-    e.g. passing prepend = [('prependedColumn', '@prependedColumn')] would return a string of the form:
-    '([prependedColumn], [attr1], [attr2], ...) VALUES (@prependedColumn, val1, val2, ...)'
+    e.g. passing prepend = [('prependedColumn', '@prependedColumn')] would return a
+    string of the form:
+    '([prependedColumn], [attr1], ...) VALUES (@prependedColumn, val1, ...)'
 
-    Note: prepended and appended columns are not parameter substituted, this can leave your code open to sql injection.
+    Note: prepended and appended columns are not parameter substituted,
+          this can leave your code open to sql injection.
 
-    :param model: a Dictionary or anything that implements the __dict__ method (e.g. a Pydantic Model)
-    :param prepend: prepend a variable number of columns to the beginning of the values statement.
-    :param append: append a variable number of columns to the end of the values statement.
+    :param model: a Dictionary or anything that implements the __dict__ method
+                  (e.g. a Pydantic Model)
+    :param prepend: prepend a variable number of columns to the beginning of the
+                    values statement.
+    :param append: append a variable number of columns to the end of the
+                   values statement.
     :return: str
     """
     keys = [x[0] for x in prepend] if prepend else []
diff --git a/pyproject.toml b/pyproject.toml
index 2d24c46..4df1cc4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "pymssql-utils"
-version = "0.1.5"
+version = "0.2.0"
 description = "pymssql-utils is a small library that wraps pymssql to make your life easier."
 authors = ["Tim OSullivan <tim@lanster.dev>"]
 license = "GNU LGPLv2.1"
@@ -28,9 +28,11 @@ pandas = { version = ">=1.0.0,<2.0.0", optional = true }
 pytest = "~6"
 pytest-mock = ">=3.6.0,<4.0.0"
 pytest-dotenv = ">=0.5.0,<1.0.0"
-black = "^21.6b0"
-flake8 = "^3.9.2"
+black = "^21.12b0"
+flake8 = "^4"
 isort = "^5.9.2"
+pyright = "^0.0.13"
+coverage = "^6.2"
 
 [tool.poetry.extras]
 json = ["orjson"]
@@ -51,3 +53,7 @@ line_length = 88
 
 [tool.black]
 line-length = 88
+
+[tool.pyright]
+include = ["pymssqlutils"]
+exclude = ["tests"]


Test patch:
diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
index 9b91cff..83a3ebb 100644
--- a/.github/workflows/python-test.yml
+++ b/.github/workflows/python-test.yml
@@ -7,7 +7,7 @@ jobs:
   test:
     strategy:
       matrix:
-        python-version: [3.7, 3.8, 3.9]
+        python-version: ['3.7', '3.8', '3.9']
         os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/tests/helpers.py b/tests/helpers.py
index 4b4d45f..5258271 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -107,6 +107,7 @@ def __init__(
         self.example_row_ = row
 
         if self.description_:
+            assert self.example_row_ is not None
             assert len(self.description_) == len(self.example_row_[0])
 
     def __enter__(self):
diff --git a/tests/test_database.py b/tests/test_database.py
index 164ee29..057bda4 100644
--- a/tests/test_database.py
+++ b/tests/test_database.py
@@ -119,3 +119,9 @@ def test_datetime_offset_handling():
         2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))
     )
     assert result.data[0]["col4"] == 1
+
+
+@pytest.mark.skipif(SKIP_FILE, reason=SKIP_REASON)
+def test_no_return():
+    result = sql.query("SELECT TOP 0 'test' Col1")
+    assert result.data == []
diff --git a/tests/test_databaseresult.py b/tests/test_databaseresult.py
new file mode 100644
index 0000000..d469bdc
--- /dev/null
+++ b/tests/test_databaseresult.py
@@ -0,0 +1,186 @@
+import sys
+
+import pandas
+import pymssql
+import pytest
+
+from pymssqlutils import DatabaseResult
+from pymssqlutils.databaseresult import _cursor_generator
+from tests.helpers import (
+    MockCursor,
+    check_correct_types,
+    cursor_description,
+    cursor_row,
+)
+
+
+def test_no_data_errors():
+    result = DatabaseResult(
+        ok=True,
+        fetch=False,
+        commit=False,
+    )
+
+    with pytest.raises(ValueError):
+        result.data
+    with pytest.raises(ValueError):
+        result.raw_data
+    with pytest.raises(ValueError):
+        result.columns
+    with pytest.raises(ValueError):
+        result.source_types
+
+
+def test_no_cursor_but_fetch():
+    with pytest.raises(ValueError):
+        DatabaseResult(ok=True, fetch=True, commit=False)
+
+
+def test_write_error_but_ok():
+    result = DatabaseResult(ok=True, fetch=False, commit=False)
+    with pytest.raises(ValueError, match=".*did not error"):
+        result.write_error_to_logger()
+
+
+def test_write_error_no_error():
+    result = DatabaseResult(ok=False, fetch=False, commit=False)
+    with pytest.raises(ValueError, match=".*error message"):
+        result.write_error_to_logger()
+
+
+def test_raise_error_but_ok():
+    result = DatabaseResult(ok=True, fetch=False, commit=False)
+    with pytest.raises(ValueError, match=".*did not error"):
+        result.raise_error()
+
+
+def test_raise_error_but_no_error():
+    result = DatabaseResult(ok=False, fetch=False, commit=False)
+    with pytest.raises(ValueError, match=".*error instance"):
+        result.raise_error()
+
+
+def test_write_error(caplog):
+    result = DatabaseResult(
+        ok=False, fetch=False, commit=False, error=pymssql.Error("123", "abc")
+    )
+    result.write_error_to_logger()
+    assert "Error" in caplog.text
+
+
+def test_data_parsing():
+    result = DatabaseResult(
+        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=1)
+    )
+    check_correct_types(result.data[0])
+
+
+def test_source_types():
+    result = DatabaseResult(
+        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=1)
+    )
+    assert result.source_types
+    assert isinstance(result.source_types, tuple)
+    assert isinstance(result.source_types[0], int)
+
+
+def test_unhandled_data_parsing():
+    description = (("Col_Range", 3, None, None, None, None, None),)
+    cursor = MockCursor(row_count=1, description=description, row=[(range(1, 2),)])
+    with pytest.warns(RuntimeWarning):
+        result = DatabaseResult(ok=True, fetch=True, commit=False, cursor=cursor)
+
+    assert result.data[0]["Col_Range"] == range(1, 2)
+
+
+def test_correct_row_count_large_query():
+    result = DatabaseResult(
+        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=25000)
+    )
+    assert len(result.raw_data) == 25000
+    assert len(result.data) == 25000
+
+
+def test_cursor_generator():
+    count = 0
+    row_count = 0
+    rows = 25000
+    for batch in _cursor_generator(MockCursor(row_count=rows)):
+        row_count += len(batch)
+        count += 1
+
+    assert count == (rows // 10000) + 1
+    assert row_count == rows
+
+
+def test_to_json():
+    description = list(cursor_description)
+    example_row = list(cursor_row[0])
+
+    # remove bytes as not JSON Serializable
+    for idx in (37, 9, 8):
+        example_row.pop(idx)
+        description.pop(idx)
+
+    result = DatabaseResult(
+        ok=True,
+        fetch=True,
+        commit=False,
+        cursor=MockCursor(
+            row_count=3, row=[tuple(example_row)], description=tuple(description)
+        ),
+    )
+
+    assert isinstance(result.to_json(), str)
+    assert isinstance(result.to_json(as_bytes=True), bytes)
+
+
+def test_to_json_no_orjson(monkeypatch):
+    description = list(cursor_description)
+    example_row = list(cursor_row[0])
+
+    monkeypatch.setitem(sys.modules, "orjson", None)
+
+    result = DatabaseResult(
+        ok=True,
+        fetch=True,
+        commit=False,
+        cursor=MockCursor(
+            row_count=3, row=[tuple(example_row)], description=tuple(description)
+        ),
+    )
+
+    with pytest.raises(ImportError):
+        result.to_json()
+
+
+def test_to_json_no_data():
+    result = DatabaseResult(
+        ok=True,
+        fetch=False,
+        commit=False,
+    )
+
+    with pytest.raises(ValueError):
+        result.to_json()
+
+
+def test_cast_to_dataframe():
+    result = DatabaseResult(
+        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=5)
+    )
+    df = result.to_dataframe()
+
+    assert isinstance(df, pandas.DataFrame)
+    assert df.columns.tolist() == list(result.columns)
+
+
+def test_cast_to_dataframe_no_pandas(monkeypatch):
+    monkeypatch.setitem(sys.modules, "pandas", None)
+
+    result = DatabaseResult(
+        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=5)
+    )
+
+    with pytest.raises(ImportError):
+        result.to_dataframe()
diff --git a/tests/test_main.py b/tests/test_execute.py
similarity index 53%
rename from tests/test_main.py
rename to tests/test_execute.py
index 44f3c09..931119a 100644
--- a/tests/test_main.py
+++ b/tests/test_execute.py
@@ -1,68 +1,17 @@
-from datetime import datetime, timedelta, timezone
-
 import pymssql
 import pytest
 from pytest_mock import MockerFixture
 
 import pymssqlutils as sql
-import pymssqlutils.methods
-from pymssqlutils import (
-    DatabaseResult,
-    model_to_values,
-    set_connection_details,
-    substitute_parameters,
-    to_sql_list,
-)
-from pymssqlutils.databaseresult import cursor_generator
-from pymssqlutils.methods import _with_conn_details
-from tests.helpers import (
-    MockCursor,
-    check_correct_types,
-    cursor_description,
-    cursor_row,
-)
-
+from pymssqlutils import DatabaseResult
 
-def test_with_conn_details_from_args():
-    conn_details = _with_conn_details(
-        {
-            "database": "database",
-            "server": "server",
-            "user": "user",
-            "password": "password",
-        }
-    )
-    assert conn_details["database"] == "database"
-    assert conn_details["server"] == "server"
-    assert conn_details["user"] == "user"
-    assert conn_details["password"] == "password"
 
-
-def test_with_conn_details_from_env(monkeypatch):
-    monkeypatch.setenv("MSSQL_DATABASE", "database")
+@pytest.fixture(autouse=True)
+def set_env(monkeypatch):
     monkeypatch.setenv("MSSQL_SERVER", "server")
-    monkeypatch.setenv("MSSQL_USER", "user")
-    monkeypatch.setenv("MSSQL_PASSWORD", "password")
-    conn_details = _with_conn_details({})
-    assert conn_details["database"] == "database"
-    assert conn_details["server"] == "server"
-    assert conn_details["user"] == "user"
-    assert conn_details["password"] == "password"
-
-
-def test_set_connection_details(monkeypatch):
-    set_connection_details(
-        database="database", server="server", user="user", password="password"
-    )
-    conn_details = _with_conn_details({})
-    assert conn_details["database"] == "database"
-    assert conn_details["server"] == "server"
-    assert conn_details["user"] == "user"
-    assert conn_details["password"] == "password"
 
 
 def test_execute_single_operations_no_params(mocker: MockerFixture, monkeypatch):
-    monkeypatch.setenv("MSSQL_SERVER", "server")
     conn = mocker.patch("pymssqlutils.methods._get_connection", autospec=True)
     cursor = (
         conn.return_value.__enter__.return_value.cursor.return_value.__enter__.return_value
@@ -70,7 +19,8 @@ def test_execute_single_operations_no_params(mocker: MockerFixture, monkeypatch)
     result = sql.execute("test query")
     assert cursor.execute.call_args_list == [(("test query",),)]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -86,7 +36,8 @@ def test_execute_single_operations_single_params(mocker: MockerFixture, monkeypa
         (("select 2 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -104,7 +55,8 @@ def test_execute_single_operations_multiple_params(mocker: MockerFixture, monkey
         (("select 4 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -123,7 +75,8 @@ def test_execute_single_operations_multiple_params_batched(
         (("select 4 val\n;select 5 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -140,7 +93,28 @@ def test_execute_multiple_operations_no_params(mocker: MockerFixture, monkeypatc
         (("second query",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
+    assert result.ok
+    assert result.commit
+
+
+def test_execute_multiple_operations_no_params_batched(
+    mocker: MockerFixture, monkeypatch
+):
+    monkeypatch.setenv("MSSQL_SERVER", "server")
+    conn = mocker.patch("pymssqlutils.methods._get_connection", autospec=True)
+    cursor = (
+        conn.return_value.__enter__.return_value.cursor.return_value.__enter__.return_value
+    )
+    result = sql.execute(["test query", "second query"] * 2, batch_size=2)
+    assert cursor.execute.call_args_list == [
+        (("test query\n;second query",),),
+        (("test query\n;second query",),),
+    ]
+    assert isinstance(result, DatabaseResult)
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -157,7 +131,8 @@ def test_execute_multiple_operations_single_param(mocker: MockerFixture, monkeyp
         (("select 1 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -176,7 +151,8 @@ def test_execute_multiple_operations_single_param_batched(
         (("select 1 val\n;select 1 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -195,7 +171,8 @@ def test_execute_multiple_operations_multiple_params(
         (("select 2 val",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
@@ -218,14 +195,15 @@ def test_execute_multiple_operations_multiple_params_batched(
         (("select 3 val3\n;select 4 val4",),),
     ]
     assert isinstance(result, DatabaseResult)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
     assert result.ok
     assert result.commit
 
 
 def test_query(mocker: MockerFixture, monkeypatch):
     monkeypatch.setenv("MSSQL_SERVER", "server")
-    mocker.patch("pymssqlutils.databaseresult.cursor_generator", return_value=[])
+    mocker.patch("pymssqlutils.databaseresult._cursor_generator", return_value=[])
     conn = mocker.patch("pymssqlutils.methods._get_connection", autospec=True)
     cursor = (
         conn.return_value.__enter__.return_value.cursor.return_value.__enter__.return_value
@@ -241,66 +219,6 @@ def test_query(mocker: MockerFixture, monkeypatch):
     assert not result.commit
 
 
-def test_data_parsing():
-    result = DatabaseResult(
-        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=1)
-    )
-    check_correct_types(result.data[0])
-
-
-def test_unhandled_data_parsing():
-    description = (("Col_Range", 3, None, None, None, None, None),)
-    cursor = MockCursor(row_count=1, description=description, row=[(range(1, 2),)])
-    with pytest.warns(RuntimeWarning):
-        result = DatabaseResult(ok=True, fetch=True, commit=False, cursor=cursor)
-
-    assert result.data[0]["Col_Range"] == range(1, 2)
-
-
-def test_correct_row_count_large_query():
-    result = DatabaseResult(
-        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=25000)
-    )
-    assert len(result.raw_data) == 25000
-    assert len(result.data) == 25000
-
-
-def test_cursor_generator():
-    count = 0
-    row_count = 0
-    rows = 25000
-    for batch in cursor_generator(MockCursor(row_count=rows)):
-        row_count += len(batch)
-        count += 1
-
-    assert count == (rows // 10000) + 1
-    assert row_count == rows
-
-
-def test_data_serializable():
-    pytest.importorskip("orjson")
-
-    description = list(cursor_description)
-    example_row = list(cursor_row[0])
-
-    # remove bytes as not JSON Serializable
-    for idx in (37, 9, 8):
-        example_row.pop(idx)
-        description.pop(idx)
-
-    result = DatabaseResult(
-        ok=True,
-        fetch=True,
-        commit=False,
-        cursor=MockCursor(
-            row_count=3, row=[tuple(example_row)], description=tuple(description)
-        ),
-    )
-
-    assert isinstance(result.to_json(), str)
-    assert isinstance(result.to_json(as_bytes=True), bytes)
-
-
 def test_result_error_handling_on_ignore(mocker):
     mocker.patch("pymssqlutils.methods._execute", side_effect=pymssql.OperationalError)
     result = sql.query(
@@ -309,7 +227,8 @@ def test_result_error_handling_on_ignore(mocker):
     )
     assert result.ok is False
     assert isinstance(result.error, pymssql.OperationalError)
-    assert result.data is None
+    with pytest.raises(ValueError):
+        result.data
 
 
 def test_result_error_handling_on_ignore_and_raise(mocker):
@@ -325,98 +244,3 @@ def test_result_error_handling_on_raise(mocker):
         sql.query(
             "test query",
         )
-
-
-def test_cast_to_dataframe():
-    pandas = pytest.importorskip("pandas")
-    result = DatabaseResult(
-        ok=True, fetch=True, commit=False, cursor=MockCursor(row_count=5)
-    )
-    df = result.to_dataframe()
-
-    assert isinstance(df, pandas.DataFrame)
-    assert df.columns.tolist() == list(result.columns)
-
-
-def test_dict_model_to_values():
-    model = {
-        "text": "hello",
-        "float": 1.23,
-        "datetime": datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
-        "bool": True,
-        "none": None,
-    }
-    expected = (
-        "([text], [float], [datetime], [bool], [none]) VALUES "
-        "(N'hello', 1.23, N'2020-06-01T12:30:00-01:00', 1, NULL)"
-    )
-    assert model_to_values(model) == expected
-
-
-def test_dict_model_to_values_with_prepend():
-    model = {
-        "text": "hello",
-        "float": 1.23,
-    }
-    assert (
-        model_to_values(
-            model, prepend=[("pre1", "@var1"), ("pre2", "SYSDATETIMEOFFSET()")]
-        )
-        == "([pre1], [pre2], [text], [float]) VALUES (@var1, SYSDATETIMEOFFSET(), N'hello', 1.23)"
-    )
-
-
-def test_dict_model_to_values_with_append():
-    model = {
-        "text": "hello",
-        "float": 1.23,
-    }
-    assert (
-        model_to_values(
-            model, append=[("pre1", "@var1"), ("pre2", "SYSDATETIMEOFFSET()")]
-        )
-        == "([text], [float], [pre1], [pre2]) VALUES (N'hello', 1.23, @var1, SYSDATETIMEOFFSET())"
-    )
-
-
-def test_substitute_parameters_tuple():
-    params = (
-        1.23,
-        datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
-        "hello",
-        True,
-        None,
-    )
-    query = "%s, %s, %s, %s, %s"
-    assert (
-        substitute_parameters(query, params)
-        == "1.23, N'2020-06-01T12:30:00-01:00', N'hello', 1, NULL"
-    )
-
-
-def test_substitute_parameters_single():
-    params = (
-        1.23,
-        datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
-        "hello",
-        True,
-    )
-    expected = ("1.23", "N'2020-06-01T12:30:00-01:00'", "N'hello'", "1")
-    query = "%s"
-    for param, expected in zip(params, expected):
-        assert substitute_parameters(query, param) == expected
-
-
-def test_to_sql_list():
-    assert (
-        to_sql_list(
-            (
-                1.23,
-                datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
-                "hello",
-                True,
-                None,
-            )
-        )
-        == "(1.23, N'2020-06-01T12:30:00-01:00', N'hello', 1, NULL)"
-    )
diff --git a/tests/test_methods.py b/tests/test_methods.py
new file mode 100644
index 0000000..ba06f76
--- /dev/null
+++ b/tests/test_methods.py
@@ -0,0 +1,131 @@
+from datetime import datetime, timedelta, timezone
+
+from pymssqlutils import (
+    model_to_values,
+    set_connection_details,
+    substitute_parameters,
+    to_sql_list,
+)
+from pymssqlutils.methods import _with_conn_details
+
+
+def test_with_conn_details_from_args():
+    conn_details = _with_conn_details(
+        {
+            "database": "database",
+            "server": "server",
+            "user": "user",
+            "password": "password",
+        }
+    )
+    assert conn_details["database"] == "database"
+    assert conn_details["server"] == "server"
+    assert conn_details["user"] == "user"
+    assert conn_details["password"] == "password"
+
+
+def test_with_conn_details_from_env(monkeypatch):
+    monkeypatch.setenv("MSSQL_DATABASE", "database")
+    monkeypatch.setenv("MSSQL_SERVER", "server")
+    monkeypatch.setenv("MSSQL_USER", "user")
+    monkeypatch.setenv("MSSQL_PASSWORD", "password")
+    conn_details = _with_conn_details({})
+    assert conn_details["database"] == "database"
+    assert conn_details["server"] == "server"
+    assert conn_details["user"] == "user"
+    assert conn_details["password"] == "password"
+
+
+def test_set_connection_details(monkeypatch):
+    set_connection_details(
+        database="database", server="server", user="user", password="password"
+    )
+    conn_details = _with_conn_details({})
+    assert conn_details["database"] == "database"
+    assert conn_details["server"] == "server"
+    assert conn_details["user"] == "user"
+    assert conn_details["password"] == "password"
+
+
+def test_dict_model_to_values():
+    model = {
+        "text": "hello",
+        "float": 1.23,
+        "datetime": datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
+        "bool": True,
+        "none": None,
+    }
+    expected = (
+        "([text], [float], [datetime], [bool], [none]) VALUES "
+        "(N'hello', 1.23, N'2020-06-01T12:30:00-01:00', 1, NULL)"
+    )
+    assert model_to_values(model) == expected
+
+
+def test_dict_model_to_values_with_prepend():
+    model = {
+        "text": "hello",
+        "float": 1.23,
+    }
+    assert (
+        model_to_values(
+            model, prepend=[("pre1", "@var1"), ("pre2", "SYSDATETIMEOFFSET()")]
+        )
+        == "([pre1], [pre2], [text], [float]) VALUES (@var1, SYSDATETIMEOFFSET(), N'hello', 1.23)"
+    )
+
+
+def test_dict_model_to_values_with_append():
+    model = {
+        "text": "hello",
+        "float": 1.23,
+    }
+    assert (
+        model_to_values(
+            model, append=[("pre1", "@var1"), ("pre2", "SYSDATETIMEOFFSET()")]
+        )
+        == "([text], [float], [pre1], [pre2]) VALUES (N'hello', 1.23, @var1, SYSDATETIMEOFFSET())"
+    )
+
+
+def test_substitute_parameters_tuple():
+    params = (
+        1.23,
+        datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
+        "hello",
+        True,
+        None,
+    )
+    query = "%s, %s, %s, %s, %s"
+    assert (
+        substitute_parameters(query, params)
+        == "1.23, N'2020-06-01T12:30:00-01:00', N'hello', 1, NULL"
+    )
+
+
+def test_substitute_parameters_single():
+    params = (
+        1.23,
+        datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
+        "hello",
+        True,
+    )
+    expected = ("1.23", "N'2020-06-01T12:30:00-01:00'", "N'hello'", "1")
+    query = "%s"
+    for param, expected in zip(params, expected):
+        assert substitute_parameters(query, param) == expected
+
+
+def test_to_sql_list():
+    assert (
+        to_sql_list(
+            (
+                1.23,
+                datetime(2020, 6, 1, 12, 30, tzinfo=timezone(timedelta(hours=-1))),
+                "hello",
+                True,
+                None,
+            )
+        )
+        == "(1.23, N'2020-06-01T12:30:00-01:00', N'hello', 1, NULL)"
+    )