Repository: mom1/api-client-pydantic (language: python)

Issue:
When there are two or more pydantic models in the function parameters, all arguments are passed to each one
Hi,

I noticed that when there are two or more `pydantic` models in a function's signature, `serialize_request` will use all individual arguments to each model. For example, let's say you have a function like this:

```python
    def post_universe(
        self, path_params: CatalogPathParams, body: UniversePostPayload = ...
    ) -> None:
        """Create a new universe resource"""
        self.post(Endpoints.post_universe.format(**path_params), body)
```

`serialize_request` will pass all arguments to each of the models here:

https://github.com/mom1/api-client-pydantic/blob/2f55cd7f6593ea961881ed96969ed3160ccc4e7f/apiclient_pydantic/serializers.py#L36-L37

I guess this is not a problem usually, but I have `extra = Extra.forbid` set in the models' configs and thus get a ValidationError. I would suggest to map the parameters to the specific schemas, similar to how the schemas themselves are mapped. I don't think there needs to be change for the case when the schema is specified in the decorator, since then there will only be one anyways.

For example, this works:

```python
def serialize_request(
    schema: Optional[Type[BaseModel]] = None, extra_kwargs: dict = None
):
    extra_kw = extra_kwargs or {"by_alias": True, "exclude_none": True}

    def decorator(func: Callable) -> Callable:
        nonlocal schema
        map_schemas = {}
        map_params = {}
        parameters = []

        if schema:
            parameters.extend(list(inspect.signature(schema).parameters.values()))
        else:
            for arg_name, arg_type in get_type_hints(func).items():
                if arg_name == "return":
                    continue
                map_schemas[arg_name] = arg_type
                if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
                    arg_params = inspect.signature(arg_type).parameters
                    map_params[arg_name] = list(arg_params.keys())
                    parameters.extend(list(arg_params.values()))

        @wraps(func)
        def wrap(*args, **kwargs):
            if schema:
                instance = data = parse_obj_as(schema, kwargs)
                if isinstance(instance, BaseModel):
                    data = instance.dict(**extra_kw)
                return func(*args, data)
            elif map_schemas:
                data, origin_kwargs = {}, {}
                for arg_name, arg_type in map_schemas.items():
                    if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
                        arg_kwargs = {
                            k: v for k, v in kwargs.items() if k in map_params[arg_name]
                        }
                        data[arg_name] = parse_obj_as(arg_type, arg_kwargs).dict(
                            **extra_kw
                        )
                    else:
                        val = kwargs.get(arg_name)
                        if val is not None:
                            origin_kwargs[arg_name] = val
                new_kwargs = {**origin_kwargs, **data} or kwargs
                return func(*args, **new_kwargs)
            return func(*args, **kwargs)

        # Override signature
        if parameters:
            sig = inspect.signature(func)
            _self_param = sig.parameters.get("self")
            self_param = [_self_param] if _self_param else []
            sig = sig.replace(parameters=tuple(self_param + parameters))
            wrap.__signature__ = sig
        return wrap

    return decorator
```

Happy to make a PR, if you agree.


Discussion / hints:
Hi, this is interesting. PR is always welcome)
@mom1 I had the same issue and @ljnsn modifications work perfectly for me.

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b4c395c..eadbb44 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,7 @@ repos:
           - --py37-plus
           - --keep-runtime-typing
   - repo: https://github.com/hadialqattan/pycln
-    rev: v1.2.0
+    rev: v1.2.4
     hooks:
       - id: pycln
         args: [-a]
diff --git a/Makefile b/Makefile
index ed20351..2a1883a 100644
--- a/Makefile
+++ b/Makefile
@@ -37,9 +37,9 @@ test:
 
 .PHONY: check-codestyle
 check-codestyle:
-	poetry run flake8 --count .
-	poetry run black --diff --check --config pyproject.toml .
-	poetry run isort --diff --check-only --settings-path pyproject.toml .
+	poetry run flake8 --count apiclient_pydantic tests
+	poetry run black --diff --check --config pyproject.toml apiclient_pydantic tests
+	poetry run isort --diff --check-only --settings-path pyproject.toml apiclient_pydantic tests
 
 .PHONY: mypy
 mypy:
diff --git a/README.md b/README.md
index 3a2bc22..e2c56dc 100644
--- a/README.md
+++ b/README.md
@@ -25,16 +25,16 @@ pip install api-client-pydantic
 The following decorators have been provided to validate request data and converting json straight to pydantic class.
 
 ```python
-from apiclient_pydantic import serialize, serialize_all_methods, serialize_request, serialize_response
+from apiclient_pydantic import params_serializer, response_serializer, serialize, serialize_all_methods
 
 # serialize incoming kwargs
-@serialize_request(schema: Optional[Type[BaseModel]] = None, extra_kwargs: dict = None)
+@params_serializer(by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = True)
 
 # serialize response in pydantic class
-@serialize_response(schema: Optional[Type[BaseModel]] = None)
+@response_serializer(response: Optional[Type[BaseModel]] = None)
 
 # serialize request and response data
-@serialize(schema_request: Optional[Type[BaseModel]] = None, schema_response: Optional[Type[BaseModel]] = None, **base_kwargs)
+@serialize(response: Optional[Type[BaseModel]] = None, by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = True)
 
 # wraps all local methods of a class with a specified decorator. default 'serialize'
 @serialize_all_methods(decorator=serialize)
@@ -51,37 +51,38 @@ Usage:
         sort_code: int = Field(alias='sortCode')
         date_opened: datetime = Field(alias='dateOpened')
     ```
-2. Add the `@serialize_response` decorator to the api client method to transform the response
+
+2. Add the `@response_serializer` decorator to the api client method to transform the response
 directly into your defined schema.
    ```python
-   @serialize_response(List[Account])
-   def get_accounts():
-       ...
-   # or
-   @serialize_response()
-   def get_accounts() -> List[Account]:
-       ...
-   ```
-3. Add the `@serialize_request` decorator to the api client method to translate the incoming kwargs
+    @response_serializer(List[Account])
+    def get_accounts():
+        ...
+    # or
+    @response_serializer()
+    def get_accounts() -> List[Account]:
+        ...
+    ```
+3. Add the `@params_serializer` decorator to the api client method to translate the incoming kwargs
 into the required dict for the endpoint:
    ```python
