Repository: guillp/jwskate (language: python)

Issue:
HS* algs yield invalid key sizes
- `jwskate` version: 0.5.0
- Python version: 3.7.9
- Operating System: Fedora 31

### Description

I wanted to create HS512 key.
Generated key was shorter than expected.

(for required length see) https://www.rfc-editor.org/rfc/rfc7518#section-3.2

### What I Did

```
from jwskate import Jwk, Jwt
print(Jwk.generate_for_alg(alg="HS512"))
{'kty': 'oct', 'k': 'OaFBpH2kogyNz-LvmfYYBw', 'alg': 'HS512'}
```

I created a PR with a fix #6 



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index a11367b..e1ec77d 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -35,7 +35,7 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install poetry tox tox-gh-actions tox-poetry
+          pip install poetry "tox<4" tox-gh-actions tox-poetry
 
       - name: test with tox
         run:
@@ -57,7 +57,7 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install poetry tox tox-gh-actions tox-poetry
+          pip install poetry "tox<4" tox-gh-actions tox-poetry
 
       - name: test with tox
         run:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fea175d..b93f41b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
     -   id: forbid-tabs
     -   id: remove-tabs
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
+    rev: v4.4.0
     hooks:
     - id: trailing-whitespace
     - id: end-of-file-fixer
@@ -15,7 +15,7 @@ repos:
     - id: check-yaml
       args: [--unsafe]
 -   repo: https://github.com/hadialqattan/pycln
-    rev: v2.1.1
+    rev: v2.1.2
     hooks:
     -   id: pycln
         args: [--config=pyproject.toml]
@@ -24,20 +24,21 @@ repos:
     hooks:
     - id: isort
 -   repo: https://github.com/psf/black
-    rev: 22.10.0
+    rev: 22.12.0
     hooks:
     - id: black
--   repo: https://gitlab.com/pycqa/flake8
-    rev: 3.9.2
+-   repo: https://github.com/pycqa/flake8
+    rev: 6.0.0
     hooks:
     -   id: flake8
-        additional_dependencies: [flake8-typing-imports==1.10.0]
+        additional_dependencies:
+            - flake8-typing-imports==1.14.0
 -   repo: https://github.com/asottile/blacken-docs
     rev: v1.12.1
     hooks:
         - id: blacken-docs
 -   repo: https://github.com/pycqa/pydocstyle
-    rev: 6.1.1
+    rev: 6.2.3
     hooks:
     -   id: pydocstyle
         additional_dependencies:
@@ -45,7 +46,7 @@ repos:
         args:
             - --add-ignore=D107
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.982
+    rev: v0.991
     hooks:
     -   id: mypy
         args: [--strict]
diff --git a/README.md b/README.md
index df7fd6c..33bde61 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,8 @@
 
 A Pythonic implementation of the JOSE set of IETF specifications: [Json Web Signature][rfc7515], [Keys][rfc7517],
 [Algorithms][rfc7518], [Tokens][rfc7519] and [Encryption][rfc7516] (RFC7515 to 7519), and their extensions
-[ECDH Signatures][rfc8037] (RFC8037), [JWK Thumbprints][rfc7638] (RFC7638), and [JWK Thumbprint URI][rfc9278] (RFC9278).
+[ECDH Signatures][rfc8037] (RFC8037), [JWK Thumbprints][rfc7638] (RFC7638), and [JWK Thumbprint URI][rfc9278] (RFC9278),
+and with respect to [JWT Best Current Practices][rfc8725].
 
 - Free software: MIT
 - Documentation: <https://guillp.github.io/jwskate/>
@@ -140,25 +141,25 @@ The generated JWT claims will include the standardised claims:
 
 `jwskate` supports the following signature algorithms:
 
-| Signature Alg | Description                                    | Key Type | Reference                          | Note                  |
-|---------------|------------------------------------------------|----------|------------------------------------|-----------------------|
-| HS256         | HMAC using SHA-256                             | oct      | [RFC7518, Section 3.2]             |                       |
-| HS384         | HMAC using SHA-384                             | oct      | [RFC7518, Section 3.2]             |                       |
-| HS512         | HMAC using SHA-512                             | oct      | [RFC7518, Section 3.2]             |                       |
-| RS256         | RSASSA-PKCS1-v1_5 using SHA-256                | RSA      | [RFC7518, Section 3.3]             |                       |
-| RS384         | RSASSA-PKCS1-v1_5 using SHA-384                | RSA      | [RFC7518, Section 3.3]             |                       |
-| RS512         | RSASSA-PKCS1-v1_5 using SHA-512                | RSA      | [RFC7518, Section 3.3]             |                       |
-| ES256         | ECDSA using P-256 and SHA-256                  | EC       | [RFC7518, Section 3.4]             |                       |
-| ES384         | ECDSA using P-384 and SHA-384                  | EC       | [RFC7518, Section 3.4]             |                       |
-| ES512         | ECDSA using P-521 and SHA-512                  | EC       | [RFC7518, Section 3.4]             |                       |
-| PS256         | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | RSA      | [RFC7518, Section 3.5]             |                       |
-| PS384         | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | RSA      | [RFC7518, Section 3.5]             |                       |
-| PS512         | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | RSA      | [RFC7518, Section 3.5]             |                       |
-| EdDSA         | EdDSA signature algorithms                     | OKP      | [RFC8037, Section 3.1]             |                       |
-| ES256K        | ECDSA using secp256k1 curve and SHA-256        | EC       | [RFC8812, Section 3.2]             |                       |
-| HS1           | HMAC using SHA-1                               | oct      | https://www.w3.org/TR/WebCryptoAPI | Validation Only       |
-| RS1           | RSASSA-PKCS1-v1_5 with SHA-1                   | oct      | https://www.w3.org/TR/WebCryptoAPI | Validation Only       |
-| none          | No digital signature or MAC performed          |          | [RFC7518, Section 3.6]             | Not usable by mistake |
+| Signature Alg | Description                                    | Key Type | Reference                          | Note                           |
+|---------------|------------------------------------------------|----------|------------------------------------|--------------------------------|
+| HS256         | HMAC using SHA-256                             | oct      | [RFC7518, Section 3.2]             |                                |
+| HS384         | HMAC using SHA-384                             | oct      | [RFC7518, Section 3.2]             |                                |
+| HS512         | HMAC using SHA-512                             | oct      | [RFC7518, Section 3.2]             |                                |
+| RS256         | RSASSA-PKCS1-v1_5 using SHA-256                | RSA      | [RFC7518, Section 3.3]             |                                |
+| RS384         | RSASSA-PKCS1-v1_5 using SHA-384                | RSA      | [RFC7518, Section 3.3]             |                                |
+| RS512         | RSASSA-PKCS1-v1_5 using SHA-512                | RSA      | [RFC7518, Section 3.3]             |                                |
+| ES256         | ECDSA using P-256 and SHA-256                  | EC       | [RFC7518, Section 3.4]             |                                |
+| ES384         | ECDSA using P-384 and SHA-384                  | EC       | [RFC7518, Section 3.4]             |                                |
+| ES512         | ECDSA using P-521 and SHA-512                  | EC       | [RFC7518, Section 3.4]             |                                |
+| PS256         | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | RSA      | [RFC7518, Section 3.5]             |                                |
+| PS384         | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | RSA      | [RFC7518, Section 3.5]             |                                |
+| PS512         | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | RSA      | [RFC7518, Section 3.5]             |                                |
+| EdDSA         | EdDSA signature algorithms                     | OKP      | [RFC8037, Section 3.1]             | Ed2219 and Ed448 are supported |
+| ES256K        | ECDSA using secp256k1 curve and SHA-256        | EC       | [RFC8812, Section 3.2]             |                                |
+| HS1           | HMAC using SHA-1                               | oct      | https://www.w3.org/TR/WebCryptoAPI | Validation Only                |
+| RS1           | RSASSA-PKCS1-v1_5 with SHA-1                   | oct      | https://www.w3.org/TR/WebCryptoAPI | Validation Only                |
+| none          | No digital signature or MAC performed          |          | [RFC7518, Section 3.6]             | Not usable by mistake          |
 
 ### Supported Key Management algorithms
 
@@ -247,6 +248,8 @@ of mistakes.
 
 ### Safe Signature Verification
 
+As advised in [JWT Best Practices][rfc8725] $3.1:
+
 For every signature verification method in `jwskate`, the expected signature(s) algorithm(s) must be specified. That is
 to avoid a security flaw where your application accepts tokens with a weaker encryption scheme than what your security
 policy mandates; or even worse, where it accepts unsigned tokens, or tokens that are symmetrically signed with an