-   @serialize_request(AccountHolder)
-   def create_account(data: dict):
-      ...
-   # or
-   @serialize_request()
-   def create_account(data: AccountHolder):
-    # data will be exactly a dict
-      ...
-   create_account(last_name='Smith', first_name='John')
-   # data will be a dict {"last_name": "Smith", "first_name": "John"}
-   ```
-4. `@serialize` - It is a combination of the two decorators `@serialize_response` and`@serialize_request`.
+    @params_serializer(AccountHolder)
+    def create_account(data: dict):
+        ...
+    # or
+    @params_serializer()
+    def create_account(data: AccountHolder):
+        # data will be exactly a dict
+        ...
+    create_account(last_name='Smith', first_name='John')
+    # data will be a dict {"last_name": "Smith", "first_name": "John"}
+    ```
+4. `@serialize` - It is a combination of the two decorators `@response_serializer` and`@params_serializer`.
 5. For more convenient use, you can wrap all APIClient methods with `@serialize_all_methods`.
    ```python
-   from apiclient import APIClient
-   from apiclient_pydantic import serialize_all_methods
-   from typing import List
+    from apiclient import APIClient
+    from apiclient_pydantic import serialize_all_methods
+    from typing import List
 
     from .models import Account, AccountHolder
 
diff --git a/apiclient_pydantic/__init__.py b/apiclient_pydantic/__init__.py
index cc2f3db..85f32b5 100644
--- a/apiclient_pydantic/__init__.py
+++ b/apiclient_pydantic/__init__.py
@@ -1,6 +1,19 @@
-from .serializers import serialize, serialize_all_methods, serialize_request, serialize_response
+from .serializers import (
+    ParamsSerializer,
+    ResponseSerializer,
+    params_serializer,
+    response_serializer,
+    serialize,
+    serialize_all_methods,
+    serialize_request,
+    serialize_response,
+)
 
 __all__ = (
+    'params_serializer',
+    'ParamsSerializer',
+    'response_serializer',
+    'ResponseSerializer',
     'serialize',
     'serialize_all_methods',
     'serialize_request',
diff --git a/apiclient_pydantic/serializers.py b/apiclient_pydantic/serializers.py
index 1a4d81c..c9d4d88 100644
--- a/apiclient_pydantic/serializers.py
+++ b/apiclient_pydantic/serializers.py
@@ -1,88 +1,199 @@
 import inspect
 from functools import wraps
-from typing import Any, Callable, Dict, Optional, Tuple, Type, Union, get_type_hints
+from typing import Any, Callable, Dict, ForwardRef, Optional, Type, get_type_hints
+
+from pydantic import BaseModel, create_model, parse_obj_as
+from pydantic.config import BaseConfig as PydanticBaseConfig, Extra
+from pydantic.typing import evaluate_forwardref
+
+from .utils import is_pydantic_model
+
+DictStrAny = Dict[str, Any]
+
+
+class BaseConfig(PydanticBaseConfig):
+    orm_mode = True
+
+
+class ParamsObject:
+    def __init__(self, **kwargs: DictStrAny) -> None:
+        for attr, param in kwargs.items():
+            setattr(self, attr, param)
+
+
+def get_typed_signature(call: Callable) -> inspect.Signature:
+    """Finds call signature and resolves all forwardrefs"""
+    signature = inspect.signature(call)
+    globalns = getattr(call, '__globals__', {})
+    typed_params = [
+        inspect.Parameter(
+            name=param.name,
+            kind=param.kind,
+            default=param.default,
+            annotation=get_typed_annotation(param, globalns),
+        )
+        for param in signature.parameters.values()
+    ]
+    typed_signature = inspect.Signature(typed_params)
+    return typed_signature
+
+
+def get_typed_annotation(param: inspect.Parameter, globalns: DictStrAny) -> Any:
+    annotation = param.annotation
+    if isinstance(annotation, str):
+        annotation = make_forwardref(annotation, globalns)
+    return annotation
+
+
+def make_forwardref(annotation: str, globalns: DictStrAny) -> Any:
+    forward_ref = ForwardRef(annotation)
+    return evaluate_forwardref(forward_ref, globalns, globalns)
+
+
+class ParamsSerializer:
+    __slot__ = (
+        'signature',
+        'by_alias',
+        'exclude_unset',
+        'exclude_defaults',
+        'exclude_none',
+        'has_kwargs',
+        'model_param',
+    )
+
+    def __init__(
+        self,
+        by_alias: bool = True,
+        exclude_unset: bool = False,
+        exclude_defaults: bool = False,
+        exclude_none: bool = True,
+    ):
+        self.by_alias = by_alias
+        self.exclude_unset = exclude_unset
+        self.exclude_defaults = exclude_defaults
+        self.exclude_none = exclude_none
+        self.has_kwargs = False
+
+    def __call__(self, func: Callable) -> Callable:
+        attrs, self.signature = {}, get_typed_signature(func)
+        new_signature_parameters: DictStrAny = {}
+
+        for name, arg in self.signature.parameters.items():
+            if name == 'self':
+                new_signature_parameters.setdefault(arg.name, arg)
+                continue
+
+            if arg.kind == arg.VAR_KEYWORD:
+                # Skipping **kwargs
+                self.has_kwargs = True
+                continue
+
+            if arg.kind == arg.VAR_POSITIONAL:
+                # Skipping *args
+                continue
+
+            arg_type = self._get_param_type(arg)
+
+            if name not in new_signature_parameters:
+                if is_pydantic_model(arg_type):
+                    new_signature_parameters.update(
+                        {argument.name: argument for argument in inspect.signature(arg_type).parameters.values()}
+                    )
+                else:
+                    new_signature_parameters.setdefault(arg.name, arg)
+
+            attrs[name] = (arg_type, ...)
+
+        if attrs:
+            self.model_param = create_model(f'{func.__name__}Params', __config__=BaseConfig, **attrs)  # type: ignore
 
-from pydantic import BaseModel, parse_obj_as
+        @wraps(func)
+        def wrap(*args, **kwargs):
+            object_params = {}
+            for name, fld in self.model_param.__fields__.items():
+                kw = kwargs if name not in kwargs else kwargs[name]
 
+                object_params[name] = kw
+                if is_pydantic_model(fld.type_) and fld.type_.Config.extra == Extra.forbid and isinstance(kw, dict):
+                    object_params[name] = {k: v for k, v in kw.items() if k in fld.type_.__fields__}
 
-def serialize_request(schema: Optional[Type[BaseModel]] = None, extra_kwargs: Dict[str, Any] = None) -> Callable:
-    extra_kw = extra_kwargs or {'by_alias': True, 'exclude_none': True}
+            params_object = ParamsObject(**object_params)
 
-    def decorator(func: Callable) -> Callable:
-        nonlocal schema
-        map_schemas = {}
-        parameters = []
-
-        if schema:
-            parameters.extend(list(inspect.signature(schema).parameters.values()))
-        else:
-            for arg_name, arg_type in get_type_hints(func).items():
-                if arg_name == 'return':
-                    continue
-                map_schemas[arg_name] = arg_type
-                if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
-                    parameters.extend(list(inspect.signature(arg_type).parameters.values()))
+            result = self.model_param.from_orm(params_object).dict(
+                by_alias=self.by_alias,
+                exclude_unset=self.exclude_unset,
+                exclude_defaults=self.exclude_defaults,
+                exclude_none=self.exclude_none,
+            )
 
-        @wraps(func)
-        def wrap(*args, **kwargs):
-            if schema:
-                instance = data = parse_obj_as(schema, kwargs)
-                data = instance.dict(**extra_kw)
-                return func(*args, data)
-            elif map_schemas:
-                data, origin_kwargs = {}, {}
-                for arg_name, arg_type in map_schemas.items():
-                    if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
-                        data[arg_name] = parse_obj_as(arg_type, kwargs).dict(**extra_kw)
-                    else:
-                        val = kwargs.get(arg_name)
-                        if val is not None:
-                            origin_kwargs[arg_name] = val
-                new_kwargs = {**origin_kwargs, **data} or kwargs
-                return func(*args, **new_kwargs)
-            return func(*args, **kwargs)
+            return func(*args, **result)
 
         # Override signature
-        if parameters:
+        if new_signature_parameters and attrs:
             sig = inspect.signature(func)
-            _self_param = sig.parameters.get('self')
-            self_param = [_self_param] if _self_param else []
-            sig = sig.replace(parameters=tuple(self_param + parameters))
+            sig = sig.replace(parameters=tuple(sorted(new_signature_parameters.values(), key=lambda x: x.kind)))
             wrap.__signature__ = sig  # type: ignore
-        return wrap
 
-    return decorator
+        return wrap if attrs else func
 
+    def _get_param_type(self, arg: inspect.Parameter) -> Any:
+        annotation = arg.annotation
+
+        if annotation == self.signature.empty:
+            if arg.default == self.signature.empty:
+                annotation = str
+            else:
+                annotation = type(arg.default)
+
+        if annotation is type(None) or annotation is type(Ellipsis):  # noqa: E721
+            annotation = str
+
+        return annotation
 
-def serialize_response(schema: Optional[Type[BaseModel]] = None) -> Callable:
-    def decorator(func: Callable) -> Callable:
-        nonlocal schema
-        if not schema:  # pragma: no cover
-            schema = get_type_hints(func).get('return')
+
+serialize_request = params_serializer = ParamsSerializer
+
+
+class ResponseSerializer:
+    __slot__ = ('response',)
+
+    def __init__(self, response: Optional[Type[BaseModel]] = None):
+        self.response = response
+
+    def __call__(self, func: Callable) -> Callable:
+        self.response = self.response or get_type_hints(func).get('return')
 
         @wraps(func)
-        def wrap(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Union[BaseModel, Any]:
-            response = func(*args, **kwargs)
-            if isinstance(response, (list, dict, tuple, set)) and schema:
-                return parse_obj_as(schema, response)
-            return response
+        def wrap(*args, **kwargs):
+            result = func(*args, **kwargs)
+            if result is not None:
+                return parse_obj_as(self.response, result)
+            return result
 
-        return wrap
+        return wrap if self.response else func
 
-    return decorator
+
+serialize_response = response_serializer = ResponseSerializer
 
 
 def serialize(
-    schema_request: Optional[Type[BaseModel]] = None,
-    schema_response: Optional[Type[BaseModel]] = None,
-    **base_kwargs: Dict[str, Any],
+    response: Optional[Type[BaseModel]] = None,
+    by_alias: bool = True,
+    exclude_unset: bool = False,
+    exclude_defaults: bool = False,
+    exclude_none: bool = True,
 ) -> Callable:
     def decorator(func: Callable) -> Callable:
-        response = func
-        response = serialize_request(schema_request, extra_kwargs=base_kwargs)(func)
-        response = serialize_response(schema_response)(response)
-
-        return response
+        result_func = func
+        result_func = ParamsSerializer(
+            by_alias=by_alias,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+        )(func)
+        result_func = serialize_response(response=response)(result_func)
+
+        return result_func
 
     return decorator
 
diff --git a/apiclient_pydantic/utils.py b/apiclient_pydantic/utils.py
new file mode 100644
index 0000000..ae3252e
--- /dev/null
+++ b/apiclient_pydantic/utils.py
@@ -0,0 +1,10 @@
+from typing import Any
+
+import pydantic
+
+
+def is_pydantic_model(cls: Any) -> bool:
+    try:
+        return issubclass(cls, pydantic.BaseModel)
+    except TypeError:
+        return False
diff --git a/poetry.lock b/poetry.lock
index 14d3e27..e3c54b2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -35,17 +35,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "attrs"
-version = "20.3.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.*"
+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", "zope.interface", "furo", "sphinx", "pre-commit"]
-docs = ["furo", "sphinx", "zope.interface"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+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", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
 
 [[package]]
 name = "backcall"
@@ -55,21 +55,6 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "backports.entry-points-selectable"
-version = "1.1.0"
-description = "Compatibility shim providing selectable entry points for older implementations"
-category = "dev"
-optional = false
-python-versions = ">=2.7"
-
-[package.dependencies]
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
-
 [[package]]
 name = "black"
 version = "22.1.0"
@@ -95,7 +80,7 @@ uvloop = ["uvloop (>=0.15.2)"]
 
 [[package]]
 name = "certifi"
-version = "2020.12.5"
+version = "2021.10.8"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "main"
 optional = false
@@ -110,12 +95,15 @@ optional = false
 python-versions = ">=3.6.1"
 
 [[package]]
-name = "chardet"
-version = "4.0.0"
-description = "Universal encoding detector for Python 2 and 3"
+name = "charset-normalizer"
+version = "2.0.12"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
@@ -153,15 +141,15 @@ toml = ["tomli"]
 
 [[package]]
 name = "decorator"
-version = "4.4.2"
+version = "5.1.1"
 description = "Decorators for Humans"
 category = "dev"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+python-versions = ">=3.5"
 
 [[package]]
 name = "distlib"
-version = "0.3.3"
+version = "0.3.4"
 description = "Distribution utilities"
 category = "dev"
 optional = false
@@ -193,11 +181,11 @@ python-versions = "*"
 
 [[package]]
 name = "filelock"
-version = "3.3.1"
+version = "3.6.0"
 description = "A platform independent file lock."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.extras]
 docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
@@ -337,26 +325,26 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "identify"
-version = "2.3.1"
+version = "2.4.11"
 description = "File identification library for Python"
 category = "dev"
 optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.7"
 
 [package.extras]
-license = ["editdistance-s"]
+license = ["ukkonen"]
 
 [[package]]
 name = "idna"
-version = "2.10"
+version = "3.3"
 description = "Internationalized Domain Names in Applications (IDNA)"
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.5"
 
 [[package]]
 name = "importlib-metadata"
-version = "3.4.0"
+version = "4.2.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
@@ -368,7 +356,7 @@ zipp = ">=0.5"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+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"
@@ -393,7 +381,7 @@ toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""}
 
 [[package]]
 name = "ipython"
-version = "7.31.1"
+version = "7.32.0"
 description = "IPython: Productive Interactive Computing"
 category = "dev"
 optional = false
@@ -423,14 +411,6 @@ parallel = ["ipyparallel"]
 qtconsole = ["qtconsole"]
 test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"]
 
-[[package]]
-name = "ipython-genutils"
-version = "0.2.0"
-description = "Vestigial utilities from IPython"
-category = "dev"
-optional = false
-python-versions = "*"
-
 [[package]]
 name = "isort"
 version = "5.10.1"
@@ -447,7 +427,7 @@ plugins = ["setuptools"]
 
 [[package]]
 name = "jedi"
-version = "0.18.0"
+version = "0.18.1"
 description = "An autocompletion tool for Python that can be used for text editors."
 category = "dev"
 optional = false
@@ -458,7 +438,7 @@ parso = ">=0.8.0,<0.9.0"
 
 [package.extras]
 qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
-testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
 
 [[package]]
 name = "matplotlib-inline"
@@ -479,14 +459,6 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "multidict"
-version = "5.1.0"
-description = "multidict implementation"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
 [[package]]
 name = "mypy"
 version = "0.931"
@@ -523,18 +495,18 @@ python-versions = "*"
 
 [[package]]
 name = "packaging"
-version = "20.8"
+version = "21.3"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
 
 [package.dependencies]
-pyparsing = ">=2.0.2"
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
 [[package]]
 name = "parso"
-version = "0.8.1"
+version = "0.8.3"
 description = "A Python Parser"
 category = "dev"
 optional = false
@@ -585,11 +557,11 @@ python-versions = "*"
 
 [[package]]
 name = "platformdirs"
-version = "2.4.0"
+version = "2.5.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)"]
@@ -597,17 +569,18 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
 
 [[package]]
 name = "pluggy"
-version = "0.13.1"
+version = "1.0.0"
 description = "plugin and hook calling mechanisms for python"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
 
 [package.dependencies]
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 
 [package.extras]
 dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "pre-commit"
@@ -628,11 +601,11 @@ virtualenv = ">=20.0.8"
 
 [[package]]
 name = "prompt-toolkit"
-version = "3.0.10"
+version = "3.0.28"
 description = "Library for building powerful interactive command lines in Python"
 category = "dev"
 optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.6.2"
 
 [package.dependencies]
 wcwidth = "*"
@@ -647,11 +620,11 @@ python-versions = "*"
 
 [[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"
@@ -686,7 +659,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pygments"
-version = "2.7.4"
+version = "2.11.2"
 description = "Pygments is a syntax highlighting package written in Python."
 category = "dev"
 optional = false
@@ -694,11 +667,14 @@ python-versions = ">=3.5"
 
 [[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 = "pytest"
@@ -737,58 +713,31 @@ pytest = ">=4.6"
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
 
-[[package]]
-name = "pytest-env"
-version = "0.6.2"
-description = "py.test plugin that allows you to add environment variables."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-pytest = ">=2.6.0"
-
 [[package]]
 name = "pyyaml"
-version = "5.4.1"
+version = "6.0"
 description = "YAML parser and emitter for Python"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "requests"
-version = "2.25.1"
+version = "2.27.1"
 description = "Python HTTP for Humans."
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 
 [package.dependencies]
 certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<5"
-idna = ">=2.5,<3"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
 urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
-security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
 socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
-
-[[package]]
-name = "requests-mock"
-version = "1.9.3"
-description = "Mock out responses from the requests package"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-requests = ">=2.3,<3"
-six = "*"
-
-[package.extras]
-fixture = ["fixtures"]
-test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 
 [[package]]
 name = "safety"
@@ -806,22 +755,19 @@ requests = "*"
 
 [[package]]
 name = "six"
-version = "1.15.0"
+version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tenacity"
-version = "6.3.1"
+version = "8.0.1"
 description = "Retry code until it succeeds"
 category = "main"
 optional = false
-python-versions = "*"
-
-[package.dependencies]
-six = ">=1.9.0"
+python-versions = ">=3.6"
 
 [package.extras]
 doc = ["reno", "sphinx", "tornado (>=4.5)"]
@@ -836,23 +782,20 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tomli"
-version = "1.2.1"
+version = "2.0.1"
 description = "A lil' TOML parser"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "traitlets"
-version = "5.0.5"
+version = "5.1.1"
 description = "Traitlets Python configuration system"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
-[package.dependencies]
-ipython-genutils = "*"
-
 [package.extras]
 test = ["pytest"]
 
@@ -901,30 +844,15 @@ brotli = ["brotlipy (>=0.6.0)"]
 secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
-[[package]]
-name = "vcrpy"
-version = "4.1.1"
-description = "Automatically mock your HTTP interactions to simplify and speed up testing"
-category = "dev"
-optional = false
-python-versions = ">=3.5"
-
-[package.dependencies]
-PyYAML = "*"
-six = ">=1.5"
-wrapt = "*"
-yarl = {version = "*", markers = "python_version >= \"3.6\""}
-
 [[package]]
 name = "virtualenv"
-version = "20.9.0"
+version = "20.13.3"
 description = "Virtual Python Environment builder"
 category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [package.dependencies]
-"backports.entry-points-selectable" = ">=1.0.4"
 distlib = ">=0.3.1,<1"
 filelock = ">=3.2,<4"
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
@@ -932,7 +860,7 @@ platformdirs = ">=2,<3"
 six = ">=1.9.0,<2"
 
 [package.extras]
-docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
 testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
 
 [[package]]
@@ -943,43 +871,22 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "wrapt"
-version = "1.12.1"
-description = "Module for decorators, wrappers and monkey patching."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "yarl"
-version = "1.6.3"
-description = "Yet another URL library"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-idna = ">=2.0"
-multidict = ">=4.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-
 [[package]]
 name = "zipp"
-version = "3.4.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 (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+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"]
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "3eb5334bd4984668ba2e2ea7d5a527eb8c31464676cdda094cf1ec363a2130c6"
+content-hash = "97f910574acf0be6ee523c9a51b10720e3400fd6599a4331c1632603598afbf6"
 
 [metadata.files]
 api-client = [
@@ -994,17 +901,13 @@ atomicwrites = [
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
-    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
+    {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
+    {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
 backcall = [
     {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
     {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
 ]
-"backports.entry-points-selectable" = [
-    {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"},
-    {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
-]
 black = [
     {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
     {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
@@ -1031,16 +934,16 @@ black = [
     {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
 ]
 certifi = [
-    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
-    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
+    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
 ]
 cfgv = [
     {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
     {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
 ]
-chardet = [
-    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
-    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
+charset-normalizer = [
+    {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+    {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
 ]
 click = [
     {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
@@ -1094,12 +997,12 @@ coverage = [
     {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"},
 ]
 decorator = [
-    {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
-    {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
+    {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+    {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
 ]
 distlib = [
-    {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"},
-    {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
+    {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
+    {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
 ]
 dparse = [
     {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"},
@@ -1109,8 +1012,8 @@ eradicate = [
     {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"},
 ]
 filelock = [
-    {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
-    {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
+    {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
+    {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"},
 ]
 flake8 = [
     {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
@@ -1156,16 +1059,16 @@ flake8-tidy-imports = [
     {file = "flake8_tidy_imports-4.6.0-py3-none-any.whl", hash = "sha256:6ae9f55d628156e19d19f4c359dd5d3e95431a9bd514f5e2748c53c1398c66b2"},
 ]
 identify = [
-    {file = "identify-2.3.1-py2.py3-none-any.whl", hash = "sha256:5a5000bd3293950d992843c0ef3d82b90a582de2161557bda7f493c8c8864f26"},
-    {file = "identify-2.3.1.tar.gz", hash = "sha256:8a92c56893e9a4ce951f09a50489986615e3eba7b4c60610e0b25f93ca4487ba"},
+    {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"},
+    {file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"},
 ]
 idna = [
-    {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
-    {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
+    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
-    {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
+    {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"},
@@ -1175,20 +1078,16 @@ ipdb = [
     {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"},
 ]
 ipython = [
-    {file = "ipython-7.31.1-py3-none-any.whl", hash = "sha256:55df3e0bd0f94e715abd968bedd89d4e8a7bce4bf498fb123fed4f5398fea874"},
-    {file = "ipython-7.31.1.tar.gz", hash = "sha256:b5548ec5329a4bcf054a5deed5099b0f9622eb9ea51aaa7104d215fece201d8c"},
-]
-ipython-genutils = [
-    {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
-    {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
+    {file = "ipython-7.32.0-py3-none-any.whl", hash = "sha256:86df2cf291c6c70b5be6a7b608650420e89180c8ec74f376a34e2dc15c3400e7"},
+    {file = "ipython-7.32.0.tar.gz", hash = "sha256:468abefc45c15419e3c8e8c0a6a5c115b2127bafa34d7c641b1d443658793909"},
 ]
 isort = [
     {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
     {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
 ]
 jedi = [
-    {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"},
-    {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"},
+    {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
+    {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
 ]
 matplotlib-inline = [
     {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
@@ -1198,45 +1097,6 @@ mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
-multidict = [
-    {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"},
-    {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"},
-    {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"},
-    {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"},
-    {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"},
-    {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"},
-    {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"},
-    {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"},
-    {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"},
-    {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"},
-    {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"},
-    {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"},
-    {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"},
-]
 mypy = [
     {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"},
     {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"},
@@ -1268,12 +1128,12 @@ nodeenv = [
     {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
 ]
 packaging = [
-    {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
-    {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
+    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
 parso = [
-    {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"},
-    {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"},
+    {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
+    {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
 ]
 pathspec = [
     {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
@@ -1292,28 +1152,28 @@ pickleshare = [
     {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
-    {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
+    {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
+    {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
 ]
 pluggy = [
-    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
-    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+    {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+    {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
 ]
 pre-commit = [
     {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
     {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
 ]
 prompt-toolkit = [
-    {file = "prompt_toolkit-3.0.10-py3-none-any.whl", hash = "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7"},
-    {file = "prompt_toolkit-3.0.10.tar.gz", hash = "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5"},
+    {file = "prompt_toolkit-3.0.28-py3-none-any.whl", hash = "sha256:30129d870dcb0b3b6a53efdc9d0a83ea96162ffd28ffe077e94215b233dc670c"},
+    {file = "prompt_toolkit-3.0.28.tar.gz", hash = "sha256:9f1cd16b1e86c2968f2519d7fb31dd9d669916f515612c269d14e9ed52b51650"},
 ]
 ptyprocess = [
     {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
     {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
 ]
 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.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
@@ -1361,12 +1221,12 @@ pyflakes = [
     {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
 ]
 pygments = [
-    {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"},
-    {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"},
+    {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
+    {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
 ]
 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"},
 ]
 pytest = [
     {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"},
@@ -1376,71 +1236,68 @@ pytest-cov = [
     {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
     {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
 ]
-pytest-env = [
-    {file = "pytest-env-0.6.2.tar.gz", hash = "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2"},
-]
 pyyaml = [
-    {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
-    {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
-    {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
-    {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
-    {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
-    {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
-    {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
-    {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
-    {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
-    {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
-    {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
+    {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+    {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+    {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+    {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+    {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+    {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+    {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+    {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+    {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+    {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+    {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+    {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+    {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+    {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+    {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+    {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
 ]
 requests = [
-    {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
-    {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
-]
-requests-mock = [
-    {file = "requests-mock-1.9.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"},
-    {file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"},
+    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+    {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
 ]
 safety = [
     {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"},
     {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"},
 ]
 six = [
-    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
-    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 tenacity = [
-    {file = "tenacity-6.3.1-py2.py3-none-any.whl", hash = "sha256:baed357d9f35ec64264d8a4bbf004c35058fad8795c5b0d8a7dc77ecdcbb8f39"},
-    {file = "tenacity-6.3.1.tar.gz", hash = "sha256:e14d191fb0a309b563904bbc336582efe2037de437e543b38da749769b544d7f"},
+    {file = "tenacity-8.0.1-py3-none-any.whl", hash = "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a"},
+    {file = "tenacity-8.0.1.tar.gz", hash = "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f"},
 ]
 toml = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {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-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
 ]
 traitlets = [
-    {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"},
-    {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"},
+    {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"},
+    {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"},
 ]
 typed-ast = [
     {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
@@ -1484,61 +1341,15 @@ urllib3 = [
     {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
     {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
 ]
-vcrpy = [
-    {file = "vcrpy-4.1.1-py2.py3-none-any.whl", hash = "sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162"},
-    {file = "vcrpy-4.1.1.tar.gz", hash = "sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599"},
-]
 virtualenv = [
-    {file = "virtualenv-20.9.0-py2.py3-none-any.whl", hash = "sha256:1d145deec2da86b29026be49c775cc5a9aab434f85f7efef98307fb3965165de"},
-    {file = "virtualenv-20.9.0.tar.gz", hash = "sha256:bb55ace18de14593947354e5e6cd1be75fb32c3329651da62e92bf5d0aab7213"},
+    {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"},
+    {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"},
 ]
 wcwidth = [
     {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
     {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
 ]
-wrapt = [
-    {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"},
-]
-yarl = [
-    {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"},
-    {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"},
-    {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"},
-    {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"},
-    {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"},
-    {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"},
-    {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"},
-    {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"},
-    {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"},
-    {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"},
-    {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"},
-    {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"},
-    {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
-]
 zipp = [
-    {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
-    {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
+    {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/pyproject.toml b/pyproject.toml
index a15852d..038d8db 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,9 +35,6 @@ pydantic = "^1.7,<2.0"
 coverage = "^6.3"
 pytest = "^7.0"
 pytest-cov = "^3.0"
-pytest-env = "^0.6"
-requests-mock = "^1.9"
-vcrpy = "^4.1"
 # formating
 black = "^22.1"
 isort = "^5.10"


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
deleted file mode 100644
index 846aa41..0000000
--- a/tests/conftest.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import os
-
-import pytest
-import vcr
-
-BASE_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
-VCR_CASSETTE_DIR = os.path.join(BASE_DIR, 'vcr_cassettes')
-
-api_client_vcr = vcr.VCR(
-    serializer='yaml',
-    cassette_library_dir=VCR_CASSETTE_DIR,
-    record_mode='once',
-    match_on=['uri', 'method', 'query'],
-)
-
-error_cassette_vcr = vcr.VCR(
-    serializer='yaml',
-    cassette_library_dir=VCR_CASSETTE_DIR,
-    record_mode='once',
-    match_on=['uri'],
-)
-
-
-@pytest.fixture()
-def cassette():
-    with api_client_vcr.use_cassette('cassette.yaml') as cassette:
-        yield cassette
-
-
-@pytest.fixture()
-def error_cassette():
-    with error_cassette_vcr.use_cassette('error_cassette.yaml') as cassette:
-        yield cassette
diff --git a/tests/integration_tests/__init__.py b/tests/integration_tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/integration_tests/client.py b/tests/integration_tests/client.py
deleted file mode 100644
index ca98373..0000000
--- a/tests/integration_tests/client.py
+++ /dev/null
@@ -1,130 +0,0 @@
-from typing import List, Optional
-
-from apiclient import APIClient, endpoint, paginated, retry_request
-from pydantic import BaseModel, Field
-
-from apiclient_pydantic import serialize_all_methods
-
-
-def by_query_params_callable(response, prev_params):
-    if 'nextPage' in response and response['nextPage']:
-        return {'page': response['nextPage']}
-
-
-@endpoint(base_url='http://testserver')
-class Urls:
-    users = 'users'
-    user = 'users/{id}'
-    accounts = 'accounts'
-
-
-class Client(APIClient):
-    def get_request_timeout(self):
-        return 0.1
-
-    def list_users(self):
-        return self.get(Urls.users)
-
-    @retry_request
-    def get_user(self, user_id: int):
-        url = Urls.user.format(id=user_id)
-        return self.get(url)
-
-    def create_user(self, first_name, last_name):
-        data = {'firstName': first_name, 'lastName': last_name}
-        return self.post(Urls.users, data=data)
-
-    def overwrite_user(self, user_id, first_name, last_name):
-        data = {'firstName': first_name, 'lastName': last_name}
-        url = Urls.user.format(id=user_id)
-        return self.put(url, data=data)
-
-    def update_user(self, user_id, first_name=None, last_name=None):
-        data = {}
-        if first_name:
-            data['firstName'] = first_name
-        if last_name:
-            data['lastName'] = last_name
-        url = Urls.user.format(id=user_id)
-        return self.patch(url, data=data)
-
-    def delete_user(self, user_id):
-        url = Urls.user.format(id=user_id)
-        return self.delete(url)
-
-    @paginated(by_query_params=by_query_params_callable)
-    def list_user_accounts_paginated(self, user_id):
-        return self.get(Urls.accounts, params={'userId': user_id})
-
-
-class UserID(BaseModel):
-    user_id: int = Field(alias='userId')
-
-    class Config:
-        allow_population_by_field_name = True
-
-
-class UserInfo(BaseModel):
-    first_name: str = Field(alias='firstName')
-    last_name: str = Field(alias='lastName')
-
-    class Config:
-        allow_population_by_field_name = True
-
-
-class User(UserID, UserInfo):
-    class Config:
-        allow_population_by_field_name = True
-
-
-class UpdateUser(UserID):
-    first_name: Optional[str] = Field(alias='firstName')
-    last_name: Optional[str] = Field(alias='lastName')
-
-    class Config:
-        allow_population_by_field_name = True
-
-
-class Account(BaseModel):
-    account_name: str = Field(alias='accountName')
-    number: str = Field(alias='number')
-
-    class Config:
-        allow_population_by_field_name = True
-
-
-class AccountPage(BaseModel):
-    results: List[Account]
-    page: int
-    next_page: Optional[int] = Field(alias='nextPage')
-
-    class Config:
-        allow_population_by_field_name = True
-
-
-@serialize_all_methods()
-class ClientWithPydantic(Client):
-    def list_users(self) -> List[User]:
-        return super().list_users()
-
-    @retry_request
-    def get_user(self, data: UserID) -> User:
-        url = Urls.user.format(id=data['userId'])
-        return self.get(url)
-
-    def create_user(self, data: UserInfo) -> User:
-        return self.post(Urls.users, data=data)
-
-    def overwrite_user(self, data: User) -> User:
-        url = Urls.user.format(id=data['userId'])
-        return self.put(url, data=data)
-
-    def update_user(self, data: UpdateUser) -> User:
-        return self.patch(Urls.user.format(id=data['userId']), data=data)
-
-    def delete_user(self, user_id: int) -> dict:
-        return super().delete_user(user_id)
-
-    @paginated(by_query_params=by_query_params_callable)
-    def list_user_accounts_paginated(self, data: UserID) -> List[AccountPage]:
-        return self.get(Urls.accounts, params=data)
diff --git a/tests/integration_tests/test_client_integration.py b/tests/integration_tests/test_client_integration.py
deleted file mode 100644
index ac39c4d..0000000
--- a/tests/integration_tests/test_client_integration.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# Integration tests using all request methods on a
-# real world api client with all methods implemented
-
-import pytest
-from apiclient import JsonRequestFormatter, JsonResponseHandler, NoAuthentication
-from apiclient.exceptions import UnexpectedError
-
-from .client import Client, ClientWithPydantic, User
-
-
-def test_client_response(cassette):
-    client = Client(
-        authentication_method=NoAuthentication(),
-        response_handler=JsonResponseHandler,
-        request_formatter=JsonRequestFormatter,
-    )
-    users = client.list_users()
-    assert len(users) == 3
-    assert users == [
-        {
-            'userId': 1,
-            'firstName': 'Mike',
-            'lastName': 'Foo',
-        },
-        {
-            'userId': 2,
-            'firstName': 'Sarah',
-            'lastName': 'Bar',
-        },
-        {
-            'userId': 3,
-            'firstName': 'Barry',
-            'lastName': 'Baz',
-        },
-    ]
-    assert cassette.play_count == 1
-
-    # User 1 requested successfully on first attempt
-    user = client.get_user(user_id=1)
-    assert user == {'userId': 1, 'firstName': 'Mike', 'lastName': 'Foo'}
-    assert cassette.play_count == 2
-
-    # User 2 failed on first attempt, succeeded on second
-    user = client.get_user(user_id=2)
-    assert user == {'userId': 2, 'firstName': 'Sarah', 'lastName': 'Bar'}
-    assert cassette.play_count == 4
-
-    new_user = client.create_user(first_name='Lucy', last_name='Qux')
-    assert new_user == {'userId': 4, 'firstName': 'Lucy', 'lastName': 'Qux'}
-    assert cassette.play_count == 5
-
-    overwritten_user = client.overwrite_user(user_id=4, first_name='Lucy', last_name='Foo')
-    assert overwritten_user == {'userId': 4, 'firstName': 'Lucy', 'lastName': 'Foo'}
-    assert cassette.play_count == 6
-
-    updated_user = client.update_user(user_id=4, first_name='Lucy', last_name='Qux')
-    assert updated_user == {'userId': 4, 'firstName': 'Lucy', 'lastName': 'Qux'}
-    assert cassette.play_count == 7
-
-    pages = list(client.list_user_accounts_paginated(user_id=1))
-    assert len(pages) == 3
-    assert pages == [
-        {
-            'results': [
-                {
-                    'accountName': 'business',
-                    'number': '1234',
-                },
-                {
-                    'accountName': 'expense',
-                    'number': '2345',
-                },
-            ],
-            'page': 1,
-            'nextPage': 2,
-        },
-        {
-            'results': [
-                {
-                    'accountName': 'fun',
-                    'number': '6544',
-                },
-                {
-                    'accountName': 'holiday',
-                    'number': '9283',
-                },
-            ],
-            'page': 2,
-            'nextPage': 3,
-        },
-        {
-            'results': [
-                {
-                    'accountName': 'gifts',
-                    'number': '7827',
-                },
-                {
-                    'accountName': 'home',
-                    'number': '1259',
-                },
-            ],
-            'page': 3,
-            'nextPage': None,
-        },
-    ]
-
-    # Fails to connect when connecting to non-existent url.
-    with pytest.raises(UnexpectedError) as exc_info:
-        client.get('mock://testserver')
-    assert str(exc_info.value) == "Error when contacting 'mock://testserver'"
-
-
-def test_client_response_with_pydantic(cassette):
-
-    client = ClientWithPydantic(
-        authentication_method=NoAuthentication(),
-        response_handler=JsonResponseHandler,
-        request_formatter=JsonRequestFormatter,
-    )
-    users = client.list_users()
-    assert len(users) == 3
-    assert users == [
-        User(user_id=1, first_name='Mike', last_name='Foo'),
-        User(user_id=2, first_name='Sarah', last_name='Bar'),
-        User(user_id=3, first_name='Barry', last_name='Baz'),
-    ]
-    assert cassette.play_count == 1
-
-    # User 1 requested successfully on first attempt
-    user = client.get_user(user_id=1)
-    assert user == User(user_id=1, first_name='Mike', last_name='Foo')
-    assert cassette.play_count == 2
-
-    # User 2 failed on first attempt, succeeded on second
-    user = client.get_user(user_id=2)
-    assert user == User(user_id=2, first_name='Sarah', last_name='Bar')
-
-    assert cassette.play_count == 4
-
-    new_user = client.create_user(first_name='Lucy', last_name='Qux')
-    assert new_user == User(user_id=4, first_name='Lucy', last_name='Qux')
-
-    assert cassette.play_count == 5
-
-    overwritten_user = client.overwrite_user(user_id=4, first_name='Lucy', last_name='Foo')
-    assert overwritten_user == User(user_id=4, first_name='Lucy', last_name='Foo')
-    assert cassette.play_count == 6
-
-    updated_user = client.update_user(user_id=4, first_name='Lucy', last_name='Qux')
-    assert updated_user == User(user_id=4, first_name='Lucy', last_name='Qux')
-    assert cassette.play_count == 7
-
-    # Fails to connect when connecting to non-existent url.
-    with pytest.raises(UnexpectedError) as exc_info:
-        client.get('mock://testserver')
-    assert str(exc_info.value) == "Error when contacting 'mock://testserver'"
diff --git a/tests/test_seializers.py b/tests/test_seializers.py
index 27097a4..a5e6bc2 100644
--- a/tests/test_seializers.py
+++ b/tests/test_seializers.py
@@ -1,134 +1,208 @@
-from datetime import date, datetime
-from enum import Enum
-from typing import List, Optional
+from typing import List, Optional, Union
 
-import pytest
 from apiclient import APIClient
-from pydantic import BaseModel, Field
+from pydantic import BaseModel
+from pydantic.config import Extra
+from pydantic.fields import Field
 
-from apiclient_pydantic import serialize, serialize_all_methods
+from apiclient_pydantic import params_serializer, serialize_all_methods
 
 
-class Address(BaseModel):
-    house_number: str = Field(alias='houseNumber')
-    post_code: str = Field(alias='postCode')
-    street: Optional[str]
+class SimpleModel(BaseModel):
+    test_attr: str = 'Param'
 
-    class Config:
-        allow_population_by_field_name = True
+
+class SimpleTestModel(BaseModel):
+    test: str
+
+
+class ForwardrefModel(BaseModel):
+    test_attr: str = 'Param'
+    this: Optional['ForwardrefModel'] = None
 
 
-class AccountHolder(BaseModel):
-    first_name: str = Field(alias='firstName')
-    last_name: str = Field(alias='lastName')
-    middle_names: Optional[List[str]] = Field(alias='middleNames')
-    address: Address
-    date_of_birth: date = Field(alias='dob')
+class ForbidModel(BaseModel):
+    test_attr: str = 'Param'
 
     class Config:
-        allow_population_by_field_name = True
+        extra = Extra.forbid
 
 
-class AccountType(Enum):
-    SAVING = 'SAVING'
-    CURRENT = 'CURRENT'
-    ISA = 'ISA'
+@serialize_all_methods()
+class Client(APIClient):
+    def function_without_all(self):
+        return ['test']
 
+    def function_simple_arg(self, pk: int):
+        return pk
 
-class Account(BaseModel):
-    account_number: int = Field(alias='accountNumber')
-    sort_code: int = Field(alias='sortCode')
-    account_type: AccountType = Field(alias='accountType')
-    account_holder: AccountHolder = Field(alias='accountHolder')
-    date_opened: datetime = Field(alias='dateOpened')
+    def function_simple_response(self) -> int:
+        return '1'  # type: ignore
 
-    class Config:
-        allow_population_by_field_name = True
-        use_enum_values = True
-
-
-@pytest.fixture()
-def unserialized():
-    return {
-        'accountHolder': {
-            'address': {
-                'houseNumber': '12B',
-                'postCode': 'SW11 1AP',
-            },
-            'dob': '1980-02-28',
-            'firstName': 'John',
-            'lastName': 'Smith',
-        },
-        'account_number': 12345678,
-        'accountType': 'SAVING',
-        'sortCode': 989898,
-        'dateOpened': '2020-11-03T12:32:12',
-    }
+    def function_simple_response_and_arg(self, pk: int) -> bool:
+        assert isinstance(pk, int)
+        return pk  # type: ignore
 
+    def function_simple_auto_type_str(self, pk):
+        return pk
 
-@pytest.fixture()
-def serialized(unserialized):
-    return Account(**unserialized)
+    def function_simple_args(self, *args):
+        return args
 
+    def function_simple_kwargs(self, **kwargs):
+        return kwargs
 
-def test_serialize_request(unserialized, serialized):
-    @serialize()
-    def decorated_func(endpoint: str, custom: str, data: Account):
-        return data
+    def function_simple_model(self, param: SimpleModel):
+        return param
 
-    @serialize()
-    def decorated_func_args(endpoint: str, custom: str, data: Account):
-        return custom
+    def function_simple_model_args(self, param: SimpleModel, test_attr: str):
+        return test_attr, param
 
-    @serialize()
-    def decorated_func_kwargs(endpoint: str, data: Account, custom: str = 'tests'):
-        return custom
+    def function_forbid_model(self, param: ForbidModel):
+        return param
 
-    @serialize(Account)
-    def decorated_func_schema(endpoint: str, data):
+    def function_type_from_default(self, pk=1):
+        return pk
+
+    def function_special_type(self, pk: None.__class__, name: ....__class__):  # type: ignore
+        return pk, name
+
+    def function_return_none(self, pk=1) -> None:
+        pass
+
+    def function_forwardref(self, data: 'ForwardrefModel'):
         return data
 
-    @serialize()
-    def decorated_func_no_schema(endpoint: str, **kwargs):
-        return kwargs  # data as is
+    def function_union(self, data: Union[SimpleTestModel, SimpleModel]):
+        return data
+
+    def function_list_response(self, data: SimpleModel) -> List[SimpleModel]:
+        return [data]
+
+
+def test_function_without_all():
+    client = Client()
+
+    # not wrapped
+    assert '__wrapped__' not in vars(client.function_without_all)
+
+    assert client.function_without_all() == ['test']
+
+
+def test_function_simple_arg():
+    client = Client()
+
+    # wrapped once
+    assert '__wrapped__' in vars(client.function_simple_arg)
+    assert '__wrapped__' not in vars(vars(client.function_simple_arg)['__wrapped__'])
+
+    assert client.function_simple_arg(pk='1') == 1  # type: ignore
+
+
+def test_function_simple_response():
+    client = Client()
+
+    # wrapped once
+    assert '__wrapped__' in vars(client.function_simple_response)
+    assert '__wrapped__' not in vars(vars(client.function_simple_response)['__wrapped__'])
+
+    assert client.function_simple_response() == 1  # type: ignore
+
+
+def test_function_simple_response_and_arg():
+    client = Client()
+
+    # wrapped
+    assert '__wrapped__' in vars(client.function_simple_response_and_arg)
+    assert '__wrapped__' in vars(vars(client.function_simple_response_and_arg)['__wrapped__'])
+    assert '__wrapped__' not in vars(vars(vars(client.function_simple_response_and_arg)['__wrapped__'])['__wrapped__'])
 
-    got = decorated_func('', 'test', **unserialized)
-    assert got == serialized.dict(by_alias=True, exclude_none=True)
+    assert client.function_simple_response_and_arg(pk='0') is False  # type: ignore
 
-    text = decorated_func_args('', custom='test', **unserialized)
-    assert text == 'test'
 
-    text = decorated_func_kwargs('', custom='test', **unserialized)
-    assert text == 'test'
+def test_function_simple_auto_type_str():
+    client = Client()
 
-    got = decorated_func_schema('', **unserialized)
-    assert got == serialized.dict(by_alias=True, exclude_none=True)
+    assert client.function_simple_auto_type_str(pk=0) == '0'
 
-    got = decorated_func_no_schema('', **unserialized)
-    assert got == unserialized
 
+def test_function_simple_args():
+    client = Client()
 
-def test_serialize_response(unserialized, serialized):
-    @serialize()
-    def decorated_func(endpoint: str) -> Account:
-        return unserialized
+    assert client.function_simple_args('test') == ('test',)
 
-    got = decorated_func('')
-    assert got == serialized
 
+def test_function_simple_kwargs():
+    client = Client()
 
-def test_serialize_all_methods(unserialized, serialized):
-    @serialize_all_methods()
-    class MyApiClient(APIClient):
-        def decorated_func(self, data: Account) -> Account:
-            return data
+    assert client.function_simple_kwargs(test='test') == {'test': 'test'}
 
-        def decorated_func_holder(self, data: AccountHolder) -> AccountHolder:
-            return data
 
-    client = MyApiClient()
-    got = client.decorated_func(**unserialized)
-    assert got == serialized
+def test_function_simple_model():
+    client = Client()
+
+    assert client.function_simple_model() == {'test_attr': 'Param'}  # type: ignore
+    assert client.function_simple_model(test_attr='test') == {'test_attr': 'test'}  # type: ignore
+
+
+def test_function_simple_model_args():
+    client = Client()
+
+    assert client.function_simple_model_args(test_attr='test') == ('test', {'test_attr': 'test'})  # type: ignore
+
+
+def test_function_forbid_model():
+    client = Client()
+
+    assert client.function_forbid_model(test_attr='test') == {'test_attr': 'test'}  # type: ignore
+    assert client.function_forbid_model(test_attr='test', test='bla') == {'test_attr': 'test'}  # type: ignore
+
+
+def test_function_type_from_default():
+    client = Client()
+    assert client.function_type_from_default(pk='2') == 2  # type: ignore
+
+
+def test_function_special_type():
+    client = Client()
+    assert client.function_special_type(pk=2, name=True) == ('2', 'True')  # type: ignore
+
+
+def test_function_return_none():
+    client = Client()
+    assert client.function_return_none(pk=2) is None  # type: ignore
+
+
+def test_function_forwardref():
+    client = Client()
+    assert client.function_forwardref(test_attr='123', this={'test_attr': 456}) == {  # type: ignore
+        'test_attr': '123',
+        'this': {'test_attr': '456'},
+    }
+
+
+def test_function_union():
+    client = Client()
+    assert client.function_union(test='bla') == {'test': 'bla'}  # type: ignore
+    assert client.function_union(test_attr='bla') == {'test_attr': 'bla'}  # type: ignore
+
+
+def test_function_list_response():
+    client = Client()
+    assert client.function_list_response(test_attr='bla') == [{'test_attr': 'bla'}]  # type: ignore
+
+
+def test_param_for_model():
+    class MyModel(BaseModel):
+        test: str = Field(alias='TesT')
+
+    @params_serializer()
+    def function_by_alias(data: MyModel):
+        return data
+
+    @params_serializer(by_alias=False)
+    def function(data: MyModel):
+        return data
 
-    got = client.decorated_func_holder(**unserialized.get('accountHolder'))
-    assert got == serialized.account_holder
+    assert function_by_alias(TesT='bla') == {'TesT': 'bla'}
+    assert function(TesT='bla') == {'test': 'bla'}
diff --git a/tests/vcr_cassettes/cassette.yaml b/tests/vcr_cassettes/cassette.yaml
deleted file mode 100644
index 94c1d0f..0000000
--- a/tests/vcr_cassettes/cassette.yaml
+++ /dev/null
@@ -1,201 +0,0 @@
----
-interactions:
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users
-    response:
-      body:
-        string: '[
-          {"userId": 1, "firstName": "Mike", "lastName": "Foo"},
-          {"userId": 2, "firstName": "Sarah", "lastName": "Bar"},
-          {"userId": 3, "firstName": "Barry", "lastName": "Baz"}
-        ]'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/1
-    response:
-      body:
-        string: '{"userId": 1, "firstName": "Mike", "lastName": "Foo"}'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/2
-    response:
-      body: null
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 500, message: SERVER_ERROR}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/2
-    response:
-      body:
-        string: '{"userId": 2, "firstName": "Sarah", "lastName": "Bar"}'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: POST
-      uri: http://testserver/users
-    response:
-      body:
-        string: '{"userId": 4, "firstName": "Lucy", "lastName": "Qux"}'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 201, message: CREATED}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: PUT
-      uri: http://testserver/users/4
-    response:
-      body:
-        string: '{"userId": 4, "firstName": "Lucy", "lastName": "Foo"}'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: PATCH
-      uri: http://testserver/users/4
-    response:
-      body:
-        string: '{"userId": 4, "firstName": "Lucy", "lastName": "Qux"}'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: DELETE
-      uri: http://testserver/users/4
-    response:
-      body: null
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/accounts?userId=1
-    response:
-      body:
-        string: '{
-                     "results": [
-                         {"accountName": "business", "number": "1234"},
-                         {"accountName": "expense", "number": "2345"}
-                     ],
-                     "page": 1,
-                     "nextPage": 2
-                 }'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/accounts?userId=1&page=2
-    response:
-      body:
-        string: '{
-                     "results": [
-                         {"accountName": "fun", "number": "6544"},
-                         {"accountName": "holiday", "number": "9283"}
-                     ],
-                     "page": 2,
-                     "nextPage": 3
-                 }'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/accounts?userId=1&page=3
-    response:
-      body:
-        string: '{
-                     "results": [
-                         {"accountName": "gifts", "number": "7827"},
-                         {"accountName": "home", "number": "1259"}
-                     ],
-                     "page": 3,
-                     "nextPage": null
-                 }'
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 200, message: OK}
diff --git a/tests/vcr_cassettes/error_cassette.yaml b/tests/vcr_cassettes/error_cassette.yaml
deleted file mode 100644
index ccf8881..0000000
--- a/tests/vcr_cassettes/error_cassette.yaml
+++ /dev/null
@@ -1,130 +0,0 @@
----
-interactions:
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/1
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 300, message: REDIRECT}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/2
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 399, message: REDIRECT}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/3
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 400, message: CLIENT ERROR}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/4
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 499, message: CLIENT ERROR}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/5
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 500, message: SERVER ERROR}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/6
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 599, message: SERVER ERROR}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/7
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 600, message: UNEXPECTED}
-  - request:
-      body: null
-      headers:
-        Accept: ['*/*']
-        Accept-Encoding: ['gzip, deflate']
-        Connection: [keep-alive]
-        Content-type: [application/json]
-        User-Agent: [python-requests/2.21.0]
-      method: GET
-      uri: http://testserver/users/8
-    response:
-      body:
-        string: ''
-      headers:
-        Content-Type: [application/json; charset=utf-8]
-      status: {code: 999, message: UNEXPECTED}