@@ -292,6 +295,7 @@ All cryptographic operations are handled by [cryptography](https://cryptography.
 [rfc8037]: https://www.rfc-editor.org/rfc/rfc8037.html
 [rfc8037, section 3.1]: https://www.rfc-editor.org/rfc/rfc8037.html#section-3.1
 [rfc8037, section 3.2]: https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2
+[rfc8725]: https://www.rfc-editor.org/rfc/rfc8725
 [rfc8812, section 3.1]: https://www.rfc-editor.org/rfc/rfc8812.html#section-3.1
 [rfc8812, section 3.2]: https://www.rfc-editor.org/rfc/rfc8812.html#name-ecdsa-signature-with-secp25
 [rfc9278]: https://www.rfc-editor.org/rfc/rfc9278.html
diff --git a/jwskate/enums.py b/jwskate/enums.py
index 37996c3..31a496d 100644
--- a/jwskate/enums.py
+++ b/jwskate/enums.py
@@ -1,6 +1,7 @@
 """This module contains enums for the various identifiers used in JWA and JWK.
 
 See [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml).
+
 """
 
 
diff --git a/jwskate/jwa/__init__.py b/jwskate/jwa/__init__.py
index ac8e367..503bb86 100644
--- a/jwskate/jwa/__init__.py
+++ b/jwskate/jwa/__init__.py
@@ -5,6 +5,7 @@
 `cryptography`.
 
 [RFC7518]: https://www.rfc-editor.org/rfc/rfc7518
+
 """
 
 from .base import (
@@ -71,6 +72,8 @@
     BaseECSignatureAlg,
     BaseHMACSigAlg,
     BaseRSASigAlg,
+    Ed448Dsa,
+    Ed25519Dsa,
     EdDsa,
 )
 
@@ -111,7 +114,9 @@
     "EcdhEs_A192KW",
     "EcdhEs_A256KW",
     "Ed25519",
+    "Ed25519Dsa",
     "Ed448",
+    "Ed448Dsa",
     "EdDsa",
     "EllipticCurve",
     "HS256",
diff --git a/jwskate/jwa/base.py b/jwskate/jwa/base.py
index da5400d..ae22a49 100644
--- a/jwskate/jwa/base.py
+++ b/jwskate/jwa/base.py
@@ -7,6 +7,7 @@
 
 import cryptography.exceptions
 from binapy import BinaPy
+from cryptography.hazmat.primitives import hashes
 
 
 class PrivateKeyRequired(AttributeError):
@@ -23,18 +24,19 @@ class BaseAlg:
     An algorithm has a `name` and a `description`, whose reference is found in [IANA JOSE registry][IANA].
 
     [IANA]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
+
     """
 
     use: str
     """Alg use ('sig' or 'enc')"""
 
     name: str
-    """Technical name of the algorithm"""
+    """Technical name of the algorithm."""
     description: str
     """Description of the algorithm (human readable)"""
     read_only: bool = False
-    """For algs that are considered insecure, set to True to allow only signature verification
-       or decryption of existing data, but don't allow new signatures or encryption."""
+    """For algs that are considered insecure, set to True to allow only signature verification or decryption of existing
+    data, but don't allow new signatures or encryption."""
 
     def __repr__(self) -> str:
         """Use the name of the alg as repr."""
@@ -46,6 +48,7 @@ class BaseSymmetricAlg(BaseAlg):
 
     Args:
         key: the key to use for cryptographic operations
+
     """
 
     def __init__(self, key: bytes):
@@ -64,6 +67,7 @@ def check_key(cls, key: bytes) -> None:
 
         Returns:
           Returns `None`. Raises an exception if the key is not suitable
+
         """
         pass
 
@@ -78,6 +82,7 @@ def supports_key(cls, key: bytes) -> bool:
 
         Returns:
           `True` if the key is suitable for this alg class, `False` otherwise
+
         """
         try:
             cls.check_key(key)
@@ -98,6 +103,7 @@ class BaseAsymmetricAlg(Generic[Kpriv, Kpub], BaseAlg):
 
     Args:
         key: the key to use.
+
     """
 
     private_key_class: Union[Type[Kpriv], Tuple[Type[Kpriv], ...]]
@@ -121,17 +127,19 @@ def check_key(cls, key: Union[Kpriv, Kpub]) -> None:
 
         Raises:
             Exception: if the key is not suitable for use with this alg class
+
         """
 
     @contextmanager
     def private_key_required(self) -> Iterator[Kpriv]:
-        """A context manager that checks if this alg is initialised with a private key.
+        """Check if this alg is initialised with a private key, as a context manager.
 
         Yields:
             the private key
 
         Raises:
             PrivateKeyRequired: if the configured key is not private
+
         """
         if not isinstance(self.key, self.private_key_class):
             raise PrivateKeyRequired()
@@ -139,13 +147,14 @@ def private_key_required(self) -> Iterator[Kpriv]:
 
     @contextmanager
     def public_key_required(self) -> Iterator[Kpub]:
-        """A context manager that checks if this alg is initialised with a public key.
+        """Check if this alg is initialised with a public key, as a context manager.
 
         Yields:
             The public key
 
         Raises:
             PublicKeyRequired: if the configured key is private
+
         """
         if not isinstance(self.key, self.public_key_class):
             raise PublicKeyRequired()
@@ -156,6 +165,7 @@ class BaseSignatureAlg(BaseAlg):
     """Base class for signature algorithms."""
 
     use = "sig"
+    hashing_alg: hashes.HashAlgorithm
 
     def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:
         """Sign arbitrary data, return the signature.
@@ -165,6 +175,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:
 
         Returns:
           the raw signature
+
         """
         raise NotImplementedError
 
@@ -179,6 +190,7 @@ def verify(
 
         Returns:
           `True` if the signature matches, `False` otherwise.
+
         """
         raise NotImplementedError
 
@@ -201,6 +213,7 @@ def check_key(cls, key: bytes) -> None:
 
         Raises:
             ValueError: if the key is not suitable
+
         """
         if len(key) * 8 != cls.key_size:
             raise ValueError(
@@ -213,6 +226,7 @@ def generate_key(cls) -> BinaPy:
 
         Returns:
             a random AES key
+
         """
         return BinaPy.random_bits(cls.key_size)
 
@@ -222,6 +236,7 @@ def generate_iv(cls) -> BinaPy:
 
         Returns:
             a random IV
+
         """
         return BinaPy.random_bits(cls.iv_size)
 
@@ -249,6 +264,7 @@ def encrypt(
           a tuple of ciphered data and authentication tag
 
         [AEAD]: https://wikipedia.org/wiki/Authenticated_encryption
+
         """
         raise NotImplementedError
 
@@ -275,6 +291,7 @@ def decrypt(
 
         Returns:
           the deciphered data
+
         """
         raise NotImplementedError
 
@@ -284,6 +301,7 @@ def init_random_key(cls) -> BaseAESEncryptionAlg:
 
         Returns:
             a subclass of BaseAESEncryptionAlg initialized with a randomly generated key
+
         """
         return cls(cls.generate_key())
 
diff --git a/jwskate/jwa/ec.py b/jwskate/jwa/ec.py
index 8a40464..8162a78 100644
--- a/jwskate/jwa/ec.py
+++ b/jwskate/jwa/ec.py
@@ -14,12 +14,13 @@ class EllipticCurve:
     """A descriptive class for Elliptic Curves.
 
     Elliptic Curves have a name, a `cryptography.ec.EllipticCurve`, and a coordinate size.
+
     """
 
     name: str
+    """Curve name as defined in [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml#web- key-elliptic-curve).
+    This name will appear in `alg` or `enc` fields in JOSE headers.
     """
-    Curve name as defined in [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve).
-    This name will appear in `alg` or `enc` fields in JOSE headers."""
 
     cryptography_curve: ec.EllipticCurve
     """`cryptography` curve instance."""
@@ -39,6 +40,7 @@ def generate(self) -> Tuple[int, int, int]:
 
         Returns:
              a tuple of 4 `int`s: `x` and `y` coordinates (public key) and `d` (private key)
+
         """
         key = ec.generate_private_key(self.cryptography_curve)
         pn = key.private_numbers()  # type: ignore
@@ -61,6 +63,7 @@ def get_curve(
 
         Raises:
             NotImplementedError: if the curve is not supported
+
         """
         for c in cls.instances.values():
             if c.cryptography_curve.name == key.curve.name:
@@ -83,6 +86,7 @@ def get_jwk_parameters(
 
         Raises:
             TypeError: if the provided key is not an EllipticCurvePrivateKey or EllipticCurvePublicKey
+
         """
         if not isinstance(key, (ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey)):
             raise TypeError(
@@ -113,21 +117,21 @@ def get_jwk_parameters(
     cryptography_curve=ec.SECP256R1(),
     coordinate_size=32,
 )
-"""P-256 curve"""
+"""P-256 curve."""
 
 P_384 = EllipticCurve(
     name="P-384",
     cryptography_curve=ec.SECP384R1(),
     coordinate_size=48,
 )
-"""P-384 curve"""
+"""P-384 curve."""
 
 P_521 = EllipticCurve(
     name="P-521",
     cryptography_curve=ec.SECP521R1(),
     coordinate_size=66,
 )
-"""P-521 curve"""
+"""P-521 curve."""
 
 secp256k1 = EllipticCurve(
     name="secp256k1",
diff --git a/jwskate/jwa/encryption/aescbchmac.py b/jwskate/jwa/encryption/aescbchmac.py
index 896d083..0009210 100644
--- a/jwskate/jwa/encryption/aescbchmac.py
+++ b/jwskate/jwa/encryption/aescbchmac.py
@@ -25,15 +25,12 @@ class BaseAesCbcHmacSha2(BaseAESEncryptionAlg):
     hash_alg: hashes.HashAlgorithm
     """Hash algorithm to use."""
 
-    def __init_subclass__(cls) -> None:
-        """This automatically sets the total key size based on the MAC and AES key sizes."""
-        cls.key_size = cls.mac_key_size + cls.aes_key_size
-
     def __init__(self, key: bytes) -> None:
         """Initialize this wrapper with the given key.
 
         Args:
             key: the key to use for encryption and decryption.
+
         """
         super().__init__(key)
         self.mac_key = self.key[: self.mac_key_size // 8]
@@ -56,6 +53,7 @@ def mac(
 
         Returns:
           the resulting MAC.
+
         """
         if not isinstance(ciphertext, bytes):
             ciphertext = bytes(ciphertext)
@@ -94,6 +92,7 @@ def encrypt(
 
         Returns:
           a tuple (encrypted_data, authentication_tag)
+
         """
         if not isinstance(plaintext, bytes):
             plaintext = bytes(plaintext)
@@ -134,6 +133,7 @@ def decrypt(
 
         Returns:
           the decrypted data
+
         """
         if not isinstance(ciphertext, bytes):
             ciphertext = bytes(ciphertext)
@@ -166,6 +166,7 @@ class A128CBC_HS256(BaseAesCbcHmacSha2):
     description = __doc__
     mac_key_size = 128
     aes_key_size = 128
+    key_size = mac_key_size + aes_key_size
     tag_size = 16
     hash_alg = hashes.SHA256()
 
@@ -177,6 +178,7 @@ class A192CBC_HS384(BaseAesCbcHmacSha2):
     description = __doc__
     mac_key_size = 192
     aes_key_size = 192
+    key_size = mac_key_size + aes_key_size
     tag_size = 24
     hash_alg = hashes.SHA384()
 
@@ -186,8 +188,8 @@ class A256CBC_HS512(BaseAesCbcHmacSha2):
 
     name = "A256CBC-HS512"
     description = __doc__
-    key_size = 512
     mac_key_size = 256
     aes_key_size = 256
+    key_size = mac_key_size + aes_key_size
     tag_size = 32
     hash_alg = hashes.SHA512()
diff --git a/jwskate/jwa/encryption/aesgcm.py b/jwskate/jwa/encryption/aesgcm.py
index d0673e6..cb6e9e9 100644
--- a/jwskate/jwa/encryption/aesgcm.py
+++ b/jwskate/jwa/encryption/aesgcm.py
@@ -34,6 +34,7 @@ def encrypt(
 
         Raises:
             ValueError: if the IV size is not appropriate
+
         """
         if not isinstance(iv, bytes):
             iv = bytes(iv)
@@ -70,6 +71,7 @@ def decrypt(
 
         Raises:
             ValueError: if the IV size is not appropriate
+
         """
         if not isinstance(ciphertext, bytes):
             ciphertext = bytes(ciphertext)
diff --git a/jwskate/jwa/key_mgmt/aesgcmkw.py b/jwskate/jwa/key_mgmt/aesgcmkw.py
index 71f7859..35ea82b 100644
--- a/jwskate/jwa/key_mgmt/aesgcmkw.py
+++ b/jwskate/jwa/key_mgmt/aesgcmkw.py
@@ -36,6 +36,7 @@ def wrap_key(
 
         Returns:
           a tuple (wrapped_key, authentication_tag)
+
         """
         return self.encrypt(plainkey, iv=iv)
 
@@ -61,6 +62,7 @@ def unwrap_key(
 
         Returns:
           the unwrapped key.
+
         """
         return self.decrypt(cipherkey, auth_tag=tag, iv=iv)
 
diff --git a/jwskate/jwa/key_mgmt/aeskw.py b/jwskate/jwa/key_mgmt/aeskw.py
index cb727da..b0159f7 100644
--- a/jwskate/jwa/key_mgmt/aeskw.py
+++ b/jwskate/jwa/key_mgmt/aeskw.py
@@ -24,6 +24,7 @@ def check_key(cls, key: bytes) -> None:
 
         Raises:
             ValueError: if the key is not appropriate
+
         """
         if not isinstance(key, bytes) or len(key) * 8 != cls.key_size:
             raise ValueError(f"Key must be {cls.key_size} bits.")
@@ -36,6 +37,7 @@ def wrap_key(self, plainkey: bytes) -> BinaPy:
 
         Returns:
           BinaPy: the wrapped key.
+
         """
         return BinaPy(keywrap.aes_key_wrap(self.key, plainkey))
 
@@ -47,6 +49,7 @@ def unwrap_key(self, cipherkey: Union[bytes, SupportsBytes]) -> BinaPy:
 
         Returns:
           BinaPy: the unwrapped key.
+
         """
         if not isinstance(cipherkey, bytes):
             cipherkey = bytes(cipherkey)
diff --git a/jwskate/jwa/key_mgmt/dir.py b/jwskate/jwa/key_mgmt/dir.py
index 24f7fc9..632e23e 100644
--- a/jwskate/jwa/key_mgmt/dir.py
+++ b/jwskate/jwa/key_mgmt/dir.py
@@ -21,6 +21,7 @@ def direct_key(self, aesalg: Type[BaseSymmetricAlg]) -> BinaPy:
 
         Returns:
           the current configured key, as-is
+
         """
         aesalg.check_key(self.key)
         return BinaPy(self.key)
diff --git a/jwskate/jwa/key_mgmt/ecdh.py b/jwskate/jwa/key_mgmt/ecdh.py
index c613a03..7f2a9c9 100644
--- a/jwskate/jwa/key_mgmt/ecdh.py
+++ b/jwskate/jwa/key_mgmt/ecdh.py
@@ -45,6 +45,7 @@ def otherinfo(cls, alg: str, apu: bytes, apv: bytes, key_size: int) -> BinaPy:
 
         Returns:
             the "otherinfo" value
+
         """
         algorithm_id = BinaPy.from_int(len(alg), length=4) + BinaPy(alg)
         partyuinfo = BinaPy.from_int(len(apu), length=4) + apu
@@ -63,7 +64,7 @@ def ecdh(
             ec.EllipticCurvePublicKey, x25519.X25519PublicKey, x448.X448PublicKey
         ],
     ) -> BinaPy:
-        """This does an Elliptic Curve Diffie Hellman key exchange.
+        """Perform an Elliptic Curve Diffie Hellman key exchange.
 
         This derives a shared key between a sender and a receiver, based on a public and a private key from each side.
         ECDH exchange produces the same key with either a sender private key and a recipient public key,
@@ -75,6 +76,7 @@ def ecdh(
 
         Returns:
           a shared key
+
         """
         if isinstance(private_key, ec.EllipticCurvePrivateKey) and isinstance(
             public_key, ec.EllipticCurvePublicKey
@@ -119,6 +121,7 @@ def derive(
 
         Returns:
             the derived key
+
         """
         shared_key = cls.ecdh(private_key, public_key)
         ckdf = ConcatKDFHash(
@@ -135,6 +138,7 @@ def generate_ephemeral_key(
 
         Returns:
             a generated EllipticCurvePrivateKey, on the same curve as this algorithm key
+
         """
         if isinstance(
             self.key, (ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey)
@@ -165,6 +169,7 @@ def sender_key(
 
         Returns:
             the CEK for encryption by the sender
+
         """
         with self.public_key_required() as key:
             apu = BinaPy(headers.get("apu", b"")).decode_from("b64u")
@@ -198,6 +203,7 @@ def recipient_key(
 
         Returns:
             the CEK for decryption by the recipient
+
         """
         with self.private_key_required() as key:
             apu = BinaPy(headers.get("apu", b"")).decode_from("b64u")
@@ -225,7 +231,7 @@ def wrap_key_with_epk(
         ],
         **headers: Any,
     ) -> BinaPy:
-        """Wraps a key for content encryption.
+        """Wrap a key for content encryption.
 
         Args:
           plainkey: the key to wrap
@@ -234,6 +240,7 @@ def wrap_key_with_epk(
 
         Returns:
             the wrapped CEK
+
         """
         aes_key = self.sender_key(
             ephemeral_private_key, key_size=self.kwalg.key_size, **headers
@@ -257,6 +264,7 @@ def unwrap_key_with_epk(
 
         Returns:
             the unwrapped key
+
         """
         aes_key = self.recipient_key(
             ephemeral_public_key, key_size=self.kwalg.key_size, **headers
diff --git a/jwskate/jwa/key_mgmt/pbes2.py b/jwskate/jwa/key_mgmt/pbes2.py
index 1b9cce3..ae6586b 100644
--- a/jwskate/jwa/key_mgmt/pbes2.py
+++ b/jwskate/jwa/key_mgmt/pbes2.py
@@ -17,6 +17,7 @@ class BasePbes2(BaseKeyManagementAlg):
 
     Args:
         password: the encryption/decryption password to use
+
     """
 
     kwalg: Type[BaseAesKeyWrap]
@@ -40,6 +41,7 @@ def generate_salt(self, size: int = 12) -> BinaPy:
 
         Raises:
             ValueError: if the salt is less than 8 bytes long
+
         """
         if size < 8:
             raise ValueError("salts used for PBES2 must be at least 8 bytes long")
@@ -54,6 +56,7 @@ def derive(self, *, salt: bytes, count: int) -> BinaPy:
 
         Returns:
             the generated encryption/decryption key
+
         """
         full_salt = self.name.encode() + b"\0" + salt
         pbkdf = pbkdf2.PBKDF2HMAC(
@@ -74,6 +77,7 @@ def wrap_key(self, plainkey: bytes, *, salt: bytes, count: int) -> BinaPy:
 
         Returns:
             the wrapped key
+
         """
         aes_key = self.derive(salt=salt, count=count)
         return BinaPy(self.kwalg(aes_key).wrap_key(plainkey))
@@ -88,6 +92,7 @@ def unwrap_key(self, cipherkey: bytes, *, salt: bytes, count: int) -> BinaPy:
 
         Returns:
             the unwrapped key
+
         """
         aes_key = self.derive(salt=salt, count=count)
         return BinaPy(self.kwalg(aes_key).unwrap_key(cipherkey))
diff --git a/jwskate/jwa/key_mgmt/rsa.py b/jwskate/jwa/key_mgmt/rsa.py
index d2722d4..cd1f26e 100644
--- a/jwskate/jwa/key_mgmt/rsa.py
+++ b/jwskate/jwa/key_mgmt/rsa.py
@@ -17,6 +17,7 @@ class BaseRsaKeyWrap(
 
     Args:
         key: the private or public key to use
+
     """
 
     padding: Any
@@ -41,6 +42,7 @@ def wrap_key(self, plainkey: bytes) -> BinaPy:
 
         Raises:
             PublicKeyRequired: if this algorithm is initialized with a private key instead of a public key
+
         """
         if self.read_only:
             raise NotImplementedError(
@@ -59,6 +61,7 @@ def unwrap_key(self, cipherkey: Union[bytes, SupportsBytes]) -> BinaPy:
             the unwrapped clear-text key
         Raises:
             PrivateKeyRequired: if this alg is initialized with a public key instead of a private key
+
         """
         if not isinstance(cipherkey, bytes):
             cipherkey = bytes(cipherkey)
diff --git a/jwskate/jwa/okp.py b/jwskate/jwa/okp.py
index ec09a49..4137e29 100644
--- a/jwskate/jwa/okp.py
+++ b/jwskate/jwa/okp.py
@@ -1,6 +1,7 @@
 """This module contains classes that describe CFRG Elliptic Curve Diffie-Hellman algorithms as specified in [RFC8037].
 
 [RFC8037]: https://www.rfc-editor.org/rfc/rfc8037.html
+
 """
 
 from __future__ import annotations
@@ -10,9 +11,10 @@
 
 from cryptography.hazmat.primitives import serialization
 from cryptography.hazmat.primitives.asymmetric import ed448, ed25519, x448, x25519
-from typing_extensions import Protocol
+from typing_extensions import Protocol, runtime_checkable
 
 
+@runtime_checkable
 class PublicKeyProtocol(Protocol):  # noqa
     """A protocol that each `cryptography` ECDH public key class implements."""
 
@@ -24,6 +26,7 @@ def public_bytes(
         ...
 
 
+@runtime_checkable
 class PrivateKeyProtocol(Protocol):  # noqa
     """A protocol that each `cryptography` ECDH private key class implements."""
 
@@ -48,8 +51,9 @@ class OKPCurve:
     """Represent an Octet Key Pair (OKP) Curve."""
 
     name: str
-    """Curve name as defined in [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve).
-    This name will appear in `alg` headers."""
+    """Curve name as defined in [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml#web- key-elliptic-curve).
+    This name will appear in `crv` headers.
+    """
 
     description: str
     """Curve description (human readable)."""
@@ -75,6 +79,7 @@ def generate(self) -> Tuple[bytes, bytes]:
 
         Returns:
             a tuple of `x` (public  part), and `d` (private part), as bytes
+
         """
         key = self.cryptography_private_key_class.generate()
         x = key.public_key().public_bytes(
@@ -99,13 +104,14 @@ def get_curve(cls, key: Union[PublicKeyProtocol, PrivateKeyProtocol]) -> OKPCurv
 
         Raises:
             NotImplementedError: if the required OKP curve is not supported
+
         """
         for c in cls.instances.values():
             if isinstance(
                 key, (c.cryptography_private_key_class, c.cryptography_public_key_class)
             ):
                 return c
-        raise NotImplementedError(f"Unsupported OKP key {type(key)}")
+        raise TypeError(f"Unsupported OKP key {type(key)}")
 
 
 Ed25519 = OKPCurve(
diff --git a/jwskate/jwa/signature/__init__.py b/jwskate/jwa/signature/__init__.py
index 5d85561..9e05fcf 100644
--- a/jwskate/jwa/signature/__init__.py
+++ b/jwskate/jwa/signature/__init__.py
@@ -1,7 +1,7 @@
 """This module exposes all the Signature algorithms available from `jwskate`."""
 
 from .ec import ES256, ES256K, ES384, ES512, BaseECSignatureAlg
-from .eddsa import EdDsa
+from .eddsa import Ed448Dsa, Ed25519Dsa, EdDsa
 from .hmac import HS256, HS384, HS512, BaseHMACSigAlg
 from .rsa import PS256, PS384, PS512, RS256, RS384, RS512, BaseRSASigAlg
 
@@ -13,6 +13,8 @@
     "ES256K",
     "ES384",
     "ES512",
+    "Ed25519Dsa",
+    "Ed448Dsa",
     "EdDsa",
     "HS256",
     "HS384",
diff --git a/jwskate/jwa/signature/ec.py b/jwskate/jwa/signature/ec.py
index 9ff7069..e85dae0 100644
--- a/jwskate/jwa/signature/ec.py
+++ b/jwskate/jwa/signature/ec.py
@@ -17,7 +17,6 @@ class BaseECSignatureAlg(
     """Base class for Elliptic Curve signature algorithms."""
 
     curve: EllipticCurve
-    hashing_alg: hashes.HashAlgorithm
     public_key_class = ec.EllipticCurvePublicKey
     private_key_class = ec.EllipticCurvePrivateKey
 
diff --git a/jwskate/jwa/signature/eddsa.py b/jwskate/jwa/signature/eddsa.py
index 6db07af..2b815a1 100644
--- a/jwskate/jwa/signature/eddsa.py
+++ b/jwskate/jwa/signature/eddsa.py
@@ -4,6 +4,7 @@
 
 from binapy import BinaPy
 from cryptography import exceptions
+from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives.asymmetric import ed448, ed25519
 
 from ..base import BaseAsymmetricAlg, BaseSignatureAlg
@@ -45,3 +46,35 @@ def verify(
                 return True
             except exceptions.InvalidSignature:
                 return False
+
+
+class Ed25519Dsa(
+    BaseAsymmetricAlg[
+        ed25519.Ed25519PrivateKey,
+        ed25519.Ed25519PublicKey,
+    ],
+    BaseSignatureAlg,
+):
+    """EdDSA signature algorithm with Ed25519 curve."""
+
+    description = __doc__
+    hashing_alg = hashes.SHA256()
+
+    private_key_class = ed25519.Ed25519PrivateKey
+    public_key_class = ed25519.Ed25519PublicKey
+
+
+class Ed448Dsa(
+    BaseAsymmetricAlg[
+        ed448.Ed448PrivateKey,
+        ed448.Ed448PublicKey,
+    ],
+    BaseSignatureAlg,
+):
+    """EdDSA signature algorithm with Ed25519 curve."""
+
+    description = __doc__
+    hashing_alg = hashes.SHAKE256(114)
+
+    private_key_class = ed448.Ed448PrivateKey
+    public_key_class = ed448.Ed448PublicKey
diff --git a/jwskate/jwa/signature/hmac.py b/jwskate/jwa/signature/hmac.py
index b1199c8..7f25767 100644
--- a/jwskate/jwa/signature/hmac.py
+++ b/jwskate/jwa/signature/hmac.py
@@ -12,7 +12,6 @@ class BaseHMACSigAlg(BaseSymmetricAlg, BaseSignatureAlg):
     """Base class for HMAC signature algorithms."""
 
     mac: Type[hmac.HMAC] = hmac.HMAC
-    hashing_alg: hashes.HashAlgorithm
     min_key_size: int
 
     def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:  # noqa: D102
diff --git a/jwskate/jwa/signature/rsa.py b/jwskate/jwa/signature/rsa.py
index 607dc5d..3143b76 100644
--- a/jwskate/jwa/signature/rsa.py
+++ b/jwskate/jwa/signature/rsa.py
@@ -15,7 +15,6 @@ class BaseRSASigAlg(
 ):
     """Base class for RSA based signature algorithms."""
 
-    hashing_alg: hashes.HashAlgorithm
     padding_alg: padding.AsymmetricPadding = padding.PKCS1v15()
     min_key_size: int = 2048
 
@@ -34,6 +33,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:
         Raises:
             NotImplementedError: for algorithms that are considered insecure, only signature verification is available
             PrivateKeyRequired: if the configured key is not private
+
         """
         if self.read_only:
             raise NotImplementedError
@@ -55,6 +55,7 @@ def verify(
 
         Returns:
             `True` if the signature is valid, `False` otherwise
+
         """
         if not isinstance(data, bytes):
             data = bytes(data)
diff --git a/jwskate/jwe/__init__.py b/jwskate/jwe/__init__.py
index 27dc854..578f535 100644
--- a/jwskate/jwe/__init__.py
+++ b/jwskate/jwe/__init__.py
@@ -1,6 +1,7 @@
 """This module implements Json Web Encryption as described in [RFC7516].
 
 [RFC7516]: https://www.rfc-editor.org/rfc/rfc7516
+
 """
 
 from .compact import InvalidJwe, JweCompact
diff --git a/jwskate/jwe/compact.py b/jwskate/jwe/compact.py
index c5802b3..ac747a5 100644
--- a/jwskate/jwe/compact.py
+++ b/jwskate/jwe/compact.py
@@ -93,8 +93,8 @@ def from_parts(
 
         Args:
           headers: the headers (as a mapping of name: value)
-          cek: the raw cek
-          iv: the raw iv
+          cek: the raw CEK
+          iv: the raw IV
           ciphertext: the raw ciphertext
           tag: the authentication tag
 
diff --git a/jwskate/jwk/alg.py b/jwskate/jwk/alg.py
index dfdd40a..a0dde0a 100644
--- a/jwskate/jwk/alg.py
+++ b/jwskate/jwk/alg.py
@@ -23,7 +23,7 @@ def select_alg_class(
     jwk_alg: Optional[str] = None,
     alg: Optional[str] = None,
 ) -> T:
-    """Internal helper method to choose the appropriate alg class to use for cryptographic operations.
+    """Choose the appropriate alg class to use for cryptographic operations.
 
     Given:
     - a mapping of supported algs names to wrapper classes
@@ -48,6 +48,7 @@ def select_alg_class(
     Raises:
         UnsupportedAlg: if the requested alg is not supported
         ValueError: if supported_algs is empty
+
     """
     if not supported_algs:
         raise ValueError("No possible algorithms to choose from!")
@@ -84,7 +85,7 @@ def select_alg_classes(
     alg: Optional[str] = None,
     algs: Optional[Iterable[str]] = None,
 ) -> List[T]:
-    """Internal helper method to select several appropriate algs classes to use on cryptographic operations.
+    """Select several appropriate algs classes to use on cryptographic operations.
 
     This method is typically used to get the list of valid algorithms when checking a signature, when several algorithms are allowed.
 
@@ -113,6 +114,7 @@ def select_alg_classes(
 
     Warnings:
         if the requested 'alg' is different that the 'jwk_alg', or the 'jwk_alg' is not in the 'algs'
+
     """
     if alg and algs:
         raise ValueError("Please use either parameter 'alg' or 'algs', not both.")
diff --git a/jwskate/jwk/base.py b/jwskate/jwk/base.py
index 5f9c712..d88ffea 100644
--- a/jwskate/jwk/base.py
+++ b/jwskate/jwk/base.py
@@ -30,6 +30,7 @@
     BaseAlg,
     BaseAsymmetricAlg,
     BaseEcdhEs_AesKw,
+    BaseHMACSigAlg,
     BaseKeyManagementAlg,
     BaseRsaKeyWrap,
     BaseSignatureAlg,
@@ -76,16 +77,17 @@ class Jwk(BaseJsonDict):
     Args:
         params: a dict with the parsed Jwk parameters, or a `cryptography key`, or another `Jwk`
         include_kid_thumbprint: if `True` (default), and there is no kid in the provided params, generate a kid based on the key thumbprint
+
     """
 
     subclasses: Dict[str, Type[Jwk]] = {}
-    """A dict of 'kty' values to subclasses implementing each specific Key Type"""
+    """A dict of 'kty' values to subclasses implementing each specific Key Type."""
 
     cryptography_key_types: Dict[Any, Type[Jwk]] = {}
-    """A dict of cryptography key classes to its specific 'kty' value"""
+    """A dict of cryptography key classes to its specific 'kty' value."""
 
     PARAMS: Mapping[str, JwkParameter]
-    """A dict of parameters. Key is parameter name, value is a tuple (description, is_private, is_required, kind)"""
+    """A dict of parameters. Key is parameter name, value is a tuple (description, is_private, is_required, kind)."""
 
     KTY: ClassVar[str]
     """The Key Type associated with this JWK."""
@@ -112,6 +114,7 @@ def __init_subclass__(cls) -> None:
         """Automatically add subclasses to the registry.
 
         This allows `__new__` to pick the appropriate subclass when creating a Jwk.
+
         """
         Jwk.subclasses[cls.KTY] = cls
         for klass in cls.CRYPTOGRAPHY_PRIVATE_KEY_CLASSES:
@@ -131,6 +134,7 @@ def __new__(cls, key: Union[Jwk, Dict[str, Any], Any], **kwargs: Any):  # type:
         Args:
             key: a dict containing JWK parameters, or another Jwk instance, or a `cryptography` key
             **kwargs: additional members to include in the Jwk
+
         """
         if cls == Jwk:
             if isinstance(key, Jwk):
@@ -177,6 +181,7 @@ def _get_alg_class(cls, alg: str) -> Type[BaseAlg]:
 
         Returns:
             the matching JWA wrapper
+
         """
         alg_class: Optional[Type[BaseAlg]]
 
@@ -200,6 +205,7 @@ def is_private(self) -> bool:
 
         Returns:
             `True` if the key is private, `False` otherwise
+
         """
         return True
 
@@ -221,6 +227,7 @@ def __getattr__(self, param: str) -> Any:
 
         Raises:
             AttributeError: if the param is not found
+
         """
         value = self.get(param)
         if value is None:
@@ -236,6 +243,7 @@ def __setitem__(self, key: str, value: Any) -> None:
 
         Raises:
             RuntimeError: when trying to modify cryptographic attributes
+
         """
         if key in self.PARAMS:
             raise RuntimeError("JWK key attributes cannot be modified.")
@@ -249,6 +257,7 @@ def thumbprint(self, hashalg: str = "sha-256") -> str:
 
         Returns:
             the calculated thumbprint
+
         """
         alg = self.IANA_HASH_FUNCTION_NAMES.get(hashalg)
         if not alg:
@@ -267,13 +276,14 @@ def thumbprint(self, hashalg: str = "sha-256") -> str:
         )
 
     def thumbprint_uri(self, hashalg: str = "sha-256") -> str:
-        """Returns the JWK thumbprint URI for this key.
+        """Return the JWK thumbprint URI for this key.
 
         Args:
             hashalg: the IANA registered name for the hash alg to use
 
         Returns:
              the JWK thumbprint uri for this Jwk
+
         """
         return (
             f"urn:ietf:params:oauth:jwk-thumbprint:{hashalg}:{self.thumbprint(hashalg)}"
@@ -285,6 +295,7 @@ def kty(self) -> str:
 
         Returns:
             the key type
+
         """
         return self.KTY
 
@@ -294,6 +305,7 @@ def alg(self) -> Optional[str]:
 
         Returns:
             the key alg
+
         """
         alg = self.get("alg")
         if alg is not None and not isinstance(alg, str):  # pragma: no branch
@@ -310,6 +322,7 @@ def signature_class(self, alg: Optional[str] = None) -> Type[BaseSignatureAlg]:
 
         Returns:
             the appropriate `BaseSignatureAlg` subclass
+
         """
         return select_alg_class(self.SIGNATURE_ALGORITHMS, jwk_alg=self.alg, alg=alg)
 
@@ -323,13 +336,14 @@ def encryption_class(self, alg: Optional[str] = None) -> Type[BaseAESEncryptionA
 
         Returns:
             the appropriate `BaseAESEncryptionAlg` subclass
+
         """
         return select_alg_class(self.ENCRYPTION_ALGORITHMS, jwk_alg=self.alg, alg=alg)
 
     def key_management_class(
         self, alg: Optional[str] = None
     ) -> Type[BaseKeyManagementAlg]:
-        """Return the appropriate key management algorithm class (a `BaseKeyManagementAlg` subclass) to use with this key.
+        """Return the appropriate key management algorithm class to use with this key.
 
         If this key doesn't have an `alg` parameter, you must supply one as parameter to this method.
 
@@ -338,6 +352,7 @@ def key_management_class(
 
         Returns:
             the appropriate `BaseKeyManagementAlg` subclass
+
         """
         return select_alg_class(
             self.KEY_MANAGEMENT_ALGORITHMS, jwk_alg=self.alg, alg=alg
@@ -353,6 +368,7 @@ def signature_wrapper(self, alg: Optional[str] = None) -> BaseSignatureAlg:
 
         Returns:
             a `BaseKeyManagementAlg` instance initialized with the current key
+
         """
         alg_class = self.signature_class(alg)
         if issubclass(alg_class, BaseSymmetricAlg):
@@ -371,6 +387,7 @@ def encryption_wrapper(self, alg: Optional[str] = None) -> BaseAESEncryptionAlg:
 
         Returns:
             a `BaseAESEncryptionAlg` instance initialized with the current key
+
         """
         alg_class = self.encryption_class(alg)
         if issubclass(alg_class, BaseSymmetricAlg):
@@ -389,6 +406,7 @@ def key_management_wrapper(self, alg: Optional[str] = None) -> BaseKeyManagement
 
         Returns:
             a `BaseKeyManagementAlg` instance initialized with the current key
+
         """
         alg_class = self.key_management_class(alg)
         if issubclass(alg_class, BaseSymmetricAlg):
@@ -409,9 +427,10 @@ def kid(self) -> Optional[str]:
     def use(self) -> Optional[str]:
         """Return the key use.
 
-        If no `alg` parameter is present, this returns the `use` parameter from this JWK. If an
-        `alg` parameter is present, the use is deduced from this alg. To check for the presence of
-        the `use` parameter, use `jwk.get('use')`.
+        If no `alg` parameter is present, this returns the `use` parameter from this JWK. If an `alg` parameter is
+        present, the use is deduced from this alg. To check for the presence of the `use` parameter, use
+        `jwk.get('use')`.
+
         """
         if self.alg:
             return self._get_alg_class(self.alg).use
@@ -422,9 +441,10 @@ def use(self) -> Optional[str]:
     def key_ops(self) -> Tuple[str, ...]:
         """Return the key operations.
 
-        If no `alg` parameter is present, this returns the `key_ops` parameter from this JWK. If an
-        `alg` parameter is present, the key operations are deduced from this alg. To check for the
-        presence of the `key_ops` parameter, use `jwk.get('key_ops')`.
+        If no `alg` parameter is present, this returns the `key_ops` parameter from this JWK. If an `alg` parameter is
+        present, the key operations are deduced from this alg. To check for the presence of the `key_ops` parameter, use
+        `jwk.get('key_ops')`.
+
         """
         key_ops: Tuple[str, ...]
         if self.use == "sig":
@@ -454,13 +474,14 @@ def key_ops(self) -> Tuple[str, ...]:
         return tuple(key_ops)
 
     def _validate(self) -> None:
-        """Internal method used to validate a Jwk.
+        """Validate the content of this JWK.
 
         It checks that all required parameters are present and well-formed. If the key is private, it sets the `is_private` flag to `True`.
 
         Raises:
             TypeError: if the key type doesn't match the subclass
             InvalidJwk: if the JWK misses required members or has invalid members
+
         """
         if self.get("kty") != self.KTY:
             raise TypeError(
@@ -520,6 +541,7 @@ def supported_signing_algorithms(self) -> List[str]:
 
         Returns:
           a list of supported algs
+
         """
         return list(self.SIGNATURE_ALGORITHMS)
 
@@ -528,6 +550,7 @@ def supported_key_management_algorithms(self) -> List[str]:
 
         Returns:
             a list of supported algs
+
         """
         return list(self.KEY_MANAGEMENT_ALGORITHMS)
 
@@ -536,6 +559,7 @@ def supported_encryption_algorithms(self) -> List[str]:
 
         Returns:
             a list of supported algs
+
         """
         return list(self.ENCRYPTION_ALGORITHMS)
 
@@ -544,6 +568,7 @@ def public_jwk(self) -> Jwk:
 
         Returns:
           a Jwk with the public key
+
         """
         if not self.is_private:
             return self
@@ -581,6 +606,7 @@ def as_jwks(self) -> JwkSet:
 
         Returns:
             a JwsSet with this single key
+
         """
         from .jwks import JwkSet
 
@@ -597,6 +623,7 @@ def sign(
 
         Returns:
           the generated signature
+
         """
         wrapper = self.signature_wrapper(alg)
         signature = wrapper.sign(data)
@@ -620,6 +647,7 @@ def verify(
 
         Returns:
           `True` if the signature matches, `False` otherwise
+
         """
         if not self.is_symmetric and self.is_private:
             warnings.warn(
@@ -662,6 +690,7 @@ def encrypt(
 
         Returns:
           a tuple (ciphertext, iv, authentication_tag), as raw data
+
         """
         raise NotImplementedError  # pragma: no cover
 
@@ -687,6 +716,7 @@ def decrypt(
 
         Returns:
           the clear-text data
+
         """
         raise NotImplementedError  # pragma: no cover
 
@@ -699,7 +729,9 @@ def sender_key(
         epk: Optional[Jwk] = None,
         **headers: Any,
     ) -> Tuple[Jwk, BinaPy, Mapping[str, Any]]:
-        """Used by encrypted token senders to produce a Content Encryption Key.
+        """Produce a Content Encryption Key, to use for encryption.
+
+        This method is meant to be used by encrypted token senders. Recipients should use the matching method `Jwk.recipient_key()`.
 
         Returns a tuple with 3 items:
 
@@ -725,6 +757,7 @@ def sender_key(
 
         Raises:
             UnsupportedAlg: if the requested alg identifier is not supported
+
         """
         from jwskate import SymmetricJwk
 
@@ -810,7 +843,9 @@ def recipient_key(
         alg: Optional[str] = None,
         **headers: Any,
     ) -> Jwk:
-        """Used by token recipients to obtain the CEK, which then allows decryption of the payload.
+        """Produce a Content Encryption Key, to use for decryption.
+
+        This method is meant to be used by encrypted token recipient. Senders should use the matching method `Jwk.sender_key()`.
 
         Args:
           wrapped_cek: the wrapped CEK
@@ -823,6 +858,7 @@ def recipient_key(
 
         Raises:
             UnsupportedAlg: if the requested alg identifier is not supported
+
         """
         from jwskate import SymmetricJwk
 
@@ -896,6 +932,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> Jwk:
 
         Raises:
             TypeError: if the key type is not supported
+
         """
         for klass in cryptography_key.__class__.mro():
             jwk_class = cls.cryptography_key_types.get(klass)
@@ -911,6 +948,7 @@ def _to_cryptography_key(self) -> Any:
 
         Returns:
             a `cryptography`key instance initialized from the current key
+
         """
         raise NotImplementedError
 
@@ -930,6 +968,7 @@ def from_pem_key(
 
         Returns:
             a Jwk instance from the loaded key
+
         """
         data = data.encode() if isinstance(data, str) else data
         password = password.encode("UTF-8") if isinstance(password, str) else password
@@ -962,6 +1001,7 @@ def to_pem(self, password: Union[bytes, str, None] = None) -> bytes:
 
         Returns:
             the PEM serialized key
+
         """
         password = (
             str(password).encode("UTF-8") if isinstance(password, str) else password
@@ -999,6 +1039,7 @@ def generate(cls, **kwargs: Any) -> Jwk:
 
         Returns:
             a Jwk instance with a generated key
+
         """
         raise NotImplementedError
 
@@ -1015,6 +1056,7 @@ def generate_for_kty(cls, kty: str, **kwargs: Any) -> Jwk:
 
         Raises:
             UnsupportedKeyType: if the key type is not supported
+
         """
         jwk_class = cls.subclasses.get(kty)
         if jwk_class is None:
@@ -1031,13 +1073,31 @@ def generate_for_alg(cls, alg: str, **kwargs: Any) -> Jwk:
 
         Returns:
             the resulting Jwk
+
         """
         for kty, jwk_class in cls.subclasses.items():
             alg_class: Optional[Type[BaseAlg]]
             try:
                 alg_class = jwk_class._get_alg_class(alg)
-                if issubclass(jwk_class, BaseAESEncryptionAlg):
+                # special cases for AES or HMAC based algs which require a specific key size
+                if issubclass(alg_class, (BaseAESEncryptionAlg, BaseAesKeyWrap)):
+                    key_size = kwargs.get("key_size")
+                    if key_size is not None and key_size != alg_class.key_size:
+                        raise ValueError(
+                            f"Key for {alg} must be exactly {alg_class.key_size} bits. "
+                            "You should remove the `key_size` parameter to generate a key of the appropriate length."
+                        )
                     kwargs.setdefault("key_size", alg_class.key_size)
+                elif issubclass(alg_class, BaseHMACSigAlg):
+                    key_size = kwargs.get("key_size")
+                    if key_size is not None and key_size < alg_class.min_key_size:
+                        warnings.warn(
+                            f"Symmetric keys to use with {alg} should be at least {alg_class.min_key_size} bits "
+                            "in order to make the key at least as hard to brute-force as the signature. "
+                            f"You requested a key size of {key_size} bits."
+                        )
+                    else:
+                        kwargs.setdefault("key_size", alg_class.min_key_size)
 
                 return jwk_class.generate(alg=alg, **kwargs)
             except UnsupportedAlg:
@@ -1046,10 +1106,11 @@ def generate_for_alg(cls, alg: str, **kwargs: Any) -> Jwk:
         raise UnsupportedAlg(alg)
 
     def copy(self) -> Jwk:
-        """Creates a copy of this key.
+        """Create a copy of this key.
 
         Returns:
             a copy of this key, with the same value
+
         """
         return Jwk(super().copy())
 
@@ -1065,6 +1126,7 @@ def with_kid_thumbprint(self, force: bool = False) -> Jwk:
 
         Returns:
             a copy of this key with a "kid" (either the previous one or the existing one, depending on `force`).
+
         """
         if self.get("kid") is not None and not force:
             return self
@@ -1094,6 +1156,7 @@ def with_usage_parameters(
 
         Returns:
             a Jwk with the same key, with `alg`, `use` and `key_ops` parameters.
+
         """
         alg = alg or self.alg
 
@@ -1115,10 +1178,10 @@ def with_usage_parameters(
         return jwk
 
     def minimize(self) -> Jwk:
-        """Strips out any optional or non-standard parameter from that key.
+        """Strip out any optional or non-standard parameter from that key.
+
+        This will remove `alg`, `use`, `key_ops`, optional parameters from RSA keys, and other unknown parameters.
 
-        This will remove `alg`, `use`, `key_ops`, optional parameters from RSA keys, and other unknown
-        parameters.
         """
         jwk = self.copy()
         for key in self.keys():
@@ -1147,6 +1210,7 @@ def check(
 
         Raises:
             ValueError: if any check fails
+
         """
         if is_private is not None:
             if is_private is True and self.is_private is False:
@@ -1197,6 +1261,7 @@ def to_jwk(
 
     Returns:
         a Jwk key
+
     """
     jwk = key if isinstance(key, Jwk) else Jwk(key)
     return jwk.check(kty=kty, is_private=is_private, is_symmetric=is_symmetric)
diff --git a/jwskate/jwk/ec.py b/jwskate/jwk/ec.py
index 5779b74..b379e8b 100644
--- a/jwskate/jwk/ec.py
+++ b/jwskate/jwk/ec.py
@@ -3,7 +3,7 @@
 from __future__ import annotations
 
 import warnings
-from typing import Any, List, Mapping, Optional, Union
+from typing import Any, List, Mapping, Optional, Type, Union
 
 from backports.cached_property import cached_property
 from binapy import BinaPy
@@ -18,6 +18,7 @@
     P_256,
     P_384,
     P_521,
+    BaseECSignatureAlg,
     EcdhEs,
     EcdhEs_A128KW,
     EcdhEs_A192KW,
@@ -59,11 +60,11 @@ class ECJwk(Jwk):
         curve.name: curve for curve in [P_256, P_384, P_521, secp256k1]
     }
 
-    SIGNATURE_ALGORITHMS = {
+    SIGNATURE_ALGORITHMS: Mapping[str, Type[BaseECSignatureAlg]] = {
         sigalg.name: sigalg for sigalg in [ES256, ES384, ES512, ES256K]
     }
 
-    KEY_MANAGEMENT_ALGORITHMS = {
+    KEY_MANAGEMENT_ALGORITHMS: Mapping[str, Type[EcdhEs]] = {
         keyalg.name: keyalg
         for keyalg in [EcdhEs, EcdhEs_A128KW, EcdhEs_A192KW, EcdhEs_A256KW]
     }
@@ -88,6 +89,7 @@ def get_curve(cls, crv: str) -> EllipticCurve:
 
         Raises:
             UnsupportedEllipticCurve: if the curve identifier is not supported
+
         """
         curve = cls.CURVES.get(crv)
         if curve is None:
@@ -100,6 +102,7 @@ def curve(self) -> EllipticCurve:
 
         Returns:
             the EllipticCurve instance
+
         """
         return self.get_curve(self.crv)
 
@@ -115,6 +118,7 @@ def public(cls, crv: str, x: int, y: int, **params: str) -> "ECJwk":
 
         Returns:
           an ECJwk initialized with the supplied parameters
+
         """
         coord_size = cls.get_curve(crv).coordinate_size
         return cls(
@@ -140,6 +144,7 @@ def private(cls, crv: str, x: int, y: int, d: int, **params: Any) -> "ECJwk":
 
         Returns:
           an ECJWk initialized with the supplied parameters
+
         """
         coord_size = cls.get_curve(crv).coordinate_size
         return cls(
@@ -159,6 +164,7 @@ def coordinate_size(self) -> int:
 
         Returns:
           32, 48, or 66 (bits)
+
         """
         return self.curve.coordinate_size
 
@@ -172,6 +178,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> ECJwk:
 
         Returns:
             an ECJwk initialized from the provided `cryptography` key
+
         """
         parameters = EllipticCurve.get_jwk_parameters(cryptography_key)
         return cls(parameters)
@@ -186,6 +193,7 @@ def _to_cryptography_key(
 
         Returns:
             an EllipticCurvePublicKey or EllipticCurvePrivateKey
+
         """
         if self.is_private:
             return asymmetric.ec.EllipticCurvePrivateNumbers(
@@ -219,6 +227,7 @@ def generate(
 
         Raises:
             UnsupportedEllipticCurve: if the provided curve identifier is not supported.
+
         """
         if crv is None and alg is None:
             warnings.warn(
@@ -256,6 +265,7 @@ def x_coordinate(self) -> int:
 
         Returns:
          the x coordinate (from parameter `x`)
+
         """
         return BinaPy(self.x).decode_from("b64u").to_int()
 
@@ -265,6 +275,7 @@ def y_coordinate(self) -> int:
 
         Returns:
             the y coordinate (from parameter `y`)
+
         """
         return BinaPy(self.y).decode_from("b64u").to_int()
 
@@ -274,6 +285,7 @@ def ecc_private_key(self) -> int:
 
         Returns:
              the ECC private key (from parameter `d`)
+
         """
         return BinaPy(self.d).decode_from("b64u").to_int()
 
@@ -282,6 +294,7 @@ def supported_signing_algorithms(self) -> List[str]:
 
         Returns:
             a list of supported algorithms identifiers
+
         """
         return [
             name
@@ -294,6 +307,7 @@ def supported_key_management_algorithms(self) -> List[str]:
 
         Returns:
              a list of supported algorithms identifiers
+
         """
         return list(self.KEY_MANAGEMENT_ALGORITHMS)
 
@@ -302,5 +316,6 @@ def supported_encryption_algorithms(self) -> List[str]:
 
         Returns:
              a list of supported algorithms identifiers
+
         """
         return list(self.ENCRYPTION_ALGORITHMS)
diff --git a/jwskate/jwk/jwks.py b/jwskate/jwk/jwks.py
index 6c653d2..65d7dff 100644
--- a/jwskate/jwk/jwks.py
+++ b/jwskate/jwk/jwks.py
@@ -21,6 +21,7 @@ class JwkSet(BaseJsonDict):
     Args:
         jwks: a dict, containing the JwkSet, parsed as a JSON object.
         keys: a list of `Jwk`, that will be added to this JwkSet
+
     """
 
     def __init__(
@@ -49,6 +50,7 @@ def jwks(self) -> List[Jwk]:
 
         Returns:
             a list of `Jwk`
+
         """
         return self.get("keys", [])
 
@@ -63,6 +65,7 @@ def get_jwk_by_kid(self, kid: str) -> Jwk:
 
         Raises:
             KeyError: if no key matches
+
         """
         jwk = next(filter(lambda jwk: jwk.get("kid") == kid, self.jwks), None)
         if isinstance(jwk, Jwk):
@@ -74,6 +77,7 @@ def __len__(self) -> int:
 
         Returns:
             the number of keys
+
         """
         return len(self.jwks)
 
@@ -92,6 +96,7 @@ def add_jwk(
 
         Returns:
           the kid from the added Jwk (it may be generated if no kid is provided)
+
         """
         jwk = to_jwk(jwk)
 
@@ -110,13 +115,14 @@ def add_jwk(
         return kid
 
     def remove_jwk(self, kid: str) -> None:
-        """Removes a Jwk from this JwkSet, based on a `kid`.
+        """Remove a Jwk from this JwkSet, based on a `kid`.
 
         Args:
           kid: the `kid` from the key to be removed.
 
         Raises:
             KeyError: if no key matches
+
         """
         try:
             jwk = self.get_jwk_by_kid(kid)
@@ -130,6 +136,7 @@ def is_private(self) -> bool:
 
         Returns:
             `True` if this JwkSet contains at least one private key
+
         """
         return any(key.is_private for key in self.jwks)
 
@@ -138,6 +145,7 @@ def public_jwks(self) -> "JwkSet":
 
         Returns:
             a public JwkSet
+
         """
         return JwkSet(keys=(key.public_jwk() for key in self.jwks))
 
@@ -167,6 +175,7 @@ def verify(
 
         Returns:
           `True` if the signature validates with any of the tried keys, `False` otherwise
+
         """
         if not alg and not algs:
             raise ValueError("Please provide either 'alg' or 'algs' parameter")
@@ -176,6 +185,7 @@ def verify(
             jwk = self.get_jwk_by_kid(kid)
             return jwk.verify(data, signature, alg=alg, algs=algs)
 
+        # otherwise, try all keys which support the given alg(s)
         if algs is None:
             if alg is not None:
                 algs = (alg,)
@@ -201,6 +211,7 @@ def verification_keys(self) -> List[Jwk]:
 
         Returns:
             a list of `Jwk` that are usable for signature verification
+
         """
         return [
             jwk
@@ -218,6 +229,7 @@ def encryption_keys(self) -> List[Jwk]:
 
         Returns:
             a list of `Jwk` that are suitable for encryption
+
         """
         return [
             jwk
diff --git a/jwskate/jwk/oct.py b/jwskate/jwk/oct.py
index b110b0b..d0f11d5 100644
--- a/jwskate/jwk/oct.py
+++ b/jwskate/jwk/oct.py
@@ -75,10 +75,11 @@ def is_symmetric(self) -> bool:
         return True
 
     def public_jwk(self) -> Jwk:
-        """This always raises a ValueError since SymmetricKeys are always private.
+        """Raise an error since Symmetric Keys are always private.
 
         Raises:
             ValueError: symmetric keys are always private, it makes no sense to use them as public keys
+
         """
         raise ValueError("Symmetric keys don't have a public key")
 
@@ -94,6 +95,7 @@ def from_bytes(cls, k: Union[bytes, str], **params: Any) -> SymmetricJwk:
 
         Returns:
           the resulting SymmetricJwk
+
         """
         return cls(dict(kty="oct", k=BinaPy(k).to("b64u").ascii(), **params))
 
@@ -109,6 +111,7 @@ def from_cryptography_key(
 
         Returns:
             the resulting SymmetricJwk
+
         """
         return cls.from_bytes(cryptography_key, **params)
 
@@ -122,31 +125,11 @@ def generate(cls, key_size: int = 128, **params: Any) -> SymmetricJwk:
 
         Returns:
             a SymmetricJwk with a randomly generated key
+
         """
         key = BinaPy.random_bits(key_size)
         return cls.from_bytes(key, **params)
 
-    @classmethod
-    def generate_for_alg(cls, alg: str, **params: Any) -> SymmetricJwk:
-        """Generate a SymmetricJwk that is suitable for use with the given alg.
-
-        Args:
-          alg: the algorithm identifier
-          **params: additional members to include in the Jwk
-
-        Returns:
-            the generated `Jwk`
-
-        Raises:
-            UnsupportedAlg: if the provided `alg` is not supported
-        """
-        alg_class = cls._get_alg_class(alg)
-        if issubclass(alg_class, BaseHMACSigAlg):
-            return cls.generate(key_size=alg_class.min_key_size, alg=alg, **params)
-        elif issubclass(alg_class, (BaseAESEncryptionAlg, BaseAesKeyWrap)):
-            return cls.generate(key_size=alg_class.key_size, alg=alg, **params)
-        return cls.generate(alg=alg, **params)
-
     def thumbprint(self, hashalg: str = "SHA256") -> str:
         """Return the key thumbprint as specified by RFC 7638.
 
@@ -157,6 +140,7 @@ def thumbprint(self, hashalg: str = "SHA256") -> str:
 
         Returns:
             the calculated thumbprint
+
         """
         return (
             BinaPy.serialize_to("json", {"k": self.k, "kty": self.kty})
@@ -166,12 +150,13 @@ def thumbprint(self, hashalg: str = "SHA256") -> str:
         )
 
     def _to_cryptography_key(self) -> BinaPy:
-        """Converts this Jwk into a key usable with `cryptography`.
+        """Convert this Jwk into a key usable with `cryptography`.
 
         For SymmetricJwk instances, those are just `bytes` values.
 
         Returns:
             the raw private key, as `bytes`
+
         """
         return BinaPy(self.k).decode_from("b64u")
 
@@ -181,6 +166,7 @@ def key(self) -> BinaPy:
 
         Returns:
              the key from the `k` parameter, base64u-decoded
+
         """
         return self.cryptography_key  # type: ignore
 
@@ -207,6 +193,7 @@ def encrypt(
 
         Returns:
             a (ciphertext, authentication_tag, iv) tuple
+
         """
         wrapper = self.encryption_wrapper(alg)
         if iv is None:
@@ -221,6 +208,7 @@ def key_size(self) -> int:
 
         Returns:
             the key size in bits
+
         """
         return len(self.key) * 8
 
@@ -244,6 +232,7 @@ def decrypt(
 
         Returns:
             the decrypted clear-text
+
         """
         aad = b"" if aad is None else aad
         if not isinstance(aad, bytes):
@@ -265,6 +254,7 @@ def supported_key_management_algorithms(self) -> List[str]:
 
         Returns:
             a list of supported algorithms identifiers
+
         """
         return [
             name
@@ -277,6 +267,7 @@ def supported_encryption_algorithms(self) -> List[str]:
 
         Returns:
             a list of supported algorithms identifiers
+
         """
         return [
             name
@@ -294,5 +285,6 @@ def to_pem(self, password: Union[bytes, str, None] = None) -> bytes:
 
         Raises:
             TypeError: always
+
         """
         raise TypeError("Symmetric keys are not serializable to PEM.")
diff --git a/jwskate/jwk/okp.py b/jwskate/jwk/okp.py
index e19dd41..3eb385a 100644
--- a/jwskate/jwk/okp.py
+++ b/jwskate/jwk/okp.py
@@ -1,6 +1,7 @@
 """This module implements JWK representing Octet Key Pairs from [RFC8037].
 
 [RFC8037]: https://www.rfc-editor.org/rfc/rfc8037.html
+
 """
 
 from __future__ import annotations
@@ -24,6 +25,7 @@
     EdDsa,
     OKPCurve,
 )
+from jwskate.jwa.okp import PrivateKeyProtocol, PublicKeyProtocol
 
 from .alg import UnsupportedAlg
 from .base import Jwk, JwkParameter
@@ -94,6 +96,7 @@ def get_curve(cls, crv: str) -> OKPCurve:
 
         Raises:
             UnsupportedOKPCurve: if the curve is not supported
+
         """
         curve = cls.CURVES.get(crv)
         if curve is None:
@@ -106,6 +109,7 @@ def curve(self) -> OKPCurve:
 
         Returns:
             the OKPCurve for this key
+
         """
         return self.get_curve(self.crv)
 
@@ -115,6 +119,7 @@ def public_key(self) -> bytes:
 
         Returns:
             the public key (from param `x`)
+
         """
         return BinaPy(self.x).decode_from("b64u")
 
@@ -124,6 +129,7 @@ def private_key(self) -> bytes:
 
         Returns:
             the private key (from param `d`)
+
         """
         return BinaPy(self.d).decode_from("b64u")
 
@@ -152,6 +158,7 @@ def from_bytes(
 
         Returns:
             the matching OKPJwk
+
         """
         if crv and use:
             if (crv in ("Ed25519", "Ed448") and use != "sig") or (
@@ -216,8 +223,10 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> OKPJwk:
 
         Returns:
             the matching OKPJwk
+
         """
-        if isinstance(cryptography_key, ed25519.Ed25519PrivateKey):
+        crv = OKPCurve.get_curve(cryptography_key)
+        if isinstance(cryptography_key, PrivateKeyProtocol):
             priv = cryptography_key.private_bytes(
                 encoding=serialization.Encoding.Raw,
                 format=serialization.PrivateFormat.Raw,
@@ -228,82 +237,19 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> OKPJwk:
                 format=serialization.PublicFormat.Raw,
             )
             return cls.private(
-                crv="Ed25519",
+                crv=crv.name,
                 x=pub,
                 d=priv,
             )
-        elif isinstance(cryptography_key, ed25519.Ed25519PublicKey):
+        elif isinstance(cryptography_key, PublicKeyProtocol):
             pub = cryptography_key.public_bytes(
                 encoding=serialization.Encoding.Raw,
                 format=serialization.PublicFormat.Raw,
             )
             return cls.public(
-                crv="Ed25519",
-                x=pub,
-            )
-        elif isinstance(cryptography_key, ed448.Ed448PrivateKey):
-            priv = cryptography_key.private_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PrivateFormat.Raw,
-                encryption_algorithm=serialization.NoEncryption(),
-            )
-            pub = cryptography_key.public_key().public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
-            )
-            return cls.private(
-                crv="Ed448",
+                crv=crv.name,
                 x=pub,
-                d=priv,
-            )
-        elif isinstance(cryptography_key, ed448.Ed448PublicKey):
-            pub = cryptography_key.public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
             )
-            return cls.public(crv="Ed448", x=pub)
-        elif isinstance(cryptography_key, x25519.X25519PrivateKey):
-            priv = cryptography_key.private_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PrivateFormat.Raw,
-                encryption_algorithm=serialization.NoEncryption(),
-            )
-            pub = cryptography_key.public_key().public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
-            )
-            return cls.private(
-                crv="X25519",
-                x=pub,
-                d=priv,
-            )
-        elif isinstance(cryptography_key, x25519.X25519PublicKey):
-            pub = cryptography_key.public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
-            )
-            return cls.public(crv="X25519", x=pub)
-        elif isinstance(cryptography_key, x448.X448PrivateKey):
-            priv = cryptography_key.private_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PrivateFormat.Raw,
-                encryption_algorithm=serialization.NoEncryption(),
-            )
-            pub = cryptography_key.public_key().public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
-            )
-            return cls.private(
-                crv="X448",
-                x=pub,
-                d=priv,
-            )
-        elif isinstance(cryptography_key, x448.X448PublicKey):
-            pub = cryptography_key.public_bytes(
-                encoding=serialization.Encoding.Raw,
-                format=serialization.PublicFormat.Raw,
-            )
-            return cls.public(crv="X448", x=pub)
         else:
             raise TypeError(
                 "Unsupported key type for OKP. Supported key types are: "
@@ -324,6 +270,7 @@ def _to_cryptography_key(self) -> Any:
 
         Raises:
             UnsupportedOKPCurve: if this Jwk curve is not supported.
+
         """
         if self.curve.name == "Ed25519":
             if self.is_private:
@@ -359,6 +306,7 @@ def public(cls, crv: str, x: bytes, **params: Any) -> OKPJwk:
 
         Returns:
             the resulting OKPJwk
+
         """
         return cls(dict(kty="OKP", crv=crv, x=BinaPy(x).to("b64u").ascii(), **params))
 
@@ -374,6 +322,7 @@ def private(cls, crv: str, x: bytes, d: bytes, **params: Any) -> OKPJwk:
 
         Returns:
             the resulting OKPJwk
+
         """
         return cls(
             dict(
@@ -402,6 +351,7 @@ def generate(
 
         Returns:
             the resulting OKPJwk
+
         """
         if crv:
             curve = cls.get_curve(crv)
@@ -426,6 +376,7 @@ def use(self) -> Optional[str]:
         """Return the key use.
 
         For OKP keys, this can be directly deduced from the curve.
+
         """
         if self.curve in (Ed25519, Ed448):
             return "sig"
diff --git a/jwskate/jwk/rsa.py b/jwskate/jwk/rsa.py
index 0854810..4506fa1 100644
--- a/jwskate/jwk/rsa.py
+++ b/jwskate/jwk/rsa.py
@@ -93,6 +93,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> RSAJwk:
 
         Raises:
             TypeError: if the given key type is not supported
+
         """
         if isinstance(cryptography_key, rsa.RSAPrivateKey):
             priv = cryptography_key.private_numbers()  # type: ignore[attr-defined]
@@ -121,6 +122,7 @@ def _to_cryptography_key(self) -> Union[rsa.RSAPrivateKey, rsa.RSAPublicKey]:
 
         Returns:
             a cryptography RSAPrivateKey or RSAPublicKey
+
         """
         if self.is_private:
             return rsa.RSAPrivateNumbers(
@@ -146,6 +148,7 @@ def public(cls, n: int, e: int, **params: Any) -> RSAJwk:
 
         Returns:
           a RsaJwk initialized from the provided parameters
+
         """
         return cls(
             dict(
@@ -184,6 +187,7 @@ def private(
 
         Returns:
             a RSAJwk initialized from the given parameters
+
         """
         return cls(
             dict(
@@ -210,6 +214,7 @@ def generate(cls, key_size: int = 4096, **params: Any) -> RSAJwk:
 
         Returns:
           a generated RSAJwk
+
         """
         private_key = rsa.generate_private_key(65537, key_size=key_size)
         pn = private_key.private_numbers()
@@ -231,6 +236,7 @@ def modulus(self) -> int:
 
         Returns:
             the key modulus (from parameter `n`)
+
         """
         return BinaPy(self.n).decode_from("b64u").to_int()
 
@@ -240,6 +246,7 @@ def exponent(self) -> int:
 
         Returns:
             the key exponent (from parameter `e`)
+
         """
         return BinaPy(self.e).decode_from("b64u").to_int()
 
@@ -249,6 +256,7 @@ def private_exponent(self) -> int:
 
         Returns:
             the key private exponent (from parameter `d`)
+
         """
         return BinaPy(self.d).decode_from("b64u").to_int()
 
@@ -258,6 +266,7 @@ def first_prime_factor(self) -> int:
 
         Returns:
             the first prime factor (from parameter `p`)
+
         """
         return self.prime_factors[0]
 
@@ -267,6 +276,7 @@ def second_prime_factor(self) -> int:
 
         Returns:
             the second prime factor (from parameter `q`)
+
         """
         return self.prime_factors[1]
 
@@ -289,6 +299,7 @@ def first_factor_crt_exponent(self) -> int:
 
         Returns:
             the first factor CRT coefficient (from parameter `dp`)
+
         """
         if "dp" in self:
             return BinaPy(self.dp).decode_from("b64u").to_int()
@@ -300,6 +311,7 @@ def second_factor_crt_exponent(self) -> int:
 
         Returns:
             the second factor CRT coefficient (from parameter `dq`)
+
         """
         if "dq" in self:
             return BinaPy(self.dq).decode_from("b64u").to_int()
@@ -311,6 +323,7 @@ def first_crt_coefficient(self) -> int:
 
         Returns:
             the first CRT coefficient (from parameter `qi`)
+
         """
         if "qi" in self:
             return BinaPy(self.qi).decode_from("b64u").to_int()
@@ -322,6 +335,7 @@ def key_size(self) -> int:
 
         Returns:
             the key size
+
         """
         return len(BinaPy(self.n).decode_from("b64u")) * 8
 
@@ -334,6 +348,7 @@ def with_optional_private_parameters(self) -> RSAJwk:
         - dp: first factor Chinese Remainder Theorem exponent
         - dq: second factor Chinese Remainder Theorem exponent
         - qi: first Chinese Remainder Theorem coefficient
+
         """
         if not self.is_private:
             raise ValueError(
diff --git a/jwskate/jws/compact.py b/jwskate/jws/compact.py
index abcb7e6..d340ea5 100644
--- a/jwskate/jws/compact.py
+++ b/jwskate/jws/compact.py
@@ -25,6 +25,7 @@ class JwsCompact(BaseCompactToken):
 
     Args:
         value: the JWS token value
+
     """
 
     def __init__(self, value: Union[bytes, str]):
@@ -76,6 +77,7 @@ def sign(
 
         Returns:
           the resulting token
+
         """
         jwk = to_jwk(jwk)
 
@@ -97,7 +99,7 @@ def from_parts(
         signed_part: Union[bytes, SupportsBytes, str],
         signature: Union[bytes, SupportsBytes, str],
     ) -> JwsCompact:
-        """Constructs a JWS token based on its signed part and signature values.
+        """Construct a JWS token based on its signed part and signature values.
 
         Signed part is the concatenation of the header and payload, both encoded in Base64-Url, and joined by a dot.
 
@@ -107,6 +109,7 @@ def from_parts(
 
         Returns:
             the resulting token
+
         """
         if isinstance(signed_part, str):
             signed_part = signed_part.encode("ascii")
@@ -126,6 +129,7 @@ def signed_part(self) -> bytes:
 
         Returns:
             the signed part
+
         """
         return b".".join(self.value.split(b".", 2)[:2])
 
@@ -145,6 +149,7 @@ def verify_signature(
 
         Returns:
          `True` if the signature matches, `False` otherwise
+
         """
         jwk = to_jwk(jwk)
         return jwk.verify(self.signed_part, self.signature, alg=alg, algs=algs)
@@ -157,6 +162,7 @@ def flat_json(self, unprotected_header: Any = None) -> JwsJsonFlat:
 
         Returns:
             the resulting token
+
         """
         from .json import JwsJsonFlat
 
@@ -181,6 +187,13 @@ def general_json(self, unprotected_header: Any = None) -> JwsJsonGeneral:
 
         Returns:
             the resulting token
+
         """
         jws = self.flat_json(unprotected_header)
         return jws.generalize()
+
+    def jws_signature(self, unprotected_header: Any = None) -> JwsSignature:
+        """Return a JwsSignature based on this JWS Compact token."""
+        return JwsSignature.from_parts(
+            protected=self.headers, signature=self.signature, header=unprotected_header
+        )
diff --git a/jwskate/jws/json.py b/jwskate/jws/json.py
index 53b4228..1f36783 100644
--- a/jwskate/jws/json.py
+++ b/jwskate/jws/json.py
@@ -23,6 +23,7 @@ def payload(self) -> bytes:
 
         Returns:
             The raw JWS payload.
+
         """
         payload = self.get("payload")
         if payload is None:
@@ -35,6 +36,7 @@ def jws_signature(self) -> JwsSignature:
 
         Returns:
             The JWS signature.
+
         """
         content = {
             "protected": self["protected"],
@@ -67,6 +69,7 @@ def sign(
 
         Returns:
             The JWS with the payload, signature, header and extra claims.
+
         """
         signature = super().sign(
             payload, jwk, alg, extra_protected_headers, header, **kwargs
@@ -79,6 +82,7 @@ def generalize(self) -> JwsJsonGeneral:
 
         Returns:
             A JwsJsonGeneral with the same payload and signature.
+
         """
         content = self.copy()
         protected = content.pop("protected")
@@ -91,12 +95,13 @@ def generalize(self) -> JwsJsonGeneral:
         return JwsJsonGeneral(content)
 
     def signed_part(self) -> bytes:
-        """The signed part from this JWS, as bytes.
+        """Return the signed part from this JWS, as bytes.
 
         This is a concatenation of the protected header and the payload, separated by a dot (`.`).
 
         Returns:
             The signed data part.
+
         """
         return JwsSignature.assemble_signed_part(self.protected, self.payload)
 
@@ -105,6 +110,7 @@ def compact(self) -> JwsCompact:
 
         Returns:
             A `JwsCompact` with the same payload and signature.
+
         """
         return JwsCompact.from_parts(self.signed_part(), self.signature)
 
@@ -124,6 +130,7 @@ def verify_signature(
 
         Returns:
             `True` if the signature is verified, `False` otherwise.
+
         """
         return self.jws_signature.verify(self.payload, jwk, alg=alg, algs=algs)
 
@@ -137,6 +144,7 @@ def payload(self) -> bytes:
 
         Returns:
             The signed data.
+
         """
         payload = self.get("payload")
         if payload is None:
@@ -179,6 +187,7 @@ def sign(
 
         Returns:
             A JwsJsonGeneral with the generated signatures.
+
         """
         jws = cls({"payload": BinaPy(payload).to("b64u").ascii()})
         for parameters in signature_parameters:
@@ -191,6 +200,7 @@ def signatures(self) -> List[JwsSignature]:
 
         Returns:
             The list of signatures from this JWS.
+
         """
         signatures = self.get("signatures")
         if signatures is None:
@@ -214,6 +224,7 @@ def add_signature(
 
         Returns:
             the same JWS with the new signature included.
+
         """
         self.setdefault("signatures", [])
         self["signatures"].append(
@@ -239,6 +250,7 @@ def signed_part(
 
         Returns:
             The raw signed part from the chosen signature.
+
         """
         signature = signature_chooser(self.signatures)
         return JwsSignature.assemble_signed_part(signature.protected, self.payload)
@@ -256,8 +268,13 @@ def compact(
 
         Returns:
             A JwsCompact with the payload and the chosen signature from this JWS.
+
         """
-        return JwsCompact.from_parts(self.signed_part(signature_chooser), self.payload)
+        signature = signature_chooser(self.signatures)
+        return JwsCompact.from_parts(
+            JwsSignature.assemble_signed_part(signature.protected, self.payload),
+            signature.signature,
+        )
 
     def flatten(
         self,
@@ -272,6 +289,7 @@ def flatten(
 
         Returns:
             A JwsJsonFlat with the payload and the chosen signature from this JWS.
+
         """
         signature = signature_chooser(self.signatures)
         return JwsJsonFlat.from_parts(
@@ -298,6 +316,7 @@ def verify_signature(
 
         Returns:
             `True` if any of the signature verifies with the given key, `False` otherwise.
+
         """
         for signature in self.signatures:
             if signature.verify(self.payload, jwk, alg=alg, algs=algs):
diff --git a/jwskate/jws/signature.py b/jwskate/jws/signature.py
index 66dbc3e..3bef0d8 100644
--- a/jwskate/jws/signature.py
+++ b/jwskate/jws/signature.py
@@ -22,6 +22,7 @@ class JwsSignature(BaseJsonDict):
      - a signature value (as raw data)
      - an unprotected header (as arbitrary JSON data)
      - optional extra JSON attributes
+
     """
 
     @classmethod
@@ -42,6 +43,7 @@ def from_parts(
 
         Returns:
             A `JwsSignature` based on the provided parts.
+
         """
         content = dict(
             kwargs,
@@ -61,6 +63,7 @@ def protected(self) -> Dict[str, Any]:
 
         Raises:
             AttributeError: if this signature doesn't have protected headers.
+
         """
         protected = self.get("protected")
         if protected is None:
@@ -73,6 +76,7 @@ def header(self) -> Any:
 
         Returns:
             The unprotected header
+
         """
         return self.get("header")
 
@@ -85,6 +89,7 @@ def signature(self) -> bytes:
 
         Raises:
             AttributeError: if no 'signature' member is present
+
         """
         signature = self.get("signature")
         if signature is None:
@@ -113,6 +118,7 @@ def sign(
 
         Returns:
             The generated signature.
+
         """
         jwk = to_jwk(jwk)
 
@@ -142,6 +148,7 @@ def assemble_signed_part(
 
         Returns:
             the raw data to sign
+
         """
         return b".".join(
             (
@@ -168,6 +175,7 @@ def verify(
 
         Returns:
             `True` if the signature is verifier, `False` otherwise
+
         """
         jwk = to_jwk(jwk)
         signed_part = self.assemble_signed_part(self.protected, payload)
diff --git a/jwskate/jwt/base.py b/jwskate/jwt/base.py
index 8cede98..638e193 100644
--- a/jwskate/jwt/base.py
+++ b/jwskate/jwt/base.py
@@ -29,6 +29,7 @@ def __new__(cls, value: Union[bytes, str]):  # type: ignore
 
         Args:
             value: the token value
+
         """
         if not isinstance(value, bytes):
             value = value.encode("ascii")
@@ -65,6 +66,7 @@ def sign(
 
         Returns:
           the resulting token
+
         """
         from .signed import SignedJwt
 
@@ -100,6 +102,7 @@ def unprotected(
 
         Returns:
             the resulting token
+
         """
         from .signed import SignedJwt
 
@@ -140,6 +143,7 @@ def sign_and_encrypt(
 
         Returns:
           the resulting JWE token, with the signed JWT as payload
+
         """
         enc_extra_headers = enc_extra_headers or {}
         enc_extra_headers.setdefault("cty", "JWT")
@@ -156,19 +160,20 @@ def sign_and_encrypt(
     def decrypt_nested_jwt(
         cls, jwe: Union[str, JweCompact], jwk: Union[Jwk, Dict[str, Any]]
     ) -> Jwt:
-        """Convenience method to decrypt a nested JWT.
+        """Decrypt a JWE that contains a nested JWT.
 
-        It will return a [Jwt] instance.
+        It will return a [Jwt] instance for the inner JWT.
 
         Args:
             jwe: the JWE containing a nested Token
             jwk: the decryption key
 
         Returns:
-            the inner token
+            the inner JWT
 
         Raises:
             InvalidJwt: if the inner JWT is not valid
+
         """
         if not isinstance(jwe, JweCompact):
             jwe = JweCompact(jwe)
@@ -201,6 +206,7 @@ def decrypt_and_verify(
         Raises:
             InvalidJwt: if the JWT is not valid
             InvalidSignature: if the nested JWT signature is not valid
+
         """
         from .signed import InvalidSignature, SignedJwt
 
@@ -228,6 +234,7 @@ def timestamp(cls, delta_seconds: int = 0) -> int:
 
         Returns:
             An integer timestamp
+
         """
         return int(datetime.now(timezone.utc).timestamp()) + delta_seconds
 
@@ -242,5 +249,6 @@ def timestamp_to_datetime(cls, timestamp: int) -> datetime:
 
         Returns:
             the corresponding `datetime` in UTC timezone
+
         """
         return datetime.fromtimestamp(timestamp, tz=timezone.utc)
diff --git a/jwskate/jwt/signed.py b/jwskate/jwt/signed.py
index 50dbd8b..c54c7e5 100644
--- a/jwskate/jwt/signed.py
+++ b/jwskate/jwt/signed.py
@@ -32,6 +32,7 @@ class SignedJwt(Jwt):
 
     Args:
         value: the token value.
+
     """
 
     def __init__(self, value: Union[bytes, str]) -> None:
@@ -73,6 +74,7 @@ def signed_part(self) -> bytes:
 
         Returns:
           the signed part as bytes
+
         """
         return b".".join(self.value.split(b".", 2)[:2])
 
@@ -91,6 +93,7 @@ def verify_signature(
 
         Returns:
             `True` if the token signature is verified, `False` otherwise
+
         """
         jwk = to_jwk(jwk)
 
@@ -106,6 +109,7 @@ def is_expired(self, leeway: int = 0) -> Optional[bool]:
 
         Returns:
             `True` if the token is expired, `False` if it's not, `None` if there is no `exp` claim.
+
         """
         exp = self.expires_at
         if exp is None:
@@ -121,6 +125,7 @@ def expires_at(self) -> Optional[datetime]:
 
         Raises:
             AttributeError: if the `exp` claim cannot be parsed to a date
+
         """
         exp = self.get_claim("exp")
         if not exp:
@@ -140,6 +145,7 @@ def issued_at(self) -> Optional[datetime]:
 
         Raises:
             AttributeError: if the `iss` claim cannot be parsed to a date
+
         """
         iat = self.get_claim("iat")
         if not iat:
@@ -159,6 +165,7 @@ def not_before(self) -> Optional[datetime]:
 
         Raises:
             AttributeError: if the `nbf` claim cannot be parsed to a date
+
         """
         nbf = self.get_claim("nbf")
         if not nbf:
@@ -178,6 +185,7 @@ def issuer(self) -> Optional[str]:
 
         Raises:
             AttributeError: if the `ìss` claim value is not a string
+
         """
         iss = self.get_claim("iss")
         if iss is None or isinstance(iss, str):
@@ -195,6 +203,7 @@ def audiences(self) -> List[str]:
 
         Raises:
             AttributeError: if the audience is an unexpected type
+
         """
         aud = self.get_claim("aud")
         if aud is None:
@@ -214,6 +223,7 @@ def subject(self) -> Optional[str]:
 
         Raises:
             AttributeError: if the `sub` value is not a string
+
         """
         sub = self.get_claim("sub")
         if sub is None or isinstance(sub, str):
@@ -229,6 +239,7 @@ def jwt_token_id(self) -> Optional[str]:
 
         Raises:
           AttributeError: if the `jti` value is not a string
+
         """
         jti = self.get_claim("jti")
         if jti is None or isinstance(jti, str):
@@ -244,6 +255,7 @@ def get_claim(self, key: str, default: Any = None) -> Any:
 
         Returns:
           the claim value if found, or `default` if not found
+
         """
         return self.claims.get(key, default)
 
@@ -255,6 +267,7 @@ def __getitem__(self, item: str) -> Any:
 
         Returns:
          the claim value
+
         """
         value = self.get_claim(item)
         if value is None:
@@ -269,6 +282,7 @@ def __getattr__(self, item: str) -> Any:
 
         Returns:
             the claim value
+
         """
         value = self.get_claim(item)
         if value is None:
@@ -280,6 +294,7 @@ def __str__(self) -> str:
 
         Returns:
             the serialized token value.
+
         """
         return self.value.decode()
 
@@ -288,6 +303,7 @@ def __bytes__(self) -> bytes:
 
         Returns:
             the serialized token value.
+
         """
         return self.value
 
@@ -326,6 +342,7 @@ def validate(
           InvalidSignature: if the signature is not valid
           InvalidClaim: if a claim doesn't validate
           ExpiredJwt: if the expiration date is passed
+
         """
         if not self.verify_signature(jwk, alg, algs):
             raise InvalidSignature("Signature is not valid.")
diff --git a/jwskate/token.py b/jwskate/token.py
index 4f74f0b..45459c0 100644
--- a/jwskate/token.py
+++ b/jwskate/token.py
@@ -14,6 +14,7 @@ class BaseCompactToken:
         value: the string or bytes representation of this JWS/JWE/JWT
         max_size: if the JWT length is larger than this value, raise a `ValueError`.
             This is to avoid JSON deserialization vulnerabilities.
+
     """
 
     def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024):
@@ -42,6 +43,7 @@ def __eq__(self, other: Any) -> bool:
 
         Returns:
             `True` if the other token has the same representation, `False` otherwise
+
         """
         if isinstance(other, BaseCompactToken):
             return self.value == other.value
@@ -59,6 +61,7 @@ def get_header(self, name: str) -> Any:
 
         Returns:
             the header value
+
         """
         return self.headers.get(name)
 
@@ -70,6 +73,7 @@ def alg(self) -> str:
             the `alg` value
         Raises:
             AttributeError: if the `alg` header value is not a string
+
         """
         alg = self.get_header("alg")
         if alg is None or not isinstance(alg, str):  # pragma: no branch
@@ -84,6 +88,7 @@ def kid(self) -> str:
             the `kid` value
         Raises:
             AttributeError: if the `kid` header value is not a string
+
         """
         kid = self.get_header("kid")
         if kid is None or not isinstance(kid, str):
@@ -98,6 +103,7 @@ def typ(self) -> str:
             the `typ` value
         Raises:
             AttributeError: if the `typ` header value is not a string
+
         """
         typ = self.get_header("typ")
         if typ is None or not isinstance(typ, str):  # pragma: no branch
@@ -112,6 +118,7 @@ def cty(self) -> str:
             the `typ` value
         Raises:
             AttributeError: if the `typ` header value is not a string
+
         """
         cty = self.get_header("cty")
         if cty is None or not isinstance(cty, str):  # pragma: no branch
@@ -119,7 +126,7 @@ def cty(self) -> str:
         return cty
 
     def __repr__(self) -> str:
-        """Returns the `str` representation of this token."""
+        """Return the `str` representation of this token."""
         return self.value.decode()
 
     def __bytes__(self) -> bytes:
@@ -142,6 +149,7 @@ def from_json(cls: Type[D], j: str) -> D:
 
         Returns:
             the resulting object
+
         """
         return cls(json.loads(j))
 
@@ -154,5 +162,6 @@ def to_json(self, *args: Any, **kwargs: Any) -> str:
 
         Returns:
             a JSON representation of the current object
+
         """
         return json.dumps(self, *args, **kwargs)
diff --git a/poetry.lock b/poetry.lock
index e6a0ff1..d0aa475 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,16 +1,17 @@
 [[package]]
 name = "attrs"
-version = "22.1.0"
+version = "22.2.0"
 description = "Classes Without Boilerplate"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.extras]
-dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
-docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
-tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
-tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
+dev = ["attrs[docs,tests]"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
+tests = ["attrs[tests-no-zope]", "zope.interface"]
+tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
 
 [[package]]
 name = "backports-cached-property"
@@ -33,7 +34,7 @@ typing-extensions = ">=4.3.0,<5.0.0"
 
 [[package]]
 name = "black"
-version = "22.10.0"
+version = "22.12.0"
 description = "The uncompromising code formatter."
 category = "dev"
 optional = false
@@ -64,7 +65,7 @@ python-versions = "*"
 
 [[package]]
 name = "certifi"
-version = "2022.9.24"
+version = "2022.12.7"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "dev"
 optional = false
@@ -122,7 +123,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
 
 [[package]]
 name = "coverage"
-version = "6.5.0"
+version = "7.0.4"
 description = "Code coverage measurement for Python"
 category = "dev"
 optional = false
@@ -136,7 +137,7 @@ toml = ["tomli"]
 
 [[package]]
 name = "cryptography"
-version = "38.0.3"
+version = "39.0.0"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 category = "main"
 optional = false
@@ -146,9 +147,9 @@ python-versions = ">=3.6"
 cffi = ">=1.12"
 
 [package.extras]
-docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"]
 docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
-pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+pep8test = ["black", "ruff"]
 sdist = ["setuptools-rust (>=0.11.4)"]
 ssh = ["bcrypt (>=3.1.5)"]
 test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
@@ -177,7 +178,7 @@ python-versions = "*"
 
 [[package]]
 name = "exceptiongroup"
-version = "1.0.1"
+version = "1.1.0"
 description = "Backport of PEP 654 (exception groups)"
 category = "dev"
 optional = false
@@ -188,15 +189,15 @@ test = ["pytest (>=6)"]
 
 [[package]]
 name = "filelock"
-version = "3.8.0"
+version = "3.9.0"
 description = "A platform independent file lock."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.extras]
-docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
-testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
+testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
 
 [[package]]
 name = "freezegun"
@@ -225,7 +226,7 @@ dev = ["flake8", "markdown", "twine", "wheel"]
 
 [[package]]
 name = "griffe"
-version = "0.23.0"
+version = "0.25.3"
 description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
 category = "dev"
 optional = false
@@ -233,13 +234,14 @@ python-versions = ">=3.7"
 
 [package.dependencies]
 cached-property = {version = "*", markers = "python_version < \"3.8\""}
+colorama = ">=0.4"
 
 [package.extras]
 async = ["aiofiles (>=0.7,<1.0)"]
 
 [[package]]
 name = "identify"
-version = "2.5.8"
+version = "2.5.12"
 description = "File identification library for Python"
 category = "dev"
 optional = false
@@ -258,7 +260,7 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "importlib-metadata"
-version = "5.0.0"
+version = "6.0.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
@@ -269,25 +271,25 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
 perf = ["ipython"]
 testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
 
 [[package]]
 name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.7"
 
 [[package]]
 name = "isort"
-version = "5.10.1"
+version = "5.11.4"
 description = "A Python utility / library to sort Python imports."
 category = "dev"
 optional = false
-python-versions = ">=3.6.1,<4.0"
+python-versions = ">=3.7.0"
 
 [package.extras]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
@@ -415,24 +417,26 @@ test = ["mkdocs (==1.4.0)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"]
 
 [[package]]
 name = "mkdocs-material"
-version = "8.5.8"
+version = "9.0.3"
 description = "Documentation that simply works"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
-jinja2 = ">=3.0.2"
+colorama = ">=0.4"
+jinja2 = ">=3.0"
 markdown = ">=3.2"
-mkdocs = ">=1.4.0"
+mkdocs = ">=1.4.2"
 mkdocs-material-extensions = ">=1.1"
-pygments = ">=2.12"
-pymdown-extensions = ">=9.4"
+pygments = ">=2.14"
+pymdown-extensions = ">=9.9"
+regex = ">=2022.4.24"
 requests = ">=2.26"
 
 [[package]]
 name = "mkdocs-material-extensions"
-version = "1.1"
+version = "1.1.1"
 description = "Extension pack for Python Markdown and MkDocs Material."
 category = "dev"
 optional = false
@@ -440,7 +444,7 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "mkdocstrings"
-version = "0.19.0"
+version = "0.19.1"
 description = "Automatic documentation from sources, for MkDocs."
 category = "dev"
 optional = false
@@ -462,19 +466,19 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
 
 [[package]]
 name = "mkdocstrings-python"
-version = "0.7.1"
+version = "0.8.3"
 description = "A Python handler for mkdocstrings."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.dependencies]
-griffe = ">=0.11.1"
+griffe = ">=0.24"
 mkdocstrings = ">=0.19"
 
 [[package]]
 name = "mypy"
-version = "0.990"
+version = "0.991"
 description = "Optional static typing for Python"
 category = "dev"
 optional = false
@@ -513,18 +517,15 @@ setuptools = "*"
 
 [[package]]
 name = "packaging"
-version = "21.3"
+version = "23.0"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+python-versions = ">=3.7"
 
 [[package]]
 name = "pathspec"
-version = "0.10.1"
+version = "0.10.3"
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "dev"
 optional = false
@@ -540,15 +541,18 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "platformdirs"
-version = "2.5.3"
+version = "2.6.2"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
+[package.dependencies]
+typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""}
+
 [package.extras]
-docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
-test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
+docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
 
 [[package]]
 name = "pluggy"
@@ -567,7 +571,7 @@ testing = ["pytest", "pytest-benchmark"]
 
 [[package]]
 name = "pre-commit"
-version = "2.20.0"
+version = "2.21.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 category = "dev"
 optional = false
@@ -579,8 +583,7 @@ identify = ">=1.0.0"
 importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 nodeenv = ">=0.11.1"
 pyyaml = ">=5.1"
-toml = "*"
-virtualenv = ">=20.0.8"
+virtualenv = ">=20.10.0"
 
 [[package]]
 name = "py"
@@ -600,7 +603,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pygments"
-version = "2.13.0"
+version = "2.14.0"
 description = "Pygments is a syntax highlighting package written in Python."
 category = "dev"
 optional = false
@@ -611,7 +614,7 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pymdown-extensions"
-version = "9.8"
+version = "9.9"
 description = "Extension pack for Python Markdown."
 category = "dev"
 optional = false
@@ -620,17 +623,6 @@ python-versions = ">=3.7"
 [package.dependencies]
 markdown = ">=3.2"
 
-[[package]]
-name = "pyparsing"
-version = "3.0.9"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "dev"
-optional = false
-python-versions = ">=3.6.8"
-
-[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
-
 [[package]]
 name = "pytest"
 version = "7.2.0"
@@ -669,7 +661,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
 
 [[package]]
 name = "pytest-mypy"
-version = "0.10.0"
+version = "0.10.3"
 description = "Mypy static type checker plugin for Pytest"
 category = "dev"
 optional = false
@@ -680,8 +672,9 @@ attrs = ">=19.0"
 filelock = ">=3.0"
 mypy = [
     {version = ">=0.500", markers = "python_version < \"3.8\""},
+    {version = ">=0.900", markers = "python_version >= \"3.11\""},
     {version = ">=0.700", markers = "python_version >= \"3.8\" and python_version < \"3.9\""},
-    {version = ">=0.780", markers = "python_version >= \"3.9\""},
+    {version = ">=0.780", markers = "python_version >= \"3.9\" and python_version < \"3.11\""},
 ]
 pytest = [
     {version = ">=4.6", markers = "python_version >= \"3.6\" and python_version < \"3.10\""},
@@ -718,6 +711,14 @@ python-versions = ">=3.6"
 [package.dependencies]
 pyyaml = "*"
 
+[[package]]
+name = "regex"
+version = "2022.10.31"
+description = "Alternative regular expression module, to replace re."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
 [[package]]
 name = "requests"
 version = "2.28.1"
@@ -754,7 +755,7 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes
 
 [[package]]
 name = "setuptools"
-version = "65.5.1"
+version = "65.6.3"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "dev"
 optional = false
@@ -799,7 +800,7 @@ python-versions = ">= 3.7"
 
 [[package]]
 name = "tox"
-version = "3.27.0"
+version = "3.28.0"
 description = "tox is a generic virtualenv management and test command line tool"
 category = "dev"
 optional = false
@@ -854,7 +855,7 @@ python-versions = "*"
 
 [[package]]
 name = "types-cryptography"
-version = "3.3.23.1"
+version = "3.3.23.2"
 description = "Typing stubs for cryptography"
 category = "dev"
 optional = false
@@ -862,7 +863,7 @@ python-versions = "*"
 
 [[package]]
 name = "types-requests"
-version = "2.28.11.2"
+version = "2.28.11.7"
 description = "Typing stubs for requests"
 category = "dev"
 optional = false
@@ -873,7 +874,7 @@ types-urllib3 = "<1.27"
 
 [[package]]
 name = "types-urllib3"
-version = "1.26.25.1"
+version = "1.26.25.4"
 description = "Typing stubs for urllib3"
 category = "dev"
 optional = false
@@ -889,11 +890,11 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "urllib3"
-version = "1.26.12"
+version = "1.26.13"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 
 [package.extras]
 brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
@@ -902,7 +903,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.16.6"
+version = "20.17.1"
 description = "Virtual Python Environment builder"
 category = "dev"
 optional = false
@@ -920,7 +921,7 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7
 
 [[package]]
 name = "watchdog"
-version = "2.1.9"
+version = "2.2.1"
 description = "Filesystem events monitoring"
 category = "dev"
 optional = false
@@ -939,7 +940,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
 name = "zipp"
-version = "3.10.0"
+version = "3.11.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "dev"
 optional = false
@@ -956,12 +957,12 @@ test = []
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.7,<4.0"
-content-hash = "1d6418aebd187b4bb9a506602eeb3da925c3b8214b7d016be99bc44200dc34c5"
+content-hash = "dfddf2fb0f65aa86e140dd7f19c3cc748de6b1f27b0cf2939decc9893e483379"
 
 [metadata.files]
 attrs = [
-    {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
-    {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+    {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
+    {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
 ]
 backports-cached-property = [
     {file = "backports.cached-property-1.0.2.tar.gz", hash = "sha256:9306f9eed6ec55fd156ace6bc1094e2c86fae5fb2bf07b6a9c00745c656e75dd"},
@@ -972,35 +973,26 @@ binapy = [
     {file = "binapy-0.6.0.tar.gz", hash = "sha256:1054b0ef1e6eccd941d4b23167ba4c5b1f5938960750686c421edc71af543fa7"},
 ]
 black = [
-    {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
-    {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
-    {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
-    {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
-    {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
-    {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
-    {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
-    {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
-    {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
-    {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
-    {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
-    {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
-    {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
-    {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
-    {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
-    {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
-    {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
-    {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
-    {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
-    {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
-    {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
+    {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"},
+    {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"},
+    {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"},
+    {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"},
+    {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"},
+    {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"},
+    {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"},
+    {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"},
+    {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"},
+    {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"},
+    {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"},
+    {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"},
 ]
 cached-property = [
     {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
     {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
 ]
 certifi = [
-    {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
-    {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+    {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
+    {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
 ]
 cffi = [
     {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
@@ -1085,84 +1077,82 @@ colorama = [
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 ]
 coverage = [
-    {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
-    {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
-    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
-    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
-    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
-    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
-    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
-    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
-    {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
-    {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
-    {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
-    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
-    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
-    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
-    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
-    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
-    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
-    {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
-    {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
-    {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
-    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
-    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
-    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
-    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
-    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
-    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
-    {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
-    {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
-    {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
-    {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
-    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
-    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
-    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
-    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
-    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
-    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
-    {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
-    {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
-    {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
-    {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
-    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
-    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
-    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
-    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
-    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
-    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
-    {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
-    {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
-    {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
-    {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+    {file = "coverage-7.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:daf91db39324e9939a9db919ee4fb42a1a23634a056616dae891a030e89f87ba"},
+    {file = "coverage-7.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55121fe140d7e42cb970999b93cf1c2b24484ce028b32bbd00238bb25c13e34a"},
+    {file = "coverage-7.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c027fbb83a8c78a6e06a0302ea1799fdb70e5cda9845a5e000545b8e2b47ea39"},
+    {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf82db5b7f16b51ec32fe0bd2da0805b177c807aa8bfb478c7e6f893418c284"},
+    {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ba5cc54baf3c322c4388de2a43cc95f7809366f0600e743e5aae8ea9d1038b2"},
+    {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:260854160083f8275a9d9d49a05ab0ffc7a1f08f2ccccbfaec94a18aae9f407c"},
+    {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ea45f0dba5a993e93b158f1a9dcfff2770e3bcabf2b80dbe7aa15dce0bcb3bf3"},
+    {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6abc91f6f8b3cc0ae1034e2c03f38769fba1952ab70d0b26953aa01691265c39"},
+    {file = "coverage-7.0.4-cp310-cp310-win32.whl", hash = "sha256:053cdc47cae08257051d7e934a0de4d095b60eb8a3024fa9f1b2322fa1547137"},
+    {file = "coverage-7.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:1e9e94f2612ee549a4b3ee79cbc61bceed77e69cf38cfa05858bae939a886d16"},
+    {file = "coverage-7.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5caa9dd91dcc5f054350dc57a02e053d79633907b9ccffff999568d13dcd19f8"},
+    {file = "coverage-7.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:efc200fa75d9634525b40babc7a16342bd21c101db1a58ef84dc14f4bf6ac0fd"},
+    {file = "coverage-7.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1791e5f74c5b52f76e83fe9f4bb9571cf76d40ee0c51952ee1e4ee935b7e98b9"},
+    {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d9201cfa5a98652b9cef36ab202f17fe3ea83f497b4ba2a8ed39399dfb8fcd4"},
+    {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d8ef6865cb6834cab2b72fff20747a55c714b57b675f7e11c9624fe4f7cb45"},
+    {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b84076e3de192fba0f95e279ac017b64c7c6ecd4f09f36f13420f5bed898a9c7"},
+    {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:dcfbf8ffc046f20d75fd775a92c378f6fc7b9bded6c6f2ab88b6b9cb5805a184"},
+    {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4665a714af31f160403c2e448fb2fef330719d2e04e836b08d60d612707c1041"},
+    {file = "coverage-7.0.4-cp311-cp311-win32.whl", hash = "sha256:2e59aef3fba5758059208c9eff10ae7ded3629e797972746ec33b56844f69411"},
+    {file = "coverage-7.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:2b854f7985b48122b6fe346631e86d67b63293f8255cb59a93d79e3d9f1574e3"},
+    {file = "coverage-7.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e44b60b0b49aa85d548d392a2dca2c6a581cd4084e72e9e16bd58bd86ec20816"},
+    {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2904d7a0388911c61e7e3beefe48c29dfccaba938fc1158f63190101a21e04c2"},
+    {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc74b64bfa89e2f862ea45dd6ac1def371d7cc883b76680d20bdd61a6f3daa20"},
+    {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06046f54e719da21c79f98ecc0962581d1aee0b3798dc6b12b1217da8bf93f4"},
+    {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bc9c77004970a364a1e5454cf7cb884e4277592b959c287689b2a0fd027ef552"},
+    {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0815a09b32384e8ff00a5939ec9cd10efce8742347e019c2daca1a32f5ac2aae"},
+    {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a78a80d131c067d67d8a6f9bd3d3f7ea7eac82c1c7259f97d7ab73f723da9d55"},
+    {file = "coverage-7.0.4-cp37-cp37m-win32.whl", hash = "sha256:2b5936b624fbe711ed02dfd86edd678822e5ee68da02b6d231e5c01090b64590"},
+    {file = "coverage-7.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a63922765ee49d5b4c32afb2cd5516812c8665f3b78e64a0dd005bdfabf991b1"},
+    {file = "coverage-7.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d68f2f7bddb3acdd3b36ef7f334b9d14f30b93e094f808fbbd8d288b8f9e2f9b"},
+    {file = "coverage-7.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dafdba3b2b9010abab08cb8c0dc6549bfca6e1630fe14d47b01dca00d39e694"},
+    {file = "coverage-7.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0322354757b47640535daabd2d56384ff3cad2896248fc84d328c5fad4922d5c"},
+    {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e8267466662aff93d66fa72b9591d02122dfc8a729b0a43dd70e0fb07ed9b37"},
+    {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f684d88eb4924ed0630cf488fd5606e334c6835594bb5fe36b50a509b10383ed"},
+    {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:70c294bb15ba576fb96b580db35895bf03749d683df044212b74e938a7f6821f"},
+    {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:34c0457e1ba450ae8b22dc8ea2fd36ada1010af61291e4c96963cd9d9633366f"},
+    {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b75aff2c35ceaa299691e772f7bf7c8aeab25f46acea2be3dd04cccb914a9860"},
+    {file = "coverage-7.0.4-cp38-cp38-win32.whl", hash = "sha256:6c5554d55668381e131577f20e8f620d4882b04ad558f7e7f3f1f55b3124c379"},
+    {file = "coverage-7.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c82f34fafaf5bc05d222fcf84423d6e156432ca35ca78672d4affd0c09c6ef6c"},
+    {file = "coverage-7.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8dfb5fed540f77e814bf4ec79619c241af6b4578fa1093c5e3389bbb7beab3f"},
+    {file = "coverage-7.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee32a080bab779b71c4d09a3eb5254bfca43ee88828a683dab27dfe8f582516e"},
+    {file = "coverage-7.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dfbee0bf0d633be3a2ab068f5a5731a70adf147d0ba17d9f9932b46c7c5782b"},
+    {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32dc010713455ac0fe2fddb0e48aa43875cc7eb7b09768df10bad8ce45f9c430"},
+    {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cb88a3019ad042eaa69fc7639ef077793fedbf313e89207aa82fefe92c97ebd"},
+    {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73bc6114aab7753ca784f87bcd3b7613bc797aa255b5bca45e5654070ae9acfb"},
+    {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92f135d370fcd7a6fb9659fa2eb716dd2ca364719cbb1756f74d90a221bca1a7"},
+    {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f3d485e6ec6e09857bf2115ece572d666b7c498377d4c70e66bb06c63ed177c2"},
+    {file = "coverage-7.0.4-cp39-cp39-win32.whl", hash = "sha256:c58921fcd9914b56444292e7546fe183d079db99528142c809549ddeaeacd8e9"},
+    {file = "coverage-7.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:f092d9f2ddaa30235d33335fbdb61eb8f3657af519ef5f9dd6bdae65272def11"},
+    {file = "coverage-7.0.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:cb8cfa3bf3a9f18211279458917fef5edeb5e1fdebe2ea8b11969ec2ebe48884"},
+    {file = "coverage-7.0.4.tar.gz", hash = "sha256:f6c4ad409a0caf7e2e12e203348b1a9b19c514e7d078520973147bf2d3dcbc6f"},
 ]
 cryptography = [
-    {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320"},
-    {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c"},
-    {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0"},
-    {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748"},
-    {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146"},
-    {file = "cryptography-38.0.3-cp36-abi3-win32.whl", hash = "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0"},
-    {file = "cryptography-38.0.3-cp36-abi3-win_amd64.whl", hash = "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220"},
-    {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd"},
-    {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55"},
-    {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b"},
-    {file = "cryptography-38.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36"},
-    {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d"},
-    {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7"},
-    {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249"},
-    {file = "cryptography-38.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50"},
-    {file = "cryptography-38.0.3-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0"},
-    {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8"},
-    {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436"},
-    {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548"},
-    {file = "cryptography-38.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a"},
-    {file = "cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"},
+    {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"},
+    {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"},
+    {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"},
+    {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"},
+    {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"},
+    {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"},
+    {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"},
+    {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"},
+    {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"},
+    {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"},
+    {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"},
+    {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"},
+    {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"},
+    {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"},
+    {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"},
+    {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"},
+    {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"},
+    {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"},
 ]
 deprecated = [
     {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
@@ -1173,12 +1163,12 @@ distlib = [
     {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
 ]
 exceptiongroup = [
-    {file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"},
-    {file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"},
+    {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
+    {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
 ]
 filelock = [
-    {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
-    {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
+    {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
+    {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
 ]
 freezegun = [
     {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"},
@@ -1189,28 +1179,28 @@ ghp-import = [
     {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
 ]
 griffe = [
-    {file = "griffe-0.23.0-py3-none-any.whl", hash = "sha256:cfca5f523808109da3f8cfaa46e325fa2e5bef51120d1146e908c121b56475f0"},
-    {file = "griffe-0.23.0.tar.gz", hash = "sha256:a639e2968c8e27f56ebcc57f869a03cea7ac7e7f5684bd2429c665f761c4e7bd"},
+    {file = "griffe-0.25.3-py3-none-any.whl", hash = "sha256:c98e8471a4fc7675a7989f45563a9f7ccbfdfb1713725526d69dec1bbdcda74a"},
+    {file = "griffe-0.25.3.tar.gz", hash = "sha256:a71f156851649b3f0bdad6eb6bf7d7ac70e720a30da9f2d5a60e042480e92c03"},
 ]
 identify = [
-    {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"},
-    {file = "identify-2.5.8.tar.gz", hash = "sha256:7a214a10313b9489a0d61467db2856ae8d0b8306fc923e03a9effa53d8aedc58"},
+    {file = "identify-2.5.12-py2.py3-none-any.whl", hash = "sha256:e8a400c3062d980243d27ce10455a52832205649bbcaf27ffddb3dfaaf477bad"},
+    {file = "identify-2.5.12.tar.gz", hash = "sha256:0bc96b09c838310b6fcfcc61f78a981ea07f94836ef6ef553da5bb5d4745d662"},
 ]
 idna = [
     {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
     {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"},
-    {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"},
+    {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
+    {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
 ]
 iniconfig = [
-    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
-    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+    {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+    {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
 ]
 isort = [
-    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
-    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+    {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
+    {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
 ]
 jinja2 = [
     {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
@@ -1285,52 +1275,52 @@ mkdocs-include-markdown-plugin = [
     {file = "mkdocs_include_markdown_plugin-3.9.1.tar.gz", hash = "sha256:5e5698e78d7fea111be9873a456089daa333497988405acaac8eba2924a19152"},
 ]
 mkdocs-material = [
-    {file = "mkdocs_material-8.5.8-py3-none-any.whl", hash = "sha256:7ff092299e3a63cef99cd87e4a6cc7e7d9ec31fd190d766fd147c35572e6d593"},
-    {file = "mkdocs_material-8.5.8.tar.gz", hash = "sha256:61396251819cf7f547f70a09ce6a7edb2ff5d32e47b9199769020b2d20a83d44"},
+    {file = "mkdocs_material-9.0.3-py3-none-any.whl", hash = "sha256:cedbbf84e156370489907d3c5b79999fcf6563f61a96965ec4c2513d303fa706"},
+    {file = "mkdocs_material-9.0.3.tar.gz", hash = "sha256:918fe38f504ca397b388b6c45445c22cb9acab61f00ade78d5f3edf299b6c9df"},
 ]
 mkdocs-material-extensions = [
-    {file = "mkdocs_material_extensions-1.1-py3-none-any.whl", hash = "sha256:bcc2e5fc70c0ec50e59703ee6e639d87c7e664c0c441c014ea84461a90f1e902"},
-    {file = "mkdocs_material_extensions-1.1.tar.gz", hash = "sha256:96ca979dae66d65c2099eefe189b49d5ac62f76afb59c38e069ffc7cf3c131ec"},
+    {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"},
+    {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"},
 ]
 mkdocstrings = [
-    {file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"},
-    {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
+    {file = "mkdocstrings-0.19.1-py3-none-any.whl", hash = "sha256:32a38d88f67f65b264184ea71290f9332db750d189dea4200cbbe408d304c261"},
+    {file = "mkdocstrings-0.19.1.tar.gz", hash = "sha256:d1037cacb4b522c1e8c164ed5d00d724a82e49dcee0af80db8fb67b384faeef9"},
 ]
 mkdocstrings-python = [
-    {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
-    {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
+    {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"},
+    {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"},
 ]
 mypy = [
-    {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"},
-    {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"},
-    {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"},
-    {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"},
-    {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"},
-    {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"},
-    {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"},
-    {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"},
-    {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"},
-    {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"},
-    {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"},
-    {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"},
-    {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"},
-    {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"},
-    {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"},
-    {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"},
-    {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"},
-    {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"},
-    {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"},
-    {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"},
-    {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"},
-    {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"},
-    {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"},
-    {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"},
-    {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"},
-    {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"},
-    {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"},
-    {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"},
-    {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"},
-    {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"},
+    {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"},
+    {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"},
+    {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"},
+    {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"},
+    {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"},
+    {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"},
+    {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"},
+    {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"},
+    {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"},
+    {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"},
+    {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"},
+    {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"},
+    {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"},
+    {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"},
+    {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"},
+    {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"},
+    {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"},
+    {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"},
+    {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"},
+    {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"},
+    {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"},
+    {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"},
+    {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"},
+    {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"},
+    {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"},
+    {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"},
+    {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"},
+    {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"},
+    {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"},
+    {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"},
 ]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
@@ -1341,28 +1331,28 @@ nodeenv = [
     {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
 ]
 packaging = [
-    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
-    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+    {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
+    {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
 ]
 pathspec = [
-    {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
-    {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
+    {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"},
+    {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"},
 ]
 pip = [
     {file = "pip-22.3.1-py3-none-any.whl", hash = "sha256:908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077"},
     {file = "pip-22.3.1.tar.gz", hash = "sha256:65fd48317359f3af8e593943e6ae1506b66325085ea64b706a998c6e83eeaf38"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"},
-    {file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"},
+    {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
+    {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
 ]
 pluggy = [
     {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.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
-    {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
+    {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"},
+    {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"},
 ]
 py = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
@@ -1373,16 +1363,12 @@ pycparser = [
     {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
 ]
 pygments = [
-    {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
-    {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
+    {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
+    {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
 ]
 pymdown-extensions = [
-    {file = "pymdown_extensions-9.8-py3-none-any.whl", hash = "sha256:8e62688a8b1128acd42fa823f3d429d22f4284b5e6dd4d3cd56721559a5a211b"},
-    {file = "pymdown_extensions-9.8.tar.gz", hash = "sha256:1bd4a173095ef8c433b831af1f3cb13c10883be0c100ae613560668e594651f7"},
-]
-pyparsing = [
-    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
-    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+    {file = "pymdown_extensions-9.9-py3-none-any.whl", hash = "sha256:ac698c15265680db5eb13cd4342abfcde2079ac01e5486028f47a1b41547b859"},
+    {file = "pymdown_extensions-9.9.tar.gz", hash = "sha256:0f8fb7b74a37a61cc34e90b2c91865458b713ec774894ffad64353a5fce85cfc"},
 ]
 pytest = [
     {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
@@ -1393,8 +1379,8 @@ pytest-cov = [
     {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
 ]
 pytest-mypy = [
-    {file = "pytest-mypy-0.10.0.tar.gz", hash = "sha256:e74d632685f15a39c31c551a9d8cec4619e24bd396245a6335c5db0ec6d17b6f"},
-    {file = "pytest_mypy-0.10.0-py3-none-any.whl", hash = "sha256:83843dce75a7ce055efb264ff40dad2ecf7abd4e7bd5e5eda015261d11616abb"},
+    {file = "pytest-mypy-0.10.3.tar.gz", hash = "sha256:f8458f642323f13a2ca3e2e61509f7767966b527b4d8adccd5032c3e7b4fd3db"},
+    {file = "pytest_mypy-0.10.3-py3-none-any.whl", hash = "sha256:7638d0d3906848fc1810cb2f5cc7fceb4cc5c98524aafcac58f28620e3102053"},
 ]
 python-dateutil = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
@@ -1446,6 +1432,96 @@ pyyaml-env-tag = [
     {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
     {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
 ]
+regex = [
+    {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"},
+    {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90"},
+    {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144"},
+    {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc"},
+    {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66"},
+    {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af"},
+    {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc"},
+    {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66"},
+    {file = "regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1"},
+    {file = "regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5"},
+    {file = "regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe"},
+    {file = "regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542"},
+    {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7"},
+    {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e"},
+    {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c"},
+    {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1"},
+    {file = "regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4"},
+    {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f"},
+    {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5"},
+    {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c"},
+    {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c"},
+    {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7"},
+    {file = "regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af"},
+    {file = "regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61"},
+    {file = "regex-2022.10.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"},
+    {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443"},
+    {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742"},
+    {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e"},
+    {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa"},
+    {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e"},
+    {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4"},
+    {file = "regex-2022.10.31-cp36-cp36m-win32.whl", hash = "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066"},
+    {file = "regex-2022.10.31-cp36-cp36m-win_amd64.whl", hash = "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6"},
+    {file = "regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11"},
+    {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec"},
+    {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9"},
+    {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1"},
+    {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8"},
+    {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5"},
+    {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95"},
+    {file = "regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394"},
+    {file = "regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0"},
+    {file = "regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d"},
+    {file = "regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6"},
+    {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318"},
+    {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff"},
+    {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a"},
+    {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73"},
+    {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d"},
+    {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c"},
+    {file = "regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc"},
+    {file = "regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453"},
+    {file = "regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49"},
+    {file = "regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7"},
+    {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4"},
+    {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5"},
+    {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"},
+    {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"},
+    {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"},
+    {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"},
+    {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"},
+    {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"},
+    {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"},
+]
 requests = [
     {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
     {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
@@ -1455,8 +1531,8 @@ requests-mock = [
     {file = "requests_mock-1.10.0-py2.py3-none-any.whl", hash = "sha256:2fdbb637ad17ee15c06f33d31169e71bf9fe2bdb7bc9da26185be0dd8d842699"},
 ]
 setuptools = [
-    {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"},
-    {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"},
+    {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
+    {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
 ]
 six = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -1484,8 +1560,8 @@ tornado = [
     {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"},
 ]
 tox = [
-    {file = "tox-3.27.0-py2.py3-none-any.whl", hash = "sha256:89e4bc6df3854e9fc5582462e328dd3660d7d865ba625ae5881bbc63836a6324"},
-    {file = "tox-3.27.0.tar.gz", hash = "sha256:d2c945f02a03d4501374a3d5430877380deb69b218b1df9b7f1d2f2a10befaf9"},
+    {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"},
+    {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"},
 ]
 tox-poetry = [
     {file = "tox-poetry-0.4.1.tar.gz", hash = "sha256:2395808e1ce487b5894c10f2202e14702bfa6d6909c0d1e525170d14809ac7ef"},
@@ -1522,55 +1598,58 @@ types-backports = [
     {file = "types_backports-0.1.3-py2.py3-none-any.whl", hash = "sha256:dafcd61848081503e738a7768872d1dd6c018401b4d2a1cfb608ea87ec9864b9"},
 ]
 types-cryptography = [
-    {file = "types-cryptography-3.3.23.1.tar.gz", hash = "sha256:f108e7a7161eedd9fe391e1a8ae5a98d40c1da2f0418bc0812a9119990272314"},
-    {file = "types_cryptography-3.3.23.1-py3-none-any.whl", hash = "sha256:b9f8aa93d9e4d7ff920961cdce3dc7cca479946890924d68356b66f15f4f35e3"},
+    {file = "types-cryptography-3.3.23.2.tar.gz", hash = "sha256:09cc53f273dd4d8c29fa7ad11fefd9b734126d467960162397bc5e3e604dea75"},
+    {file = "types_cryptography-3.3.23.2-py3-none-any.whl", hash = "sha256:b965d548f148f8e87f353ccf2b7bd92719fdf6c845ff7cedf2abb393a0643e4f"},
 ]
 types-requests = [
-    {file = "types-requests-2.28.11.2.tar.gz", hash = "sha256:fdcd7bd148139fb8eef72cf4a41ac7273872cad9e6ada14b11ff5dfdeee60ed3"},
-    {file = "types_requests-2.28.11.2-py3-none-any.whl", hash = "sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef"},
+    {file = "types-requests-2.28.11.7.tar.gz", hash = "sha256:0ae38633734990d019b80f5463dfa164ebd3581998ac8435f526da6fe4d598c3"},
+    {file = "types_requests-2.28.11.7-py3-none-any.whl", hash = "sha256:b6a2fca8109f4fdba33052f11ed86102bddb2338519e1827387137fefc66a98b"},
 ]
 types-urllib3 = [
-    {file = "types-urllib3-1.26.25.1.tar.gz", hash = "sha256:a948584944b2412c9a74b9cf64f6c48caf8652cb88b38361316f6d15d8a184cd"},
-    {file = "types_urllib3-1.26.25.1-py3-none-any.whl", hash = "sha256:f6422596cc9ee5fdf68f9d547f541096a20c2dcfd587e37c804c9ea720bf5cb2"},
+    {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"},
+    {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"},
 ]
 typing-extensions = [
     {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
     {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
 ]
 urllib3 = [
-    {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
-    {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+    {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"},
+    {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.16.6-py3-none-any.whl", hash = "sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108"},
-    {file = "virtualenv-20.16.6.tar.gz", hash = "sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e"},
+    {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
+    {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
 ]
 watchdog = [
-    {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
-    {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
-    {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
-    {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
-    {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
-    {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
-    {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
-    {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
-    {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
-    {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
-    {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
-    {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
-    {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
-    {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
-    {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
-    {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
-    {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
-    {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
-    {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
+    {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"},
+    {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"},
+    {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"},
+    {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"},
+    {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"},
+    {file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"},
+    {file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"},
+    {file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"},
+    {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"},
+    {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"},
+    {file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"},
+    {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"},
+    {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"},
+    {file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"},
+    {file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"},
+    {file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"},
+    {file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"},
+    {file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"},
+    {file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"},
+    {file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"},
+    {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"},
+    {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"},
 ]
 wrapt = [
     {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
@@ -1639,6 +1718,6 @@ wrapt = [
     {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
 ]
 zipp = [
-    {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"},
-    {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"},
+    {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"},
+    {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 4c3a787..f686274 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool]
 [tool.poetry]
 name = "jwskate"
-version = "0.5.0"
+version = "0.6.0"
 homepage = "https://github.com/guillp/jwskate"
 description = "A Pythonic implementation of Json Web Signature, Keys, Algorithms, Tokens and Encryption (RFC7514 to 7519), on top of the `cryptography` module."
 authors = ["Guillaume Pujol <guill.p.linux@gmail.com>"]
@@ -52,17 +52,20 @@ pytest-cov  = ">=3.0"
 pytest-mypy = ">=0.9"
 requests-mock = ">=1.9"
 toml = ">=0.10"
-tox  = ">=3.25"
+tox  = ">=3.25,<4.0"
 types-requests = ">=2.27"
 types-cryptography = ">=3.3"
 virtualenv  = ">=20.14.1"
 tox-poetry = ">=0.4.1"
 types-backports = "^0.1.3"
+tomli = "^2.0.1"
+
 
 [tool.poetry.extras]
 test = ["pytest", "pytest-cov", "jwcrypto"]
 doc = ["mkdocs"]
 
+
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"


Test patch:
diff --git a/tests/test_jwe.py b/tests/test_jwe.py
index dc74d25..7b52626 100644
--- a/tests/test_jwe.py
+++ b/tests/test_jwe.py
@@ -604,6 +604,7 @@ def test_decrypt_by_jwcrypto(
         decryption_jwk: the Jwk containing the decryption key
         key_management_alg: the key management alg
         encryption_alg: the encryption alg
+
     """
     import jwcrypto.jwe  # type: ignore[import]
     import jwcrypto.jwk  # type: ignore[import]
@@ -643,6 +644,7 @@ def jwcrypto_encrypted_jwe(
 
     Returns:
         a JWE token
+
     """
     import jwcrypto.jwe
     import jwcrypto.jwk
@@ -683,6 +685,7 @@ def test_decrypt_from_jwcrypto(
         decryption_jwk: the decryption key
         key_management_alg: the key management alg
         encryption_alg: the encryption alg
+
     """
     jwe = JweCompact(jwcrypto_encrypted_jwe)
     assert jwe.alg == key_management_alg
diff --git a/tests/test_jwk/test_generate.py b/tests/test_jwk/test_generate.py
index 3deb91d..72da2ad 100644
--- a/tests/test_jwk/test_generate.py
+++ b/tests/test_jwk/test_generate.py
@@ -22,6 +22,19 @@ def test_generate_for_alg(alg: str) -> None:
         assert jwk.use == "sig"
         assert jwk.key_ops == ("sign", "verify")
         assert jwk.is_symmetric
+        keysize = {
+            "HS256": 256,
+            "HS384": 384,
+            "HS512": 512,
+            "A128CBC-HS256": 256,
+            "A192CBC-HS384": 384,
+            "A256CBC-HS512": 512,
+            "A128GCM": 128,
+            "A192GCM": 192,
+            "A256GCM": 256,
+        }.get(alg)
+        if keysize:
+            assert jwk.key_size == keysize
     elif alg in SignatureAlgs.ALL_ASYMMETRIC:
         assert jwk.kty in ("EC", "RSA", "OKP")
         assert jwk.use == "sig"
@@ -61,3 +74,15 @@ def test_unsupported_alg() -> None:
     # trying to generate a Jwk with an unsupported alg raises a UnsupportedAlg
     with pytest.raises(UnsupportedAlg):
         Jwk.generate_for_alg("unknown_alg")
+
+
+def test_symmetric_key_size() -> None:
+    with pytest.warns():
+        Jwk.generate_for_alg("HS256", key_size=64)
+
+    assert Jwk.generate_for_alg(
+        "HS256", key_size=384
+    )  # no warning when key_size > hash_size
+
+    with pytest.raises(ValueError):
+        Jwk.generate_for_alg("A128GCM", key_size=100)
diff --git a/tests/test_jwk/test_symmetric.py b/tests/test_jwk/test_symmetric.py
index fbe218d..6832760 100644
--- a/tests/test_jwk/test_symmetric.py
+++ b/tests/test_jwk/test_symmetric.py
@@ -15,7 +15,8 @@
 def symmetric_jwk(request: pytest.FixtureRequest) -> SymmetricJwk:
     alg, min_key_size = request.param
     kid = f"my_{alg}_jwk"
-    jwk = SymmetricJwk.generate_for_alg(alg, kid=kid)
+    jwk = Jwk.generate_for_alg(alg, kid=kid)
+    assert isinstance(jwk, SymmetricJwk)
     assert jwk.kty == "oct"
     assert jwk.alg == alg
     assert jwk.kid == kid
diff --git a/tests/test_jws.py b/tests/test_jws.py
index df6ceec..fc975e0 100644
--- a/tests/test_jws.py
+++ b/tests/test_jws.py
@@ -4,6 +4,7 @@
 from jwskate import (
     P_521,
     ECJwk,
+    InvalidJws,
     Jwk,
     JwsCompact,
     JwsJsonFlat,
@@ -430,6 +431,46 @@ def test_jws_format_transformation(
         assert signed_jws_json_general.flatten() == signed_jws_json_flat
         assert signed_jws_json_flat.compact() == signed_jws_compact
 
+    unprotected_header = {"foo": "bar"}
+
+    flat_json_with_unprotected_header = signed_jws_compact.flat_json(unprotected_header)
+    assert flat_json_with_unprotected_header.payload == signed_jws_compact.payload
+    assert flat_json_with_unprotected_header.header == unprotected_header
+    assert flat_json_with_unprotected_header.protected == signed_jws_compact.headers
+    assert flat_json_with_unprotected_header.signature == signed_jws_compact.signature
+    assert (
+        flat_json_with_unprotected_header.jws_signature
+        == signed_jws_compact.jws_signature(unprotected_header)
+    )
+    assert flat_json_with_unprotected_header.compact() == signed_jws_compact
+
+    general_json_with_unprotected_header = signed_jws_compact.general_json(
+        unprotected_header
+    )
+    assert general_json_with_unprotected_header.payload == signed_jws_compact.payload
+    assert (
+        general_json_with_unprotected_header.signatures[0].header == unprotected_header
+    )
+    assert (
+        general_json_with_unprotected_header.signatures[0].protected
+        == signed_jws_compact.headers
+    )
+    assert (
+        general_json_with_unprotected_header.signatures[0].signature
+        == signed_jws_compact.signature
+    )
+    assert general_json_with_unprotected_header.signatures[
+        0
+    ] == signed_jws_compact.jws_signature(unprotected_header)
+    assert (
+        general_json_with_unprotected_header.signed_part(lambda sigs: sigs[0])
+        == signed_jws_compact.signed_part
+    )
+    assert (
+        general_json_with_unprotected_header.compact(lambda sigs: sigs[0])
+        == signed_jws_compact
+    )
+
 
 def test_verify_signature_by_jwcrypto(
     signed_jws_compact: JwsCompact, verification_jwk: Jwk, signature_alg: str
@@ -440,6 +481,7 @@ def test_verify_signature_by_jwcrypto(
         signed_jws_compact: the Jws signed by jwskate to verify
         verification_jwk: the Jwk containing the verification key
         signature_alg: the signature alg
+
     """
     import jwcrypto.jwk  # type: ignore[import]
     import jwcrypto.jws  # type: ignore[import]
@@ -463,6 +505,7 @@ def jwcrypto_signed_jws(
 
     Returns:
         a JWS token
+
     """
     import jwcrypto.jwk
     import jwcrypto.jws
@@ -488,6 +531,7 @@ def test_verify_signature_from_jwcrypto(
         jwcrypto_signed_jws: the JWS to verify
         verification_jwk: the public key to verify the signature
         signature_alg: the alg to use
+
     """
     assert JwsCompact(jwcrypto_signed_jws).verify_signature(
         verification_jwk, alg=signature_alg
@@ -495,6 +539,8 @@ def test_verify_signature_from_jwcrypto(
 
 
 def test_invalid_jws_compact() -> None:
+    with pytest.raises(InvalidJws):
+        JwsCompact("foo")
     with pytest.raises(ValueError):
         JwsCompact(
             "ey.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cOUKU1ijv3KiN2KK_o50RU978I9MzQ4lNw2y7nOGAdM"