diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..7e3a3d2
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [p1c2u]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5bb1222
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+version: 2
+updates:
+  - package-ecosystem: "docker"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+  - package-ecosystem: "pip"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
new file mode 100644
index 0000000..6ff3afe
--- /dev/null
+++ b/.github/workflows/build-docs.yml
@@ -0,0 +1,49 @@
+name: Build documentation
+
+on:
+  push:
+  pull_request:
+    types: [opened, synchronize]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python 3.12
+        id: setup-python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Set up poetry
+        uses: Gr1N/setup-poetry@v9
+
+      - name: Configure poetry
+        run: poetry config virtualenvs.in-project true
+
+      - name: Set up cache
+        uses: actions/cache@v4
+        id: cache
+        with:
+          path: .venv
+          key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+
+      - name: Ensure cache is healthy
+        if: steps.cache.outputs.cache-hit == 'true'
+        run: timeout 10s poetry run pip --version || rm -rf .venv
+
+      - name: Install dependencies
+        run: poetry install --with docs
+
+      - name: Build documentation
+        run: |
+          poetry run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs docs/_build/html -n -W
+
+      - uses: actions/upload-artifact@v4
+        name: Upload docs as artifact
+        with:
+          name: docs-html
+          path: './docs/_build/html'
+          if-no-files-found: error
diff --git a/.github/workflows/docker-publish-manual.yml b/.github/workflows/docker-publish-manual.yml
new file mode 100644
index 0000000..5a02f48
--- /dev/null
+++ b/.github/workflows/docker-publish-manual.yml
@@ -0,0 +1,51 @@
+# This workflow will upload Docker image when a release is created
+# For more information see: https://github.com/marketplace/actions/docker-build-push-action
+
+name: Publish docker image (manual)
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        required: true
+        description: Version to build
+      push:
+        type: boolean
+        description: Push to docker hub
+
+jobs:
+  docker:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: |
+            pythonopenapi/openapi-spec-validator
+          tags: |
+            type=semver,pattern={{version}},value=${{ github.event.inputs.version }}
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Login to DockerHub
+        if: github.event.inputs.push
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Build and push
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          platforms: linux/amd64,linux/arm64
+          push: ${{ github.event.inputs.push }}
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
new file mode 100644
index 0000000..9055b58
--- /dev/null
+++ b/.github/workflows/docker-publish.yml
@@ -0,0 +1,47 @@
+# This workflow will upload Docker image when a release is created
+# For more information see: https://github.com/marketplace/actions/docker-build-push-action
+
+name: Publish docker image
+
+on:
+  workflow_dispatch:
+  release:
+    types:
+    - published
+
+jobs:
+  docker:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: |
+            pythonopenapi/openapi-spec-validator
+          tags: |
+            type=semver,pattern={{version}}
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Login to DockerHub
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Build and push
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          platforms: linux/amd64,linux/arm64
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 0000000..e6b1ef5
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,34 @@
+# This workflow will upload a Python Package using Twine when a release is created
+# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
+
+name: Publish python packages
+
+on:
+  workflow_dispatch:
+  release:
+    types:
+      - published
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    permissions:
+      id-token: write
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.x'
+
+      - name: Set up poetry
+        uses: Gr1N/setup-poetry@v9
+
+      - name: Build
+        run: poetry build
+
+      - name: Publish
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          packages-dir: dist/
diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
new file mode 100644
index 0000000..f67449c
--- /dev/null
+++ b/.github/workflows/python-test.yml
@@ -0,0 +1,62 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Test python code
+
+on:
+  push:
+  pull_request:
+    types: [opened, synchronize]
+
+jobs:
+  test:
+    name: "Tests"
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+        os: [windows-latest, ubuntu-latest]
+      fail-fast: false
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python ${{ matrix.python-version }}
+        id: setup-python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Set up poetry
+        uses: Gr1N/setup-poetry@v9
+
+      - name: Configure poetry
+        run: poetry config virtualenvs.in-project true
+
+      - name: Set up cache
+        uses: actions/cache@v4
+        id: cache
+        with:
+          path: .venv
+          key: venv-${{ github.event_name }}-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+
+      - name: Ensure cache is healthy
+        if: steps.cache.outputs.cache-hit == 'true'
+        shell: bash 
+        run: timeout 10s poetry run pip --version || rm -rf .venv
+
+      - name: Install dependencies
+        run: poetry install --all-extras
+
+      - name: Test
+        env:
+          PYTEST_ADDOPTS: "--color=yes"
+        run: poetry run pytest
+
+      - name: Static type check
+        run: poetry run mypy
+
+      - name: Check dependencies
+        run: poetry run deptry .
+
+      - name: Upload coverage
+        uses: codecov/codecov-action@v5
diff --git a/.gitignore b/.gitignore
index 7bbc71c..615bc7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,5 +97,12 @@ ENV/
 # mkdocs documentation
 /site
 
+# asdf versions
+.tool-versions
+.default-python-packages
+
 # mypy
 .mypy_cache/
+
+# pytest
+.pytest_cache/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..98d11c1
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,46 @@
+---
+default_stages: [commit, push]
+default_language_version:
+  # force all unspecified python hooks to run python3
+  python: python3
+minimum_pre_commit_version: "1.20.0"
+repos:
+  - repo: meta
+    hooks:
+      - id: check-hooks-apply
+
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v2.19.0
+    hooks:
+      - id: pyupgrade
+        args: ["--py36-plus"]
+
+  - repo: local
+    hooks:
+      - id: flynt
+        name: Convert to f-strings with flynt
+        entry: flynt
+        language: python
+        additional_dependencies: ['flynt==0.76']
+
+      - id: black
+        name: black
+        entry: black
+        language: system
+        require_serial: true
+        types: [python]
+
+      - id: isort
+        name: isort
+        entry: isort
+        args: ['--filter-files']
+        language: system
+        require_serial: true
+        types: [python]
+
+      - id: pyflakes
+        name: pyflakes
+        entry: pyflakes
+        language: system
+        require_serial: true
+        types: [python]
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
new file mode 100644
index 0000000..5e7ce0e
--- /dev/null
+++ b/.pre-commit-hooks.yaml
@@ -0,0 +1,6 @@
+- id: openapi-spec-validator
+  name: openapi-spec-validator
+  entry: openapi-spec-validator
+  description: Hook to validate Open API specs.
+  language: python
+  files: .*openapi.*\.(json|yaml|yml)
\ No newline at end of file
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..26116de
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,15 @@
+version: 2
+
+sphinx:
+  configuration: docs/conf.py
+
+formats: all
+
+build:
+  os: ubuntu-24.04
+  tools:
+    python: "3.13"
+  jobs:
+    post_install:
+      - pip install poetry
+      - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
diff --git a/.travis.yml b/.travis.yml
index 39ac693..693dc34 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,32 +2,19 @@ language: python
 sudo: false
 matrix:
   include:
-  - python: 3.2
-  - python: 3.3
-  - python: 3.4
-  - python: 3.5
-  - python: 3.6
-  - python: nightly
-  - python: pypy3
+  - python: '3.8'
+  - python: '3.9'
+  - python: '3.10-dev'
+  - python: 'nightly'
+  - python: 'pypy3'
   allow_failures:
-  - python: 3.2
-  - python: nightly
+  - python: 'nightly'
 before_install:
-- if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install 'coverage<4.0.0'; fi
-- pip install codecov
+- python -m pip install --upgrade pip
+- pip install "poetry<1.2"
 install:
-- pip install -r requirements.txt
-- pip install -r requirements_dev.txt
-- pip install -e .
+- poetry install
 script:
-- python setup.py test
+- poetry run pytest
 after_success:
 - codecov
-deploy:
-  provider: pypi
-  user: p1c2u
-  password:
-    secure: iZWZuDMIWyFtJf5cLDPwA82d7DVi+/8yBQJVowctJwkioz4PEZBrf4N7cKyFc7JlhsS0/gqPJ9nw1FBqHwlTFwikpCYjudcfVijzibwKBbTbYTbTY1xEYiv+2/Q2UGoGjGmf2qdqM9SBaQwvax+KgMO6e4I4vrX4cm3kMx4LHt0Z2ArORlhZ0oKxyi6azcFiZYwlOlp31PuV0iNpBkroBf+gQ20S35hD+GIm1U6D4zqkN0HmZ0LxlpZLXsHZ0FrEE57KU06RowWfkAFBkGjMBjr+phiZ/XRe88SFaiB3HVZaJm+ZPTJKnxryuGt5th54Q10DKLZ3KUien33saBYVziamHZ8ZYS01ztahEhqLKlQVB1e+p1M8mYXKVodqLgytOsddixIBmibq2rDJmLSPwro8RBwLhLdGZdRsH2kii06OQxPrzlUrOwtErozxvdNjS47hwjJ4ZVm4ZGcnOXZut4qwkiEEUMWUd54V+zDNnRxOf+hi/mEx3u8CmkV26XFJ7WHpr/E1T9XHuRh7YVP8MXrM3gjoL86g1swalpH/QBjf0UaF2BlTvWJ3j52uThH7MFUlCBgpYer1giJayyNjFw4+qUVMCyByD87V7x6/3glA7t4Kh0LiMq0Zo23PPbhuJOmJmDy6GTtjkXZEJ6XnNPV9+VR8LApmppevBDKafgA=
-  distributions: sdist bdist_wheel
-  on:
-    tags: true
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..ae9464f
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,76 @@
+Contributing
+============
+
+Firstly, thank you all for taking the time to contribute.
+
+The following section describes how you can contribute to the openapi-spec-validator project on GitHub.
+
+Reporting bugs
+--------------
+
+Before you report
+^^^^^^^^^^^^^^^^^
+
+* Check whether your issue does not already exist in the `Issue tracker <https://github.com/python-openapi/openapi-spec-validator/issues>`__.
+* Make sure it is not a support request or question better suited for `Discussion board <https://github.com/python-openapi/openapi-spec-validator/discussions>`__.
+
+How to submit a report
+^^^^^^^^^^^^^^^^^^^^^^
+
+* Include clear title.
+* Describe your runtime environment with exact versions you use.
+* Describe the exact steps which reproduce the problem, including minimal code snippets.
+* Describe the behavior you observed after following the steps, pasting console outputs.
+* Describe expected behavior to see and why, including links to documentations.
+
+Code contribution
+-----------------
+
+Prerequisites
+^^^^^^^^^^^^^
+
+Install `Poetry <https://python-poetry.org>`__ by following the `official installation instructions <https://python-poetry.org/docs/#installation>`__. Optionally (but recommended), configure Poetry to create a virtual environment in a folder named ``.venv`` within the root directory of the project:
+
+.. code-block:: console
+
+   poetry config virtualenvs.in-project true
+
+Setup
+^^^^^
+
+To create a development environment and install the runtime and development dependencies, run:
+
+.. code-block:: console
+
+   poetry install
+
+Then enter the virtual environment created by Poetry:
+
+.. code-block:: console
+
+   poetry shell
+
+Static checks
+^^^^^^^^^^^^^
+
+The project uses static checks using fantastic `pre-commit <https://pre-commit.com/>`__. Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit.
+
+To turn on pre-commit checks for commit operations in git, enter:
+
+.. code-block:: console
+
+   pre-commit install
+
+To run all checks on your staged files, enter:
+
+.. code-block:: console
+
+   pre-commit run
+
+To run all checks on all files, enter:
+
+.. code-block:: console
+
+   pre-commit run --all-files
+
+Pre-commit check results are also attached to your PR through integration with Github Action.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2d0df31
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+ARG OPENAPI_SPEC_VALIDATOR_VERSION=0.8.0b1
+
+FROM python:3.13.5-alpine as builder
+
+ARG OPENAPI_SPEC_VALIDATOR_VERSION
+
+ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
+
+RUN apk add --no-cache cargo
+RUN python -m pip wheel --wheel-dir /wheels openapi-spec-validator==${OPENAPI_SPEC_VALIDATOR_VERSION}
+
+FROM python:3.13.5-alpine
+
+ARG OPENAPI_SPEC_VALIDATOR_VERSION
+
+COPY --from=builder /wheels /wheels
+RUN apk add --no-cache libgcc
+RUN pip install --no-cache-dir --pre --find-links /wheels openapi-spec-validator==${OPENAPI_SPEC_VALIDATOR_VERSION} && \
+    rm -r /wheels
+
+ENTRYPOINT ["openapi-spec-validator"]
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 7dc7152..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-include README.md
-include requirements.txt
-include requirements_dev.txt
-include openapi_spec_validator/resources/schemas/*/*
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5ecaa79
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+.EXPORT_ALL_VARIABLES:
+
+PROJECT_NAME=openapi-spec-validator
+PACKAGE_NAME=$(subst -,_,${PROJECT_NAME})
+VERSION=`git describe --abbrev=0`
+
+DOCKER_REGISTRY=pythonopenapi
+
+PYTHONDONTWRITEBYTECODE=1
+
+params:
+	@echo "Project name: ${PROJECT_NAME}"
+	@echo "Package name: ${PACKAGE_NAME}"
+	@echo "Version: ${VERSION}"
+
+dist-build:
+	@python setup.py bdist_wheel
+
+dist-cleanup:
+	@rm -rf build dist ${PACKAGE_NAME}.egg-info
+
+dist-upload:
+	@twine upload dist/*.whl
+
+test-python:
+	@python setup.py test
+
+test-cache-cleanup:
+	@rm -rf .pytest_cache
+
+reports-cleanup:
+	@rm -rf reports
+
+test-cleanup: test-cache-cleanup reports-cleanup
+
+cleanup: dist-cleanup test-cleanup
+
+docker-build:
+	@docker build --no-cache --build-arg OPENAPI_SPEC_VALIDATOR_VERSION=${VERSION} -t ${PROJECT_NAME}:${VERSION} .
+
+docker-tag:
+	@docker tag ${PROJECT_NAME}:${VERSION} ${DOCKER_REGISTRY}/${PROJECT_NAME}:${VERSION}
+
+docker-push:
+	@docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}:${VERSION}
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..36a105c
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,14 @@
+openapi-spec-validator
+Copyright 2017-2021 Artur Maciag
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
deleted file mode 100644
index ef7a9ca..0000000
--- a/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# OpenAPI Spec validator
-
-[![Package Version](https://img.shields.io/pypi/v/openapi-spec-validator.svg)](https://pypi.python.org/pypi/openapi-spec-validator)
-[![Build Status](https://travis-ci.org/p1c2u/openapi-spec-validator.svg?branch=master)](https://travis-ci.org/p1c2u/openapi-spec-validator)
-[![Code Coverage](https://img.shields.io/codecov/c/github/p1c2u/openapi-spec-validator/master.svg?style=flat)](https://codecov.io/github/p1c2u/openapi-spec-validator?branch=master)
-[![PyPI Version](https://img.shields.io/pypi/pyversions/openapi-spec-validator.svg)](https://pypi.python.org/pypi/openapi-spec-validator)
-[![PyPI Format](https://img.shields.io/pypi/format/openapi-spec-validator.svg)](https://pypi.python.org/pypi/openapi-spec-validator)
-[![PyPI Status](https://img.shields.io/pypi/status/openapi-spec-validator.svg)](https://pypi.python.org/pypi/openapi-spec-validator)
-
-## About
-
-OpenAPI Spec Validator is a Python library that validates OpenAPI Specs against the [OpenAPI 2.0 (aka Swagger)](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) and [OpenAPI 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) specification. The validator aims to check for full compliance with the Specification.
-
-## Installation
-
-    $ pip install openapi-spec-validator
-
-## Usage
-
-### Command Line Interface
-
-Straight forward way:
-
-```bash
-$ openapi-spec-validator some.yaml
-```
-
-or more pythonic way:
-
-```bash
-$ python -m openapi_spec_validator some.yaml
-```
-
-### Examples
-
-Validate spec:
-
-```python
-
-from openapi_spec_validator import validate_spec
-
-validate_spec(spec_dict)
-```
-
-Add `spec_url` to validate spec with relative files:
-
-```python
-
-from openapi_spec_validator import validate_spec
-
-validate_spec(spec_dict, spec_url='file:///path/to/spec/openapi.yaml')
-```
-
-You can also validate spec from url:
-
-```python
-
-from openapi_spec_validator import validate_spec_url
-
-validate_spec_url('http://example.com/openapi.json')
-```
-
-If you want to iterate through validation errors:
-
-```python
-
-from openapi_spec_validator import openapi_v3_spec_validator
-
-errors_iterator = openapi_v3_spec_validator.iter_errors(spec)
-```
-
-## Related projects
-
-* [openapi-core](https://github.com/p1c2u/openapi-core) is a Python library that adds client-side and server-side support for the OpenAPI.
-
-## License
-
-Copyright (c) 2017, Artur Maciag, All rights reserved.
-Apache v2
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..2948cca
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,128 @@
+**********************
+OpenAPI Spec validator
+**********************
+
+.. image:: https://img.shields.io/docker/v/pythonopenapi/openapi-spec-validator.svg?color=%23086DD7&label=docker%20hub&sort=semver
+     :target: https://hub.docker.com/r/pythonopenapi/openapi-spec-validator
+.. image:: https://img.shields.io/pypi/v/openapi-spec-validator.svg
+     :target: https://pypi.python.org/pypi/openapi-spec-validator
+.. image:: https://travis-ci.org/python-openapi/openapi-spec-validator.svg?branch=master
+     :target: https://travis-ci.org/python-openapi/openapi-spec-validator
+.. image:: https://img.shields.io/codecov/c/github/python-openapi/openapi-spec-validator/master.svg?style=flat
+     :target: https://codecov.io/github/python-openapi/openapi-spec-validator?branch=master
+.. image:: https://img.shields.io/pypi/pyversions/openapi-spec-validator.svg
+     :target: https://pypi.python.org/pypi/openapi-spec-validator
+.. image:: https://img.shields.io/pypi/format/openapi-spec-validator.svg
+     :target: https://pypi.python.org/pypi/openapi-spec-validator
+.. image:: https://img.shields.io/pypi/status/openapi-spec-validator.svg
+     :target: https://pypi.python.org/pypi/openapi-spec-validator
+
+About
+#####
+
+OpenAPI Spec Validator is a CLI, pre-commit hook and python package that validates OpenAPI Specs
+against the `OpenAPI 2.0 (aka Swagger)
+<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__,
+`OpenAPI 3.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md>`__
+and `OpenAPI 3.1 <https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md>`__
+specification. The validator aims to check for full compliance with the Specification.
+
+
+Documentation
+#############
+
+Check documentation to see more details about the features. All documentation is in the "docs" directory and online at `openapi-spec-validator.readthedocs.io <https://openapi-spec-validator.readthedocs.io>`__
+
+
+Installation
+############
+
+.. code-block:: console
+
+    pip install openapi-spec-validator
+
+Alternatively you can download the code and install from the repository:
+
+.. code-block:: bash
+
+   pip install -e git+https://github.com/python-openapi/openapi-spec-validator.git#egg=openapi_spec_validator
+
+
+Usage
+#####
+
+CLI (Command Line Interface)
+****************************
+
+Straight forward way:
+
+.. code-block:: bash
+
+    openapi-spec-validator openapi.yaml
+
+pipes way:
+
+.. code-block:: bash
+
+    cat openapi.yaml | openapi-spec-validator -
+
+docker way:
+
+.. code-block:: bash
+
+    docker run -v path/to/openapi.yaml:/openapi.yaml --rm pythonopenapi/openapi-spec-validator /openapi.yaml
+
+or more pythonic way:
+
+.. code-block:: bash
+
+    python -m openapi_spec_validator openapi.yaml
+
+For more details, read about `CLI (Command Line Interface) <https://openapi-spec-validator.readthedocs.io/en/latest/cli.html>`__.
+
+pre-commit hook
+***************
+
+.. code-block:: yaml
+
+   repos:
+   -   repo: https://github.com/python-openapi/openapi-spec-validator
+       rev: 0.8.0b1 # The version to use or 'master' for latest
+       hooks:
+       -   id: openapi-spec-validator
+
+For more details, read about `pre-commit hook <https://openapi-spec-validator.readthedocs.io/en/latest/hook.html>`__.
+
+Python package
+**************
+
+.. code:: python
+
+    from openapi_spec_validator import validate
+    from openapi_spec_validator.readers import read_from_filename
+
+    spec_dict, base_uri = read_from_filename('openapi.yaml')
+
+    # If no exception is raised by validate(), the spec is valid.
+    validate(spec_dict)
+
+    validate({'openapi': '3.1.0'})
+
+    Traceback (most recent call last):
+        ...
+    OpenAPIValidationError: 'info' is a required property
+
+For more details, read about `Python package <https://openapi-spec-validator.readthedocs.io/en/latest/python.html>`__.
+
+Related projects
+################
+
+* `openapi-core <https://github.com/python-openapi/openapi-core>`__
+   Python library that adds client-side and server-side support for the OpenAPI v3.0 and OpenAPI v3.1 specification.
+* `openapi-schema-validator <https://github.com/python-openapi/openapi-schema-validator>`__
+   Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1.
+
+License
+#######
+
+Copyright (c) 2017-2023, Artur Maciag, All rights reserved. Apache v2
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..8ffe77b
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,20 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+If you believe you have found a security vulnerability in the repository, please report it to us as described below.
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them directly to the repository maintainer.
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+* Full paths of source file(s) related to the manifestation of the issue
+* The location of the affected source code (tag/branch/commit or direct URL)
+* Any special configuration required to reproduce the issue
+* Step-by-step instructions to reproduce the issue
+* Proof-of-concept or exploit code (if possible)
+* Impact of the issue, including how an attacker might exploit the issue
+* This information will help us triage your report more quickly.
diff --git a/docs/cli.rst b/docs/cli.rst
new file mode 100644
index 0000000..b058a1a
--- /dev/null
+++ b/docs/cli.rst
@@ -0,0 +1,49 @@
+CLI (Command Line Interface)
+============================
+
+.. md-tab-set::
+
+   .. md-tab-item:: Executable
+
+      Straight forward way:
+
+      .. code-block:: bash
+
+         openapi-spec-validator openapi.yaml
+
+      pipes way:
+
+      .. code-block:: bash
+
+         cat openapi.yaml | openapi-spec-validator -
+
+   .. md-tab-item:: Docker
+
+      .. code-block:: bash
+
+         docker run -v path/to/openapi.yaml:/openapi.yaml --rm pythonopenapi/openapi-spec-validator /openapi.yaml
+
+   .. md-tab-item:: Python interpreter
+
+      .. code-block:: bash
+
+         python -m openapi_spec_validator openapi.yaml
+
+.. code-block:: bash
+
+   usage: openapi-spec-validator [-h] [--errors {best-match,all}]
+                                 [--schema {2.0,3.0.0,3.1.0,detect}]
+                                 filename
+   
+   positional arguments:
+     filename              Absolute or relative path to file
+   
+   options:
+     -h, --help            show this help message and exit
+     --errors {best-match,all}
+                           Control error reporting. Defaults to "best-
+                           match", use "all" to get all subschema
+                           errors.
+     --schema {2.0,3.0.0,3.1.0,detect}
+                           OpenAPI schema (default: detect)
+
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..1eac603
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,63 @@
+import openapi_spec_validator
+
+project = "openapi-spec-validator"
+copyright = "2023, Artur Maciag"
+author = "Artur Maciag"
+
+release = openapi_spec_validator.__version__
+
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx.ext.doctest",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.coverage",
+    "sphinx.ext.viewcode",
+    "sphinx_immaterial",
+]
+
+templates_path = ["_templates"]
+
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+html_theme = "sphinx_immaterial"
+
+html_static_path = []
+
+html_title = "openapi-spec-validator"
+
+html_theme_options = {
+    "analytics": {
+        "provider": "google",
+        "property": "G-SWHTV603PN",
+    },
+    "repo_url": "https://github.com/python-openapi/openapi-spec-validator/",
+    "repo_name": "openapi-spec-validator",
+    "icon": {
+        "repo": "fontawesome/brands/github-alt",
+        "edit": "material/file-edit-outline",
+    },
+    "palette": [
+        {
+            "media": "(prefers-color-scheme: dark)",
+            "scheme": "slate",
+            "primary": "lime",
+            "accent": "amber",
+            "scheme": "slate",
+            "toggle": {
+                "icon": "material/toggle-switch",
+                "name": "Switch to light mode",
+            },
+        },
+        {
+            "media": "(prefers-color-scheme: light)",
+            "scheme": "default",
+            "primary": "lime",
+            "accent": "amber",
+            "toggle": {
+                "icon": "material/toggle-switch-off-outline",
+                "name": "Switch to dark mode",
+            },
+        },
+    ],
+    "globaltoc_collapse": False,
+}
diff --git a/docs/contributing.rst b/docs/contributing.rst
new file mode 100644
index 0000000..b6e6c69
--- /dev/null
+++ b/docs/contributing.rst
@@ -0,0 +1 @@
+..  include:: ../CONTRIBUTING.rst
diff --git a/docs/hook.rst b/docs/hook.rst
new file mode 100644
index 0000000..5af2756
--- /dev/null
+++ b/docs/hook.rst
@@ -0,0 +1,23 @@
+pre-commit hook
+===============
+
+`pre-commit <https://pre-commit.com>`__ is a framework for building and running `git hooks <https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks>`__.
+
+This document describes available pre-commit hook provided by openapi-spec-validator.
+
+Usage
+-----
+
+The ``openapi-spec-validator`` hook calls the openapi-spec-validator command to make sure the specification does not get committed in a broken state. For more information see the :doc:`cli`.
+
+A full .pre-commit-config.yaml example you can use in your repository:
+
+.. code-block:: yaml
+
+   repos:
+   -   repo: https://github.com/python-openapi/openapi-spec-validator
+       rev: 0.8.0b1 # The version to use or 'master' for latest
+       hooks:
+       -   id: openapi-spec-validator
+
+For more information on how to use pre-commit please see the official `documentation <https://pre-commit.com>`__.
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..060880b
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,117 @@
+openapi-spec-validator
+======================
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   cli
+   python
+   hook
+   contributing
+
+OpenAPI Spec Validator is a CLI, pre-commit hook and python package that validates OpenAPI Specs
+against the `OpenAPI 2.0 (aka Swagger)
+<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__,
+`OpenAPI 3.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md>`__
+and `OpenAPI 3.1 <https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md>`__
+specification. The validator aims to check for full compliance with the Specification.
+
+Installation
+------------
+
+.. md-tab-set::
+
+   .. md-tab-item:: Pip + PyPI (recommended)
+
+      .. code-block:: console
+
+         pip install openapi-spec-validator
+
+   .. md-tab-item:: Pip + the source
+
+      .. code-block:: console
+
+         pip install -e git+https://github.com/python-openapi/openapi-spec-validator.git#egg=openapi_spec_validator
+
+Usage
+-----
+
+.. md-tab-set::
+
+   .. md-tab-item:: CLI (Command Line Interface)
+
+      .. md-tab-set::
+
+         .. md-tab-item:: Executable
+
+            Straight forward way:
+
+            .. code-block:: bash
+
+               openapi-spec-validator openapi.yaml
+
+            pipes way:
+
+            .. code-block:: bash
+
+               cat openapi.yaml | openapi-spec-validator -
+
+         .. md-tab-item:: Docker
+
+            .. code-block:: bash
+
+               docker run -v path/to/openapi.yaml:/openapi.yaml --rm pythonopenapi/openapi-spec-validator /openapi.yaml
+
+         .. md-tab-item:: Python interpreter
+
+            .. code-block:: bash
+
+               python -m openapi_spec_validator openapi.yaml
+
+      Read more about the :doc:`cli`.
+
+   .. md-tab-item:: pre-commit hook
+
+      .. code-block:: yaml
+
+         repos:
+         -   repo: https://github.com/python-openapi/openapi-spec-validator
+             rev: 0.8.0b1 # The version to use or 'master' for latest
+             hooks:
+             -   id: openapi-spec-validator
+      
+      Read more about the :doc:`hook`.
+
+   .. md-tab-item:: Python package
+
+      .. code-block:: python
+
+         from openapi_spec_validator import validate_spec
+         from openapi_spec_validator.readers import read_from_filename
+
+         spec_dict, base_uri = read_from_filename('openapi.yaml')
+
+         # If no exception is raised by validate_spec(), the spec is valid.
+         validate_spec(spec_dict)
+
+         validate_spec({'openapi': '3.1.0'})
+
+         Traceback (most recent call last):
+            ...
+         OpenAPIValidationError: 'info' is a required property
+      
+      Read more about the :doc:`python`.
+
+Related projects
+----------------
+
+* `openapi-core <https://github.com/python-openapi/openapi-core>`__
+   Python library that adds client-side and server-side support for the OpenAPI v3.0 and OpenAPI v3.1 specification.
+* `openapi-schema-validator <https://github.com/python-openapi/openapi-schema-validator>`__
+   Python library that validates schema against the OpenAPI Schema Specification v3.0 and OpenAPI Schema Specification v3.1.
+
+License
+-------
+
+Copyright (c) 2017-2023, Artur Maciag, All rights reserved. Apache v2
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..2119f51
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.http://sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/python.rst b/docs/python.rst
new file mode 100644
index 0000000..b8ecdd5
--- /dev/null
+++ b/docs/python.rst
@@ -0,0 +1,57 @@
+Python package
+==============
+
+By default, OpenAPI spec version is detected. To validate spec:
+
+.. code:: python
+
+    from openapi_spec_validator import validate
+    from openapi_spec_validator.readers import read_from_filename
+
+    spec_dict, base_uri = read_from_filename('openapi.yaml')
+
+    # If no exception is raised by validate(), the spec is valid.
+    validate(spec_dict)
+
+    validate({'openapi': '3.1.0'})
+
+    Traceback (most recent call last):
+    ...
+    OpenAPIValidationError: 'info' is a required property
+         
+Add ``base_uri`` to validate spec with relative files:
+
+.. code:: python
+
+    validate(spec_dict, base_uri='file:///path/to/spec/openapi.yaml')
+
+You can also validate spec from url:
+
+.. code:: python
+
+    from openapi_spec_validator import validate_url
+
+    # If no exception is raised by validate_url(), the spec is valid.
+    validate_url('http://example.com/openapi.json')
+
+In order to explicitly validate a:
+
+* Swagger / OpenAPI 2.0 spec, import ``OpenAPIV2SpecValidator``
+* OpenAPI 3.0 spec, import ``OpenAPIV30SpecValidator`` 
+* OpenAPI 3.1 spec, import ``OpenAPIV31SpecValidator`` 
+
+and pass the validator class to ``validate`` or ``validate_url`` function:
+
+.. code:: python
+
+    validate(spec_dict, cls=OpenAPIV31SpecValidator)
+
+You can also explicitly import ``OpenAPIV3SpecValidator`` which is a shortcut to the latest v3 release.
+
+If you want to iterate through validation errors:
+
+.. code:: python
+
+    from openapi_spec_validator import OpenAPIV31SpecValidator
+
+    errors_iterator = OpenAPIV31SpecValidator(spec).iter_errors()
diff --git a/openapi_spec_validator/__init__.py b/openapi_spec_validator/__init__.py
index 1e139c1..62a25a2 100644
--- a/openapi_spec_validator/__init__.py
+++ b/openapi_spec_validator/__init__.py
@@ -1,62 +1,34 @@
-# -*- coding: utf-8 -*-
-from openapi_spec_validator.shortcuts import (
-    validate_spec_factory, validate_spec_url_factory,
-)
-from openapi_spec_validator.handlers import UrlHandler
-from openapi_spec_validator.schemas import get_openapi_schema
-from openapi_spec_validator.factories import JSONSpecValidatorFactory
-from openapi_spec_validator.validators import SpecValidator
-
-__author__ = 'Artur Maciąg'
-__email__ = 'maciag.artur@gmail.com'
-__version__ = '0.2.0'
-__url__ = 'https://github.com/p1c2u/openapi-spec-validator'
-__license__ = 'Apache License, Version 2.0'
+"""OpenAPI spec validator module."""
+from openapi_spec_validator.shortcuts import validate
+from openapi_spec_validator.shortcuts import validate_spec
+from openapi_spec_validator.shortcuts import validate_spec_url
+from openapi_spec_validator.shortcuts import validate_url
+from openapi_spec_validator.validation import OpenAPIV2SpecValidator
+from openapi_spec_validator.validation import OpenAPIV3SpecValidator
+from openapi_spec_validator.validation import OpenAPIV30SpecValidator
+from openapi_spec_validator.validation import OpenAPIV31SpecValidator
+from openapi_spec_validator.validation import openapi_v2_spec_validator
+from openapi_spec_validator.validation import openapi_v3_spec_validator
+from openapi_spec_validator.validation import openapi_v30_spec_validator
+from openapi_spec_validator.validation import openapi_v31_spec_validator
+
+__author__ = "Artur Maciag"
+__email__ = "maciag.artur@gmail.com"
+__version__ = "0.8.0b1"
+__url__ = "https://github.com/python-openapi/openapi-spec-validator"
+__license__ = "Apache License, Version 2.0"
 
 __all__ = [
-    'openapi_v2_spec_validator', 'openapi_v3_spec_validator',
-    'validate_v2_spec', 'validate_v3_spec', 'validate_spec',
-    'validate_v2_spec_url', 'validate_v3_spec_url', 'validate_spec_url',
+    "openapi_v2_spec_validator",
+    "openapi_v3_spec_validator",
+    "openapi_v30_spec_validator",
+    "openapi_v31_spec_validator",
+    "OpenAPIV2SpecValidator",
+    "OpenAPIV3SpecValidator",
+    "OpenAPIV30SpecValidator",
+    "OpenAPIV31SpecValidator",
+    "validate",
+    "validate_url",
+    "validate_spec",
+    "validate_spec_url",
 ]
-
-default_handlers = {
-    '<all_urls>': UrlHandler('http', 'https', 'file'),
-    'http': UrlHandler('http'),
-    'https': UrlHandler('https'),
-    'file': UrlHandler('file'),
-}
-
-# v2.0 spec
-schema_v2, schema_v2_url = get_openapi_schema('2.0')
-openapi_v2_validator_factory = JSONSpecValidatorFactory(
-    schema_v2, schema_v2_url,
-    resolver_handlers=default_handlers,
-)
-openapi_v2_spec_validator = SpecValidator(
-    openapi_v2_validator_factory,
-    resolver_handlers=default_handlers,
-)
-
-# v3.0.0 spec
-schema_v3, schema_v3_url = get_openapi_schema('3.0.0')
-openapi_v3_validator_factory = JSONSpecValidatorFactory(
-    schema_v3, schema_v3_url,
-    resolver_handlers=default_handlers,
-)
-openapi_v3_spec_validator = SpecValidator(
-    openapi_v3_validator_factory,
-    resolver_handlers=default_handlers,
-)
-
-# shortcuts
-validate_v2_spec = validate_spec_factory(openapi_v2_spec_validator.validate)
-validate_v2_spec_url = validate_spec_url_factory(
-    openapi_v2_spec_validator.validate, default_handlers)
-
-validate_v3_spec = validate_spec_factory(openapi_v3_spec_validator.validate)
-validate_v3_spec_url = validate_spec_url_factory(
-    openapi_v3_spec_validator.validate, default_handlers)
-
-# aliases to the latest version
-validate_spec = validate_v3_spec
-validate_spec_url = validate_v3_spec_url
diff --git a/openapi_spec_validator/__main__.py b/openapi_spec_validator/__main__.py
index 2ba7005..bc97714 100644
--- a/openapi_spec_validator/__main__.py
+++ b/openapi_spec_validator/__main__.py
@@ -1,48 +1,118 @@
 import logging
-import argparse
-import os
 import sys
+from argparse import ArgumentParser
+from typing import Optional
+from typing import Sequence
 
-from openapi_spec_validator import validate_spec_url, validate_v2_spec_url
-from openapi_spec_validator.exceptions import ValidationError
+from jsonschema.exceptions import ValidationError
+from jsonschema.exceptions import best_match
+
+from openapi_spec_validator.readers import read_from_filename
+from openapi_spec_validator.readers import read_from_stdin
+from openapi_spec_validator.shortcuts import validate
+from openapi_spec_validator.validation import OpenAPIV2SpecValidator
+from openapi_spec_validator.validation import OpenAPIV30SpecValidator
+from openapi_spec_validator.validation import OpenAPIV31SpecValidator
 
 logger = logging.getLogger(__name__)
 logging.basicConfig(
-    format='%(asctime)s %(levelname)s %(name)s %(message)s',
-    level=logging.WARNING
+    format="%(asctime)s %(levelname)s %(name)s %(message)s",
+    level=logging.WARNING,
 )
 
 
-def main(args=None):
-    parser = argparse.ArgumentParser()
-    parser.add_argument('filename', help="Absolute or relative path to file")
+def print_ok(filename: str) -> None:
+    print(f"{filename}: OK")
+
+
+def print_error(filename: str, exc: Exception) -> None:
+    print(f"{filename}: Error: {exc}")
+
+
+def print_validationerror(
+    filename: str, exc: ValidationError, errors: str = "best-match"
+) -> None:
+    print(f"{filename}: Validation Error: {exc}")
+    if exc.cause:
+        print("\n# Cause\n")
+        print(exc.cause)
+    if not exc.context:
+        return
+    if errors == "all":
+        print("\n\n# Due to one of those errors\n")
+        print("\n\n\n".join("## " + str(e) for e in exc.context))
+    elif errors == "best-match":
+        print("\n\n# Probably due to this subschema error\n")
+        print("## " + str(best_match(exc.context)))
+        if len(exc.context) > 1:
+            print(
+                f"\n({len(exc.context) - 1} more subschemas errors,",
+                "use --errors=all to see them.)",
+            )
+
+
+def main(args: Optional[Sequence[str]] = None) -> None:
+    parser = ArgumentParser()
+    parser.add_argument(
+        "file",
+        nargs="+",
+        help="Validate specified file(s).",
+    )
     parser.add_argument(
-        '--schema',
-        help="OpenAPI schema (default: 3.0.0)",
+        "--errors",
+        choices=("best-match", "all"),
+        default="best-match",
+        help="""Control error reporting. Defaults to "best-match", """
+        """use "all" to get all subschema errors.""",
+    )
+    parser.add_argument(
+        "--schema",
         type=str,
-        choices=['2.0', '3.0.0'],
-        default='3.0.0'
+        choices=["detect", "2.0", "3.0", "3.1", "3.0.0", "3.1.0"],
+        default="detect",
+        metavar="{detect,2.0,3.0,3.1}",
+        help="OpenAPI schema version (default: detect).",
     )
-    args = parser.parse_args(args)
-    filename = args.filename
-    filename = os.path.abspath(filename)
-    # choose the validator
-    if args.schema == '2.0':
-        validate_url = validate_v2_spec_url
-    elif args.schema == '3.0.0':
-        validate_url = validate_spec_url
-    # validate
-    try:
-        validate_url('file://'+filename)
-    except ValidationError as e:
-        print(e)
-        sys.exit(1)
-    except Exception as e:
-        print(e)
-        sys.exit(2)
-    else:
-        print('OK')
-
-
-if __name__ == '__main__':
+    args_parsed = parser.parse_args(args)
+
+    for filename in args_parsed.file:
+        # choose source
+        reader = read_from_filename
+        if filename in ["-", "/-"]:
+            filename = "stdin"
+            reader = read_from_stdin
+
+        # read source
+        try:
+            spec, base_uri = reader(filename)
+        except Exception as exc:
+            print(exc)
+            sys.exit(1)
+
+        # choose the validator
+        validators = {
+            "detect": None,
+            "2.0": OpenAPIV2SpecValidator,
+            "3.0": OpenAPIV30SpecValidator,
+            "3.1": OpenAPIV31SpecValidator,
+            # backward compatibility
+            "3.0.0": OpenAPIV30SpecValidator,
+            "3.1.0": OpenAPIV31SpecValidator,
+        }
+        validator_cls = validators[args_parsed.schema]
+
+        # validate
+        try:
+            validate(spec, base_uri=base_uri, cls=validator_cls)
+        except ValidationError as exc:
+            print_validationerror(filename, exc, args_parsed.errors)
+            sys.exit(1)
+        except Exception as exc:
+            print_error(filename, exc)
+            sys.exit(2)
+        else:
+            print_ok(filename)
+
+
+if __name__ == "__main__":
     main()
diff --git a/openapi_spec_validator/decorators.py b/openapi_spec_validator/decorators.py
deleted file mode 100644
index 353ec23..0000000
--- a/openapi_spec_validator/decorators.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""OpenAPI spec validator decorators module."""
-import logging
-
-from openapi_spec_validator.managers import VisitingManager
-
-log = logging.getLogger(__name__)
-
-
-class DerefValidatorDecorator:
-    """Dereferences instance if it is a $ref before passing it for validation.
-
-    :param instance_resolver: Resolves refs in the openapi service spec
-    """
-    def __init__(self, instance_resolver):
-        self.instance_resolver = instance_resolver
-        self.visiting = VisitingManager()
-
-    def __call__(self, func):
-        def wrapped(validator, schema_element, instance, schema):
-            if not isinstance(instance, dict) or '$ref' not in instance:
-                yield from func(validator, schema_element, instance, schema)
-                return
-
-            ref = instance['$ref']
-
-            # ref already visited
-            if ref in self.visiting:
-                return
-
-            self._attach_scope(instance)
-            with self.visiting.visit(ref):
-                with self.instance_resolver.resolving(ref) as target:
-                    yield from func(validator, schema_element, target, schema)
-
-        return wrapped
-
-    def _attach_scope(self, instance):
-        log.debug('Attaching x-scope to %s', instance)
-        if 'x-scope' in instance:
-            log.debug('Ref %s already has scope attached', instance['$ref'])
-            return
-
-        instance['x-scope'] = list(self.instance_resolver._scopes_stack)
diff --git a/openapi_spec_validator/exceptions.py b/openapi_spec_validator/exceptions.py
index 85d8c8a..bcfc17a 100644
--- a/openapi_spec_validator/exceptions.py
+++ b/openapi_spec_validator/exceptions.py
@@ -1,17 +1,6 @@
-from jsonschema.exceptions import ValidationError
-
-
-class OpenAPIValidationError(ValidationError):
-    pass
-
-
-class ExtraParametersError(OpenAPIValidationError):
-    pass
-
-
-class ParameterDuplicateError(OpenAPIValidationError):
+class OpenAPIError(Exception):
     pass
 
 
-class UnresolvableParameterError(OpenAPIValidationError):
+class OpenAPISpecValidatorError(OpenAPIError):
     pass
diff --git a/openapi_spec_validator/factories.py b/openapi_spec_validator/factories.py
deleted file mode 100644
index 956235a..0000000
--- a/openapi_spec_validator/factories.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""OpenAPI spec validator factories module."""
-from jsonschema import validators
-from jsonschema.validators import Draft4Validator, RefResolver
-
-from openapi_spec_validator.generators import (
-    SpecValidatorsGeneratorFactory,
-)
-
-
-class Draft4ExtendedValidatorFactory(Draft4Validator):
-    """Draft4Validator with extra validators factory that follows $refs
-    in the schema being validated."""
-
-    @classmethod
-    def from_resolver(cls, spec_resolver):
-        """Creates a customized Draft4ExtendedValidator.
-
-        :param spec_resolver: resolver for the spec
-        :type resolver: :class:`jsonschema.RefResolver`
-        """
-        spec_validators = cls._get_spec_validators(spec_resolver)
-        return validators.extend(Draft4Validator, spec_validators)
-
-    @classmethod
-    def _get_spec_validators(cls, spec_resolver):
-        generator = SpecValidatorsGeneratorFactory.from_spec_resolver(
-            spec_resolver)
-        return dict(list(generator))
-
-
-class JSONSpecValidatorFactory:
-    """
-    Json documents validator factory against a json schema.
-
-    :param schema: schema for validation.
-    :param schema_url: schema base uri.
-    """
-
-    schema_validator_class = Draft4Validator
-    spec_validator_factory = Draft4ExtendedValidatorFactory
-
-    def __init__(self, schema, schema_url='', resolver_handlers=None):
-        self.schema = schema
-        self.schema_url = schema_url
-        self.resolver_handlers = resolver_handlers or ()
-
-        self.schema_validator_class.check_schema(self.schema)
-
-    @property
-    def schema_resolver(self):
-        return self._get_resolver(self.schema_url, self.schema)
-
-    def create(self, spec_resolver):
-        """Creates json documents validator from spec resolver.
-        :param spec_resolver: reference resolver.
-
-        :return: RefResolver for spec with cached remote $refs used during
-            validation.
-        :rtype: :class:`jsonschema.RefResolver`
-        """
-        validator_cls = self.spec_validator_factory.from_resolver(
-            spec_resolver)
-
-        return validator_cls(
-            self.schema, resolver=self.schema_resolver)
-
-    def _get_resolver(self, base_uri, referrer):
-        return RefResolver(
-            base_uri, referrer, handlers=self.resolver_handlers)
diff --git a/openapi_spec_validator/generators.py b/openapi_spec_validator/generators.py
deleted file mode 100644
index c8adb52..0000000
--- a/openapi_spec_validator/generators.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""OpenAPI spec validator generators module."""
-import logging
-
-from six import iteritems
-from jsonschema import _validators
-
-from openapi_spec_validator.decorators import DerefValidatorDecorator
-
-log = logging.getLogger(__name__)
-
-
-class SpecValidatorsGeneratorFactory:
-    """Generator factory for customized validators that follows $refs
-    in the schema being validated.
-    """
-
-    validators = {
-        '$ref': _validators.ref,
-        'properties': _validators.properties_draft4,
-        'additionalProperties': _validators.additionalProperties,
-        'patternProperties': _validators.patternProperties,
-        'type': _validators.type_draft4,
-        'dependencies': _validators.dependencies,
-        'required': _validators.required_draft4,
-        'minProperties': _validators.minProperties_draft4,
-        'maxProperties': _validators.maxProperties_draft4,
-        'allOf': _validators.allOf_draft4,
-        'oneOf': _validators.oneOf_draft4,
-        'anyOf': _validators.anyOf_draft4,
-        'not': _validators.not_draft4,
-    }
-
-    @classmethod
-    def from_spec_resolver(cls, spec_resolver):
-        """Creates validators generator for the spec resolver.
-
-        :param spec_resolver: resolver for the spec
-        :type instance_resolver: :class:`jsonschema.RefResolver`
-        """
-        deref = DerefValidatorDecorator(spec_resolver)
-        for key, validator_callable in iteritems(cls.validators):
-            yield key, deref(validator_callable)
diff --git a/openapi_spec_validator/handlers.py b/openapi_spec_validator/handlers.py
deleted file mode 100644
index a0aaf28..0000000
--- a/openapi_spec_validator/handlers.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""OpenAPI spec validator handlers module."""
-import contextlib
-
-from six.moves.urllib.parse import urlparse
-from six.moves.urllib.request import urlopen
-from yaml import safe_load
-
-
-class UrlHandler:
-    """OpenAPI spec validator URL scheme handler."""
-
-    def __init__(self, *allowed_schemes):
-        self.allowed_schemes = allowed_schemes
-
-    def __call__(self, url, timeout=1):
-        assert urlparse(url).scheme in self.allowed_schemes
-
-        with contextlib.closing(urlopen(url, timeout=timeout)) as fh:
-            return safe_load(fh)
diff --git a/openapi_spec_validator/managers.py b/openapi_spec_validator/managers.py
deleted file mode 100644
index a4810e1..0000000
--- a/openapi_spec_validator/managers.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""OpenAPI spec validator managers module."""
-from contextlib import contextmanager
-
-
-class VisitingManager(dict):
-    """Visiting manager. Mark keys which being visited."""
-
-    @contextmanager
-    def visit(self, key):
-        """Visits key and marks as visited.
-        Support context manager interface.
-
-        :param key: key being visited.
-        """
-        self[key] = key
-        try:
-            yield key
-        finally:
-            del self[key]
-
-
-class ResolverManager(object):
-    def __init__(self, resolver):
-        self.resolver = resolver
-
-    @contextmanager
-    def in_scope(self, item, scope='x-scope'):
-        if scope not in item:
-            yield self.resolver
-        else:
-            saved_scope_stack = self.resolver._scopes_stack
-            try:
-                self.resolver._scopes_stack = item[scope]
-                yield self.resolver
-            finally:
-                self.resolver._scopes_stack = saved_scope_stack
diff --git a/openapi_spec_validator/py.typed b/openapi_spec_validator/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/openapi_spec_validator/readers.py b/openapi_spec_validator/readers.py
new file mode 100644
index 0000000..4af9270
--- /dev/null
+++ b/openapi_spec_validator/readers.py
@@ -0,0 +1,21 @@
+import sys
+from os import path
+from pathlib import Path
+from typing import Tuple
+
+from jsonschema_path.handlers import all_urls_handler
+from jsonschema_path.handlers import file_handler
+from jsonschema_path.typing import Schema
+
+
+def read_from_stdin(filename: str) -> Tuple[Schema, str]:
+    return file_handler(sys.stdin), ""  # type: ignore
+
+
+def read_from_filename(filename: str) -> Tuple[Schema, str]:
+    if not path.isfile(filename):
+        raise OSError(f"No such file: {filename}")
+
+    filename = path.abspath(filename)
+    uri = Path(filename).as_uri()
+    return all_urls_handler(uri), uri
diff --git a/openapi_spec_validator/resources/schemas/v2.0/schema.json b/openapi_spec_validator/resources/schemas/v2.0/schema.json
index ebe10ed..3aa08a1 100644
--- a/openapi_spec_validator/resources/schemas/v2.0/schema.json
+++ b/openapi_spec_validator/resources/schemas/v2.0/schema.json
@@ -1,6 +1,5 @@
 {
   "title": "A JSON Schema for Swagger 2.0 API.",
-  "id": "http://swagger.io/v2/schema.json#",
   "$schema": "http://json-schema.org/draft-04/schema#",
   "type": "object",
   "required": [
diff --git a/openapi_spec_validator/resources/schemas/v3.0.0/schema.json b/openapi_spec_validator/resources/schemas/v3.0.0/schema.json
index b199631..7b414d8 100644
--- a/openapi_spec_validator/resources/schemas/v3.0.0/schema.json
+++ b/openapi_spec_validator/resources/schemas/v3.0.0/schema.json
@@ -433,7 +433,15 @@
           "content": {
             "$ref": "#/definitions/mediaTypes"
           }
-        }
+        },
+        "oneOf": [
+          {
+            "required": [ "schema" ]
+          },
+          {
+            "required": [ "content" ]
+          }
+        ]
       },
       "requestBody": {
         "type": "object",
@@ -665,7 +673,15 @@
           "content": {
             "$ref": "#/definitions/mediaTypes"
           }
-        }
+        },
+        "oneOf": [
+          {
+            "required": [ "schema" ]
+          },
+          {
+            "required": [ "content" ]
+          }
+        ]
       },
       "tag": {
         "type": "object",
@@ -1250,4 +1266,4 @@
         ]
       }
     }
-  }
\ No newline at end of file
+  }
diff --git a/openapi_spec_validator/resources/schemas/v3.0/schema.json b/openapi_spec_validator/resources/schemas/v3.0/schema.json
new file mode 100644
index 0000000..6e8eab8
--- /dev/null
+++ b/openapi_spec_validator/resources/schemas/v3.0/schema.json
@@ -0,0 +1,1666 @@
+{
+  "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28",
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3",
+  "type": "object",
+  "required": [
+    "openapi",
+    "info",
+    "paths"
+  ],
+  "properties": {
+    "openapi": {
+      "type": "string",
+      "pattern": "^3\\.0\\.\\d(-.+)?$"
+    },
+    "info": {
+      "$ref": "#/definitions/Info"
+    },
+    "externalDocs": {
+      "$ref": "#/definitions/ExternalDocumentation"
+    },
+    "servers": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/Server"
+      }
+    },
+    "security": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/SecurityRequirement"
+      }
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/Tag"
+      },
+      "uniqueItems": true
+    },
+    "paths": {
+      "$ref": "#/definitions/Paths"
+    },
+    "components": {
+      "$ref": "#/definitions/Components"
+    }
+  },
+  "patternProperties": {
+    "^x-": {
+    }
+  },
+  "additionalProperties": false,
+  "definitions": {
+    "Reference": {
+      "type": "object",
+      "required": [
+        "$ref"
+      ],
+      "patternProperties": {
+        "^\\$ref$": {
+          "type": "string",
+          "format": "uri-reference"
+        }
+      }
+    },
+    "Info": {
+      "type": "object",
+      "required": [
+        "title",
+        "version"
+      ],
+      "properties": {
+        "title": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "termsOfService": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "contact": {
+          "$ref": "#/definitions/Contact"
+        },
+        "license": {
+          "$ref": "#/definitions/License"
+        },
+        "version": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Contact": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "email": {
+          "type": "string",
+          "format": "email"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "License": {
+      "type": "object",
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri-reference"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Server": {
+      "type": "object",
+      "required": [
+        "url"
+      ],
+      "properties": {
+        "url": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "variables": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/ServerVariable"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "ServerVariable": {
+      "type": "object",
+      "required": [
+        "default"
+      ],
+      "properties": {
+        "enum": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "default": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Components": {
+      "type": "object",
+      "properties": {
+        "schemas": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Schema"
+                },
+                {
+                  "$ref": "#/definitions/Reference"
+                }
+              ]
+            }
+          }
+        },
+        "responses": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Response"
+                }
+              ]
+            }
+          }
+        },
+        "parameters": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Parameter"
+                }
+              ]
+            }
+          }
+        },
+        "examples": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Example"
+                }
+              ]
+            }
+          }
+        },
+        "requestBodies": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/RequestBody"
+                }
+              ]
+            }
+          }
+        },
+        "headers": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Header"
+                }
+              ]
+            }
+          }
+        },
+        "securitySchemes": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/SecurityScheme"
+                }
+              ]
+            }
+          }
+        },
+        "links": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Link"
+                }
+              ]
+            }
+          }
+        },
+        "callbacks": {
+          "type": "object",
+          "patternProperties": {
+            "^[a-zA-Z0-9\\.\\-_]+$": {
+              "oneOf": [
+                {
+                  "$ref": "#/definitions/Reference"
+                },
+                {
+                  "$ref": "#/definitions/Callback"
+                }
+              ]
+            }
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Schema": {
+      "type": "object",
+      "properties": {
+        "title": {
+          "type": "string"
+        },
+        "multipleOf": {
+          "type": "number",
+          "minimum": 0,
+          "exclusiveMinimum": true
+        },
+        "maximum": {
+          "type": "number"
+        },
+        "exclusiveMaximum": {
+          "type": "boolean",
+          "default": false
+        },
+        "minimum": {
+          "type": "number"
+        },
+        "exclusiveMinimum": {
+          "type": "boolean",
+          "default": false
+        },
+        "maxLength": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "minLength": {
+          "type": "integer",
+          "minimum": 0,
+          "default": 0
+        },
+        "pattern": {
+          "type": "string",
+          "format": "regex"
+        },
+        "maxItems": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "minItems": {
+          "type": "integer",
+          "minimum": 0,
+          "default": 0
+        },
+        "uniqueItems": {
+          "type": "boolean",
+          "default": false
+        },
+        "maxProperties": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "minProperties": {
+          "type": "integer",
+          "minimum": 0,
+          "default": 0
+        },
+        "required": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "minItems": 1,
+          "uniqueItems": true
+        },
+        "enum": {
+          "type": "array",
+          "items": {
+          },
+          "minItems": 1,
+          "uniqueItems": false
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "array",
+            "boolean",
+            "integer",
+            "number",
+            "object",
+            "string"
+          ]
+        },
+        "not": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "allOf": {
+          "type": "array",
+          "items": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Schema"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "oneOf": {
+          "type": "array",
+          "items": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Schema"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "anyOf": {
+          "type": "array",
+          "items": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Schema"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "items": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "properties": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Schema"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "additionalProperties": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            },
+            {
+              "type": "boolean"
+            }
+          ],
+          "default": true
+        },
+        "description": {
+          "type": "string"
+        },
+        "format": {
+          "type": "string"
+        },
+        "default": {
+        },
+        "nullable": {
+          "type": "boolean",
+          "default": false
+        },
+        "discriminator": {
+          "$ref": "#/definitions/Discriminator"
+        },
+        "readOnly": {
+          "type": "boolean",
+          "default": false
+        },
+        "writeOnly": {
+          "type": "boolean",
+          "default": false
+        },
+        "example": {
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/ExternalDocumentation"
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "xml": {
+          "$ref": "#/definitions/XML"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Discriminator": {
+      "type": "object",
+      "required": [
+        "propertyName"
+      ],
+      "properties": {
+        "propertyName": {
+          "type": "string"
+        },
+        "mapping": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      }
+    },
+    "XML": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "namespace": {
+          "type": "string",
+          "format": "uri"
+        },
+        "prefix": {
+          "type": "string"
+        },
+        "attribute": {
+          "type": "boolean",
+          "default": false
+        },
+        "wrapped": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Response": {
+      "type": "object",
+      "required": [
+        "description"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Header"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "content": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/MediaType"
+          }
+        },
+        "links": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Link"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "MediaType": {
+      "type": "object",
+      "properties": {
+        "schema": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "example": {
+        },
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Example"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "encoding": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/Encoding"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false,
+      "allOf": [
+        {
+          "$ref": "#/definitions/ExampleXORExamples"
+        }
+      ]
+    },
+    "Example": {
+      "type": "object",
+      "properties": {
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "value": {
+        },
+        "externalValue": {
+          "type": "string",
+          "format": "uri-reference"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Header": {
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "type": "boolean",
+          "default": false
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "allowEmptyValue": {
+          "type": "boolean",
+          "default": false
+        },
+        "style": {
+          "type": "string",
+          "enum": [
+            "simple"
+          ],
+          "default": "simple"
+        },
+        "explode": {
+          "type": "boolean"
+        },
+        "allowReserved": {
+          "type": "boolean",
+          "default": false
+        },
+        "schema": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "content": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/MediaType"
+          },
+          "minProperties": 1,
+          "maxProperties": 1
+        },
+        "example": {
+        },
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Example"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false,
+      "allOf": [
+        {
+          "$ref": "#/definitions/ExampleXORExamples"
+        },
+        {
+          "$ref": "#/definitions/SchemaXORContent"
+        }
+      ]
+    },
+    "Paths": {
+      "type": "object",
+      "patternProperties": {
+        "^\\/": {
+          "$ref": "#/definitions/PathItem"
+        },
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "PathItem": {
+      "type": "object",
+      "properties": {
+        "$ref": {
+          "type": "string"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Server"
+          }
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Parameter"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          },
+          "uniqueItems": true
+        }
+      },
+      "patternProperties": {
+        "^(get|put|post|delete|options|head|patch|trace)$": {
+          "$ref": "#/definitions/Operation"
+        },
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Operation": {
+      "type": "object",
+      "required": [
+        "responses"
+      ],
+      "properties": {
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/ExternalDocumentation"
+        },
+        "operationId": {
+          "type": "string"
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Parameter"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          },
+          "uniqueItems": true
+        },
+        "requestBody": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/RequestBody"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "responses": {
+          "$ref": "#/definitions/Responses"
+        },
+        "callbacks": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Callback"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "security": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/SecurityRequirement"
+          }
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Server"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Responses": {
+      "type": "object",
+      "properties": {
+        "default": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Response"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        }
+      },
+      "patternProperties": {
+        "^[1-5](?:\\d{2}|XX)$": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Response"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "^x-": {
+        }
+      },
+      "minProperties": 1,
+      "additionalProperties": false
+    },
+    "SecurityRequirement": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "array",
+        "items": {
+          "type": "string"
+        }
+      }
+    },
+    "Tag": {
+      "type": "object",
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/ExternalDocumentation"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "ExternalDocumentation": {
+      "type": "object",
+      "required": [
+        "url"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri-reference"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "ExampleXORExamples": {
+      "description": "Example and examples are mutually exclusive",
+      "not": {
+        "required": [
+          "example",
+          "examples"
+        ]
+      }
+    },
+    "SchemaXORContent": {
+      "description": "Schema and content are mutually exclusive, at least one is required",
+      "not": {
+        "required": [
+          "schema",
+          "content"
+        ]
+      },
+      "oneOf": [
+        {
+          "required": [
+            "schema"
+          ]
+        },
+        {
+          "required": [
+            "content"
+          ],
+          "description": "Some properties are not allowed if content is present",
+          "allOf": [
+            {
+              "not": {
+                "required": [
+                  "style"
+                ]
+              }
+            },
+            {
+              "not": {
+                "required": [
+                  "explode"
+                ]
+              }
+            },
+            {
+              "not": {
+                "required": [
+                  "allowReserved"
+                ]
+              }
+            },
+            {
+              "not": {
+                "required": [
+                  "example"
+                ]
+              }
+            },
+            {
+              "not": {
+                "required": [
+                  "examples"
+                ]
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "Parameter": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "in": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "type": "boolean",
+          "default": false
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "allowEmptyValue": {
+          "type": "boolean",
+          "default": false
+        },
+        "style": {
+          "type": "string"
+        },
+        "explode": {
+          "type": "boolean"
+        },
+        "allowReserved": {
+          "type": "boolean",
+          "default": false
+        },
+        "schema": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/Schema"
+            },
+            {
+              "$ref": "#/definitions/Reference"
+            }
+          ]
+        },
+        "content": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/MediaType"
+          },
+          "minProperties": 1,
+          "maxProperties": 1
+        },
+        "example": {
+        },
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Example"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "name",
+        "in"
+      ],
+      "allOf": [
+        {
+          "$ref": "#/definitions/ExampleXORExamples"
+        },
+        {
+          "$ref": "#/definitions/SchemaXORContent"
+        },
+        {
+          "$ref": "#/definitions/ParameterLocation"
+        }
+      ]
+    },
+    "ParameterLocation": {
+      "description": "Parameter location",
+      "oneOf": [
+        {
+          "description": "Parameter in path",
+          "required": [
+            "required"
+          ],
+          "properties": {
+            "in": {
+              "enum": [
+                "path"
+              ]
+            },
+            "style": {
+              "enum": [
+                "matrix",
+                "label",
+                "simple"
+              ],
+              "default": "simple"
+            },
+            "required": {
+              "enum": [
+                true
+              ]
+            }
+          }
+        },
+        {
+          "description": "Parameter in query",
+          "properties": {
+            "in": {
+              "enum": [
+                "query"
+              ]
+            },
+            "style": {
+              "enum": [
+                "form",
+                "spaceDelimited",
+                "pipeDelimited",
+                "deepObject"
+              ],
+              "default": "form"
+            }
+          }
+        },
+        {
+          "description": "Parameter in header",
+          "properties": {
+            "in": {
+              "enum": [
+                "header"
+              ]
+            },
+            "style": {
+              "enum": [
+                "simple"
+              ],
+              "default": "simple"
+            }
+          }
+        },
+        {
+          "description": "Parameter in cookie",
+          "properties": {
+            "in": {
+              "enum": [
+                "cookie"
+              ]
+            },
+            "style": {
+              "enum": [
+                "form"
+              ],
+              "default": "form"
+            }
+          }
+        }
+      ]
+    },
+    "RequestBody": {
+      "type": "object",
+      "required": [
+        "content"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "content": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/MediaType"
+          }
+        },
+        "required": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "SecurityScheme": {
+      "oneOf": [
+        {
+          "$ref": "#/definitions/APIKeySecurityScheme"
+        },
+        {
+          "$ref": "#/definitions/HTTPSecurityScheme"
+        },
+        {
+          "$ref": "#/definitions/OAuth2SecurityScheme"
+        },
+        {
+          "$ref": "#/definitions/OpenIdConnectSecurityScheme"
+        }
+      ]
+    },
+    "APIKeySecurityScheme": {
+      "type": "object",
+      "required": [
+        "type",
+        "name",
+        "in"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "apiKey"
+          ]
+        },
+        "name": {
+          "type": "string"
+        },
+        "in": {
+          "type": "string",
+          "enum": [
+            "header",
+            "query",
+            "cookie"
+          ]
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "HTTPSecurityScheme": {
+      "type": "object",
+      "required": [
+        "scheme",
+        "type"
+      ],
+      "properties": {
+        "scheme": {
+          "type": "string"
+        },
+        "bearerFormat": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "http"
+          ]
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false,
+      "oneOf": [
+        {
+          "description": "Bearer",
+          "properties": {
+            "scheme": {
+              "type": "string",
+              "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
+            }
+          }
+        },
+        {
+          "description": "Non Bearer",
+          "not": {
+            "required": [
+              "bearerFormat"
+            ]
+          },
+          "properties": {
+            "scheme": {
+              "not": {
+                "type": "string",
+                "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
+              }
+            }
+          }
+        }
+      ]
+    },
+    "OAuth2SecurityScheme": {
+      "type": "object",
+      "required": [
+        "type",
+        "flows"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "oauth2"
+          ]
+        },
+        "flows": {
+          "$ref": "#/definitions/OAuthFlows"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "OpenIdConnectSecurityScheme": {
+      "type": "object",
+      "required": [
+        "type",
+        "openIdConnectUrl"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "openIdConnect"
+          ]
+        },
+        "openIdConnectUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "OAuthFlows": {
+      "type": "object",
+      "properties": {
+        "implicit": {
+          "$ref": "#/definitions/ImplicitOAuthFlow"
+        },
+        "password": {
+          "$ref": "#/definitions/PasswordOAuthFlow"
+        },
+        "clientCredentials": {
+          "$ref": "#/definitions/ClientCredentialsFlow"
+        },
+        "authorizationCode": {
+          "$ref": "#/definitions/AuthorizationCodeOAuthFlow"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "ImplicitOAuthFlow": {
+      "type": "object",
+      "required": [
+        "authorizationUrl",
+        "scopes"
+      ],
+      "properties": {
+        "authorizationUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "refreshUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "scopes": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "PasswordOAuthFlow": {
+      "type": "object",
+      "required": [
+        "tokenUrl",
+        "scopes"
+      ],
+      "properties": {
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "refreshUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "scopes": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "ClientCredentialsFlow": {
+      "type": "object",
+      "required": [
+        "tokenUrl",
+        "scopes"
+      ],
+      "properties": {
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "refreshUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "scopes": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "AuthorizationCodeOAuthFlow": {
+      "type": "object",
+      "required": [
+        "authorizationUrl",
+        "tokenUrl",
+        "scopes"
+      ],
+      "properties": {
+        "authorizationUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "refreshUrl": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "scopes": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    },
+    "Link": {
+      "type": "object",
+      "properties": {
+        "operationId": {
+          "type": "string"
+        },
+        "operationRef": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "parameters": {
+          "type": "object",
+          "additionalProperties": {
+          }
+        },
+        "requestBody": {
+        },
+        "description": {
+          "type": "string"
+        },
+        "server": {
+          "$ref": "#/definitions/Server"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false,
+      "not": {
+        "description": "Operation Id and Operation Ref are mutually exclusive",
+        "required": [
+          "operationId",
+          "operationRef"
+        ]
+      }
+    },
+    "Callback": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/PathItem"
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      }
+    },
+    "Encoding": {
+      "type": "object",
+      "properties": {
+        "contentType": {
+          "type": "string"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "oneOf": [
+              {
+                "$ref": "#/definitions/Header"
+              },
+              {
+                "$ref": "#/definitions/Reference"
+              }
+            ]
+          }
+        },
+        "style": {
+          "type": "string",
+          "enum": [
+            "form",
+            "spaceDelimited",
+            "pipeDelimited",
+            "deepObject"
+          ]
+        },
+        "explode": {
+          "type": "boolean"
+        },
+        "allowReserved": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+        }
+      },
+      "additionalProperties": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/openapi_spec_validator/resources/schemas/v3.1/schema.json b/openapi_spec_validator/resources/schemas/v3.1/schema.json
new file mode 100644
index 0000000..b57b3ba
--- /dev/null
+++ b/openapi_spec_validator/resources/schemas/v3.1/schema.json
@@ -0,0 +1,1449 @@
+{
+  "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0",
+  "type": "object",
+  "properties": {
+    "openapi": {
+      "type": "string",
+      "pattern": "^3\\.1\\.\\d+(-.+)?$"
+    },
+    "info": {
+      "$ref": "#/$defs/info"
+    },
+    "jsonSchemaDialect": {
+      "type": "string",
+      "format": "uri",
+      "default": "https://spec.openapis.org/oas/3.1/dialect/base"
+    },
+    "servers": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/server"
+      },
+      "default": [
+        {
+          "url": "/"
+        }
+      ]
+    },
+    "paths": {
+      "$ref": "#/$defs/paths"
+    },
+    "webhooks": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/$defs/path-item-or-reference"
+      }
+    },
+    "components": {
+      "$ref": "#/$defs/components"
+    },
+    "security": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/security-requirement"
+      }
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/tag"
+      }
+    },
+    "externalDocs": {
+      "$ref": "#/$defs/external-documentation"
+    }
+  },
+  "required": [
+    "openapi",
+    "info"
+  ],
+  "anyOf": [
+    {
+      "required": [
+        "paths"
+      ]
+    },
+    {
+      "required": [
+        "components"
+      ]
+    },
+    {
+      "required": [
+        "webhooks"
+      ]
+    }
+  ],
+  "$ref": "#/$defs/specification-extensions",
+  "unevaluatedProperties": false,
+  "$defs": {
+    "info": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object",
+      "type": "object",
+      "properties": {
+        "title": {
+          "type": "string"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "termsOfService": {
+          "type": "string",
+          "format": "uri"
+        },
+        "contact": {
+          "$ref": "#/$defs/contact"
+        },
+        "license": {
+          "$ref": "#/$defs/license"
+        },
+        "version": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "title",
+        "version"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "contact": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        },
+        "email": {
+          "type": "string",
+          "format": "email"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "license": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "identifier": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "dependentSchemas": {
+        "identifier": {
+          "not": {
+            "required": [
+              "url"
+            ]
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "server": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object",
+      "type": "object",
+      "properties": {
+        "url": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "description": {
+          "type": "string"
+        },
+        "variables": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/server-variable"
+          }
+        }
+      },
+      "required": [
+        "url"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "server-variable": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object",
+      "type": "object",
+      "properties": {
+        "enum": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "minItems": 1
+        },
+        "default": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "default"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "components": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object",
+      "type": "object",
+      "properties": {
+        "schemas": {
+          "type": "object",
+          "additionalProperties": {
+            "$dynamicRef": "#meta"
+          }
+        },
+        "responses": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/response-or-reference"
+          }
+        },
+        "parameters": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/example-or-reference"
+          }
+        },
+        "requestBodies": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/request-body-or-reference"
+          }
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "securitySchemes": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/security-scheme-or-reference"
+          }
+        },
+        "links": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/link-or-reference"
+          }
+        },
+        "callbacks": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/callbacks-or-reference"
+          }
+        },
+        "pathItems": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/path-item-or-reference"
+          }
+        }
+      },
+      "patternProperties": {
+        "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
+          "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected",
+          "propertyNames": {
+            "pattern": "^[a-zA-Z0-9._-]+$"
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "paths": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object",
+      "type": "object",
+      "patternProperties": {
+        "^/": {
+          "$ref": "#/$defs/path-item-or-reference"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "path-item": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object",
+      "type": "object",
+      "properties": {
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/server"
+          }
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "get": {
+          "$ref": "#/$defs/operation"
+        },
+        "put": {
+          "$ref": "#/$defs/operation"
+        },
+        "post": {
+          "$ref": "#/$defs/operation"
+        },
+        "delete": {
+          "$ref": "#/$defs/operation"
+        },
+        "options": {
+          "$ref": "#/$defs/operation"
+        },
+        "head": {
+          "$ref": "#/$defs/operation"
+        },
+        "patch": {
+          "$ref": "#/$defs/operation"
+        },
+        "trace": {
+          "$ref": "#/$defs/operation"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "path-item-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/path-item"
+      }
+    },
+    "operation": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object",
+      "type": "object",
+      "properties": {
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/$defs/external-documentation"
+        },
+        "operationId": {
+          "type": "string"
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "requestBody": {
+          "$ref": "#/$defs/request-body-or-reference"
+        },
+        "responses": {
+          "$ref": "#/$defs/responses"
+        },
+        "callbacks": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/callbacks-or-reference"
+          }
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "security": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/security-requirement"
+          }
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/server"
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "external-documentation": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "required": [
+        "url"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "parameter": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "in": {
+          "enum": [
+            "query",
+            "header",
+            "path",
+            "cookie"
+          ]
+        },
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "schema": {
+          "$dynamicRef": "#meta"
+        },
+        "content": {
+          "$ref": "#/$defs/content",
+          "minProperties": 1,
+          "maxProperties": 1
+        }
+      },
+      "required": [
+        "name",
+        "in"
+      ],
+      "oneOf": [
+        {
+          "required": [
+            "schema"
+          ]
+        },
+        {
+          "required": [
+            "content"
+          ]
+        }
+      ],
+      "if": {
+        "properties": {
+          "in": {
+            "const": "query"
+          }
+        },
+        "required": [
+          "in"
+        ]
+      },
+      "then": {
+        "properties": {
+          "allowEmptyValue": {
+            "default": false,
+            "type": "boolean"
+          }
+        }
+      },
+      "dependentSchemas": {
+        "schema": {
+          "properties": {
+            "style": {
+              "type": "string"
+            },
+            "explode": {
+              "type": "boolean"
+            }
+          },
+          "allOf": [
+            {
+              "$ref": "#/$defs/examples"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form"
+            }
+          ],
+          "$defs": {
+            "styles-for-path": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "path"
+                  }
+                },
+                "required": [
+                  "in"
+                ]
+              },
+              "then": {
+                "properties": {
+                  "name": {
+                    "pattern": "[^/#?]+$"
+                  },
+                  "style": {
+                    "default": "simple",
+                    "enum": [
+                      "matrix",
+                      "label",
+                      "simple"
+                    ]
+                  },
+                  "required": {
+                    "const": true
+                  }
+                },
+                "required": [
+                  "required"
+                ]
+              }
+            },
+            "styles-for-header": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "header"
+                  }
+                },
+                "required": [
+                  "in"
+                ]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "simple",
+                    "const": "simple"
+                  }
+                }
+              }
+            },
+            "styles-for-query": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "query"
+                  }
+                },
+                "required": [
+                  "in"
+                ]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "form",
+                    "enum": [
+                      "form",
+                      "spaceDelimited",
+                      "pipeDelimited",
+                      "deepObject"
+                    ]
+                  },
+                  "allowReserved": {
+                    "default": false,
+                    "type": "boolean"
+                  }
+                }
+              }
+            },
+            "styles-for-cookie": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "cookie"
+                  }
+                },
+                "required": [
+                  "in"
+                ]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "form",
+                    "const": "form"
+                  }
+                }
+              }
+            },
+            "styles-for-form": {
+              "if": {
+                "properties": {
+                  "style": {
+                    "const": "form"
+                  }
+                },
+                "required": [
+                  "style"
+                ]
+              },
+              "then": {
+                "properties": {
+                  "explode": {
+                    "default": true
+                  }
+                }
+              },
+              "else": {
+                "properties": {
+                  "explode": {
+                    "default": false
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "parameter-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/parameter"
+      }
+    },
+    "request-body": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "content": {
+          "$ref": "#/$defs/content"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "required": [
+        "content"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "request-body-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/request-body"
+      }
+    },
+    "content": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10",
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/$defs/media-type"
+      },
+      "propertyNames": {
+        "format": "media-range"
+      }
+    },
+    "media-type": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object",
+      "type": "object",
+      "properties": {
+        "schema": {
+          "$dynamicRef": "#meta"
+        },
+        "encoding": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/encoding"
+          }
+        }
+      },
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/examples"
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+    "encoding": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object",
+      "type": "object",
+      "properties": {
+        "contentType": {
+          "type": "string",
+          "format": "media-range"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "style": {
+          "default": "form",
+          "enum": [
+            "form",
+            "spaceDelimited",
+            "pipeDelimited",
+            "deepObject"
+          ]
+        },
+        "explode": {
+          "type": "boolean"
+        },
+        "allowReserved": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/encoding/$defs/explode-default"
+        }
+      ],
+      "unevaluatedProperties": false,
+      "$defs": {
+        "explode-default": {
+          "if": {
+            "properties": {
+              "style": {
+                "const": "form"
+              }
+            },
+            "required": [
+              "style"
+            ]
+          },
+          "then": {
+            "properties": {
+              "explode": {
+                "default": true
+              }
+            }
+          },
+          "else": {
+            "properties": {
+              "explode": {
+                "default": false
+              }
+            }
+          }
+        }
+      }
+    },
+    "responses": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object",
+      "type": "object",
+      "properties": {
+        "default": {
+          "$ref": "#/$defs/response-or-reference"
+        }
+      },
+      "patternProperties": {
+        "^[1-5](?:[0-9]{2}|XX)$": {
+          "$ref": "#/$defs/response-or-reference"
+        }
+      },
+      "minProperties": 1,
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false,
+      "if": {
+        "$comment": "either default, or at least one response code property must exist",
+        "patternProperties": {
+          "^[1-5](?:[0-9]{2}|XX)$": false
+        }
+      },
+      "then" : {
+        "required": [ "default" ]
+      }
+    },
+    "response": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "content": {
+          "$ref": "#/$defs/content"
+        },
+        "links": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/link-or-reference"
+          }
+        }
+      },
+      "required": [
+        "description"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "response-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/response"
+      }
+    },
+    "callbacks": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object",
+      "type": "object",
+      "$ref": "#/$defs/specification-extensions",
+      "additionalProperties": {
+        "$ref": "#/$defs/path-item-or-reference"
+      }
+    },
+    "callbacks-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/callbacks"
+      }
+    },
+    "example": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object",
+      "type": "object",
+      "properties": {
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "value": true,
+        "externalValue": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "not": {
+        "required": [
+          "value",
+          "externalValue"
+        ]
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "example-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/example"
+      }
+    },
+    "link": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object",
+      "type": "object",
+      "properties": {
+        "operationRef": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "operationId": {
+          "type": "string"
+        },
+        "parameters": {
+          "$ref": "#/$defs/map-of-strings"
+        },
+        "requestBody": true,
+        "description": {
+          "type": "string"
+        },
+        "body": {
+          "$ref": "#/$defs/server"
+        }
+      },
+      "oneOf": [
+        {
+          "required": [
+            "operationRef"
+          ]
+        },
+        {
+          "required": [
+            "operationId"
+          ]
+        }
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "link-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/link"
+      }
+    },
+    "header": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "schema": {
+          "$dynamicRef": "#meta"
+        },
+        "content": {
+          "$ref": "#/$defs/content",
+          "minProperties": 1,
+          "maxProperties": 1
+        }
+      },
+      "oneOf": [
+        {
+          "required": [
+            "schema"
+          ]
+        },
+        {
+          "required": [
+            "content"
+          ]
+        }
+      ],
+      "dependentSchemas": {
+        "schema": {
+          "properties": {
+            "style": {
+              "default": "simple",
+              "const": "simple"
+            },
+            "explode": {
+              "default": false,
+              "type": "boolean"
+            }
+          },
+          "$ref": "#/$defs/examples"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "header-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/header"
+      }
+    },
+    "tag": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/$defs/external-documentation"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "reference": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object",
+      "type": "object",
+      "properties": {
+        "$ref": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "unevaluatedProperties": false
+    },
+    "schema": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
+      "$dynamicAnchor": "meta",
+      "type": [
+        "object",
+        "boolean"
+      ]
+    },
+    "security-scheme": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object",
+      "type": "object",
+      "properties": {
+        "type": {
+          "enum": [
+            "apiKey",
+            "http",
+            "mutualTLS",
+            "oauth2",
+            "openIdConnect"
+          ]
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "type"
+      ],
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-apikey"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-http"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-http-bearer"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-oauth2"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-oidc"
+        }
+      ],
+      "unevaluatedProperties": false,
+      "$defs": {
+        "type-apikey": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "apiKey"
+              }
+            },
+            "required": [
+              "type"
+            ]
+          },
+          "then": {
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "in": {
+                "enum": [
+                  "query",
+                  "header",
+                  "cookie"
+                ]
+              }
+            },
+            "required": [
+              "name",
+              "in"
+            ]
+          }
+        },
+        "type-http": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "http"
+              }
+            },
+            "required": [
+              "type"
+            ]
+          },
+          "then": {
+            "properties": {
+              "scheme": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "scheme"
+            ]
+          }
+        },
+        "type-http-bearer": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "http"
+              },
+              "scheme": {
+                "type": "string",
+                "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
+              }
+            },
+            "required": [
+              "type",
+              "scheme"
+            ]
+          },
+          "then": {
+            "properties": {
+              "bearerFormat": {
+                "type": "string"
+              }
+            }
+          }
+        },
+        "type-oauth2": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "oauth2"
+              }
+            },
+            "required": [
+              "type"
+            ]
+          },
+          "then": {
+            "properties": {
+              "flows": {
+                "$ref": "#/$defs/oauth-flows"
+              }
+            },
+            "required": [
+              "flows"
+            ]
+          }
+        },
+        "type-oidc": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "openIdConnect"
+              }
+            },
+            "required": [
+              "type"
+            ]
+          },
+          "then": {
+            "properties": {
+              "openIdConnectUrl": {
+                "type": "string",
+                "format": "uri"
+              }
+            },
+            "required": [
+              "openIdConnectUrl"
+            ]
+          }
+        }
+      }
+    },
+    "security-scheme-or-reference": {
+      "if": {
+        "type": "object",
+        "required": [
+          "$ref"
+        ]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/security-scheme"
+      }
+    },
+    "oauth-flows": {
+      "type": "object",
+      "properties": {
+        "implicit": {
+          "$ref": "#/$defs/oauth-flows/$defs/implicit"
+        },
+        "password": {
+          "$ref": "#/$defs/oauth-flows/$defs/password"
+        },
+        "clientCredentials": {
+          "$ref": "#/$defs/oauth-flows/$defs/client-credentials"
+        },
+        "authorizationCode": {
+          "$ref": "#/$defs/oauth-flows/$defs/authorization-code"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false,
+      "$defs": {
+        "implicit": {
+          "type": "object",
+          "properties": {
+            "authorizationUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": [
+            "authorizationUrl",
+            "scopes"
+          ],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "password": {
+          "type": "object",
+          "properties": {
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": [
+            "tokenUrl",
+            "scopes"
+          ],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "client-credentials": {
+          "type": "object",
+          "properties": {
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": [
+            "tokenUrl",
+            "scopes"
+          ],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "authorization-code": {
+          "type": "object",
+          "properties": {
+            "authorizationUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": [
+            "authorizationUrl",
+            "tokenUrl",
+            "scopes"
+          ],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        }
+      }
+    },
+    "security-requirement": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object",
+      "type": "object",
+      "additionalProperties": {
+        "type": "array",
+        "items": {
+          "type": "string"
+        }
+      }
+    },
+    "specification-extensions": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions",
+      "patternProperties": {
+        "^x-": true
+      }
+    },
+    "examples": {
+      "properties": {
+        "example": true,
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/example-or-reference"
+          }
+        }
+      }
+    },
+    "map-of-strings": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "string"
+      }
+    }
+  }
+}
diff --git a/openapi_spec_validator/schemas.py b/openapi_spec_validator/schemas.py
deleted file mode 100644
index 470d88d..0000000
--- a/openapi_spec_validator/schemas.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""OpenAIP spec validator schemas module."""
-import os
-
-from pkg_resources import resource_filename
-from six.moves.urllib import parse, request
-from yaml import safe_load
-
-
-def get_openapi_schema(version):
-    path = 'resources/schemas/v{0}/schema.json'.format(version)
-    path_resource = resource_filename('openapi_spec_validator', path)
-    path_full = os.path.join(os.path.dirname(__file__), path_resource)
-    schema = read_yaml_file(path_full)
-    schema_url = parse.urljoin('file:', request.pathname2url(path_full))
-    return schema, schema_url
-
-
-def read_yaml_file(path):
-    """Open a file, read it and return its contents."""
-    with open(path) as fh:
-        return safe_load(fh)
diff --git a/openapi_spec_validator/schemas/__init__.py b/openapi_spec_validator/schemas/__init__.py
new file mode 100644
index 0000000..8141788
--- /dev/null
+++ b/openapi_spec_validator/schemas/__init__.py
@@ -0,0 +1,29 @@
+"""OpenAIP spec validator schemas module."""
+from functools import partial
+
+from jsonschema.validators import Draft4Validator
+from jsonschema.validators import Draft202012Validator
+from lazy_object_proxy import Proxy
+
+from openapi_spec_validator.schemas.utils import get_schema_content
+
+__all__ = ["schema_v2", "schema_v3", "schema_v30", "schema_v31"]
+
+get_schema_content_v2 = partial(get_schema_content, "2.0")
+get_schema_content_v30 = partial(get_schema_content, "3.0")
+get_schema_content_v31 = partial(get_schema_content, "3.1")
+
+schema_v2 = Proxy(get_schema_content_v2)
+schema_v30 = Proxy(get_schema_content_v30)
+schema_v31 = Proxy(get_schema_content_v31)
+
+# alias to the latest v3 version
+schema_v3 = schema_v31
+
+get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
+get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
+get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
+
+openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
+openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
+openapi_v31_schema_validator = Proxy(get_openapi_v31_schema_validator)
diff --git a/openapi_spec_validator/schemas/types.py b/openapi_spec_validator/schemas/types.py
new file mode 100644
index 0000000..d48c516
--- /dev/null
+++ b/openapi_spec_validator/schemas/types.py
@@ -0,0 +1,6 @@
+from typing import Union
+
+from jsonschema_path.paths import SchemaPath
+from jsonschema_path.typing import Schema
+
+AnySchema = Union[Schema, SchemaPath]
diff --git a/openapi_spec_validator/schemas/utils.py b/openapi_spec_validator/schemas/utils.py
new file mode 100644
index 0000000..7b2fd92
--- /dev/null
+++ b/openapi_spec_validator/schemas/utils.py
@@ -0,0 +1,21 @@
+"""OpenAIP spec validator schemas utils module."""
+from importlib.resources import as_file
+from importlib.resources import files
+from os import path
+from typing import Tuple
+
+from jsonschema_path.readers import FilePathReader
+from jsonschema_path.typing import Schema
+
+
+def get_schema(version: str) -> Tuple[Schema, str]:
+    schema_path = f"resources/schemas/v{version}/schema.json"
+    ref = files("openapi_spec_validator") / schema_path
+    with as_file(ref) as resource_path:
+        schema_path_full = path.join(path.dirname(__file__), resource_path)
+    return FilePathReader(schema_path_full).read()
+
+
+def get_schema_content(version: str) -> Schema:
+    content, _ = get_schema(version)
+    return content
diff --git a/openapi_spec_validator/shortcuts.py b/openapi_spec_validator/shortcuts.py
index 83fff18..70c2c8f 100644
--- a/openapi_spec_validator/shortcuts.py
+++ b/openapi_spec_validator/shortcuts.py
@@ -1,17 +1,98 @@
 """OpenAPI spec validator shortcuts module."""
-from six.moves.urllib import parse
+import warnings
+from typing import Mapping
+from typing import Optional
+from typing import Type
 
+from jsonschema_path import SchemaPath
+from jsonschema_path.handlers import all_urls_handler
+from jsonschema_path.typing import Schema
 
-def validate_spec_factory(validator_callable):
-    def validate(spec, spec_url=''):
-        return validator_callable(spec, spec_url=spec_url)
-    return validate
+from openapi_spec_validator.validation import OpenAPIV2SpecValidator
+from openapi_spec_validator.validation import OpenAPIV30SpecValidator
+from openapi_spec_validator.validation import OpenAPIV31SpecValidator
+from openapi_spec_validator.validation.exceptions import ValidatorDetectError
+from openapi_spec_validator.validation.protocols import SupportsValidation
+from openapi_spec_validator.validation.types import SpecValidatorType
+from openapi_spec_validator.validation.validators import SpecValidator
+from openapi_spec_validator.versions import consts as versions
+from openapi_spec_validator.versions.datatypes import SpecVersion
+from openapi_spec_validator.versions.exceptions import OpenAPIVersionNotFound
+from openapi_spec_validator.versions.shortcuts import get_spec_version
 
+SPEC2VALIDATOR: Mapping[SpecVersion, SpecValidatorType] = {
+    versions.OPENAPIV2: OpenAPIV2SpecValidator,
+    versions.OPENAPIV30: OpenAPIV30SpecValidator,
+    versions.OPENAPIV31: OpenAPIV31SpecValidator,
+}
 
-def validate_spec_url_factory(validator_callable, handlers):
-    def validate(url):
-        result = parse.urlparse(url)
-        handler = handlers[result.scheme]
-        spec = handler(url)
-        return validator_callable(spec, spec_url=url)
-    return validate
+
+def get_validator_cls(spec: Schema) -> SpecValidatorType:
+    try:
+        spec_version = get_spec_version(spec)
+    # backward compatibility
+    except OpenAPIVersionNotFound:
+        raise ValidatorDetectError
+    return SPEC2VALIDATOR[spec_version]
+
+
+def validate(
+    spec: Schema,
+    base_uri: str = "",
+    cls: Optional[SpecValidatorType] = None,
+) -> None:
+    if cls is None:
+        cls = get_validator_cls(spec)
+    sp = SchemaPath.from_dict(spec, base_uri=base_uri)
+    v = cls(sp)
+    return v.validate()
+
+
+def validate_url(
+    spec_url: str,
+    cls: Optional[Type[SpecValidator]] = None,
+) -> None:
+    spec = all_urls_handler(spec_url)
+    return validate(spec, base_uri=spec_url, cls=cls)
+
+
+def validate_spec(
+    spec: Schema,
+    base_uri: str = "",
+    validator: Optional[SupportsValidation] = None,
+    cls: Optional[SpecValidatorType] = None,
+    spec_url: Optional[str] = None,
+) -> None:
+    warnings.warn(
+        "validate_spec shortcut is deprecated. Use validate instead.",
+        DeprecationWarning,
+    )
+    if validator is not None:
+        warnings.warn(
+            "validator parameter is deprecated. Use cls instead.",
+            DeprecationWarning,
+        )
+        return validator.validate(spec, base_uri=base_uri, spec_url=spec_url)
+    if cls is None:
+        cls = get_validator_cls(spec)
+    v = cls(spec)
+    return v.validate()
+
+
+def validate_spec_url(
+    spec_url: str,
+    validator: Optional[SupportsValidation] = None,
+    cls: Optional[Type[SpecValidator]] = None,
+) -> None:
+    warnings.warn(
+        "validate_spec_url shortcut is deprecated. Use validate_url instead.",
+        DeprecationWarning,
+    )
+    if validator is not None:
+        warnings.warn(
+            "validator parameter is deprecated. Use cls instead.",
+            DeprecationWarning,
+        )
+        spec = all_urls_handler(spec_url)
+        return validator.validate(spec, base_uri=spec_url)
+    return validate_url(spec_url, cls=cls)
diff --git a/openapi_spec_validator/validation/__init__.py b/openapi_spec_validator/validation/__init__.py
new file mode 100644
index 0000000..3450616
--- /dev/null
+++ b/openapi_spec_validator/validation/__init__.py
@@ -0,0 +1,55 @@
+from openapi_spec_validator.validation.proxies import DetectValidatorProxy
+from openapi_spec_validator.validation.proxies import SpecValidatorProxy
+from openapi_spec_validator.validation.validators import OpenAPIV2SpecValidator
+from openapi_spec_validator.validation.validators import (
+    OpenAPIV30SpecValidator,
+)
+from openapi_spec_validator.validation.validators import (
+    OpenAPIV31SpecValidator,
+)
+
+__all__ = [
+    "openapi_v2_spec_validator",
+    "openapi_v3_spec_validator",
+    "openapi_v30_spec_validator",
+    "openapi_v31_spec_validator",
+    "openapi_spec_validator_proxy",
+    "OpenAPIV2SpecValidator",
+    "OpenAPIV3SpecValidator",
+    "OpenAPIV30SpecValidator",
+    "OpenAPIV31SpecValidator",
+]
+
+# v2.0 spec
+openapi_v2_spec_validator = SpecValidatorProxy(
+    OpenAPIV2SpecValidator,
+    deprecated="openapi_v2_spec_validator",
+    use="OpenAPIV2SpecValidator",
+)
+
+# v3.0 spec
+openapi_v30_spec_validator = SpecValidatorProxy(
+    OpenAPIV30SpecValidator,
+    deprecated="openapi_v30_spec_validator",
+    use="OpenAPIV30SpecValidator",
+)
+
+# v3.1 spec
+openapi_v31_spec_validator = SpecValidatorProxy(
+    OpenAPIV31SpecValidator,
+    deprecated="openapi_v31_spec_validator",
+    use="OpenAPIV31SpecValidator",
+)
+
+# alias to the latest v3 version
+openapi_v3_spec_validator = openapi_v31_spec_validator
+OpenAPIV3SpecValidator = OpenAPIV31SpecValidator
+
+# detect version spec
+openapi_spec_validator_proxy = DetectValidatorProxy(
+    {
+        ("swagger", "2.0"): openapi_v2_spec_validator,
+        ("openapi", "3.0"): openapi_v30_spec_validator,
+        ("openapi", "3.1"): openapi_v31_spec_validator,
+    },
+)
diff --git a/openapi_spec_validator/validation/caches.py b/openapi_spec_validator/validation/caches.py
new file mode 100644
index 0000000..acc6b36
--- /dev/null
+++ b/openapi_spec_validator/validation/caches.py
@@ -0,0 +1,65 @@
+from typing import Generic
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import TypeVar
+
+T = TypeVar("T")
+
+
+class CachedIterable(Iterable[T], Generic[T]):
+    """
+    A cache-implementing wrapper for an iterator.
+    Note that this is class is `Iterable[T]` rather than `Iterator[T]`.
+    It should not be iterated by his own.
+    """
+
+    cache: List[T]
+    iter: Iterator[T]
+    completed: bool
+
+    def __init__(self, it: Iterator[T]):
+        self.iter = iter(it)
+        self.cache = list()
+        self.completed = False
+
+    def __iter__(self) -> Iterator[T]:
+        return CachedIterator(self)
+
+    def __next__(self) -> T:
+        try:
+            item = next(self.iter)
+        except StopIteration:
+            self.completed = True
+            raise
+        else:
+            self.cache.append(item)
+            return item
+
+    def __del__(self) -> None:
+        del self.cache
+
+
+class CachedIterator(Iterator[T], Generic[T]):
+    """
+    A cache-using wrapper for an iterator.
+    This class is only constructed by `CachedIterable` and cannot be used without it.
+    """
+
+    parent: CachedIterable[T]
+    position: int
+
+    def __init__(self, parent: CachedIterable[T]):
+        self.parent = parent
+        self.position = 0
+
+    def __next__(self) -> T:
+        if self.position < len(self.parent.cache):
+            item = self.parent.cache[self.position]
+        elif self.parent.completed:
+            raise StopIteration
+        else:
+            item = next(self.parent)
+
+        self.position += 1
+        return item
diff --git a/openapi_spec_validator/validation/decorators.py b/openapi_spec_validator/validation/decorators.py
new file mode 100644
index 0000000..191c035
--- /dev/null
+++ b/openapi_spec_validator/validation/decorators.py
@@ -0,0 +1,56 @@
+"""OpenAPI spec validator validation decorators module."""
+import logging
+from functools import wraps
+from typing import Any
+from typing import Callable
+from typing import Iterable
+from typing import Iterator
+from typing import TypeVar
+
+from jsonschema.exceptions import ValidationError
+
+from openapi_spec_validator.validation.caches import CachedIterable
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+
+Args = TypeVar("Args")
+T = TypeVar("T")
+
+log = logging.getLogger(__name__)
+
+
+def wraps_errors(
+    func: Callable[..., Any]
+) -> Callable[..., Iterator[ValidationError]]:
+    @wraps(func)
+    def wrapper(*args: Any, **kwds: Any) -> Iterator[ValidationError]:
+        errors = func(*args, **kwds)
+        for err in errors:
+            if not isinstance(err, OpenAPIValidationError):
+                # wrap other exceptions with library specific version
+                yield OpenAPIValidationError.create_from(err)
+            else:
+                yield err
+
+    return wrapper
+
+
+def wraps_cached_iter(
+    func: Callable[[Args], Iterator[T]]
+) -> Callable[[Args], CachedIterable[T]]:
+    @wraps(func)
+    def wrapper(*args: Any, **kwargs: Any) -> CachedIterable[T]:
+        result = func(*args, **kwargs)
+        return CachedIterable(result)
+
+    return wrapper
+
+
+def unwraps_iter(
+    func: Callable[[Args], Iterable[T]]
+) -> Callable[[Args], Iterator[T]]:
+    @wraps(func)
+    def wrapper(*args: Any, **kwargs: Any) -> Iterator[T]:
+        result = func(*args, **kwargs)
+        return iter(result)
+
+    return wrapper
diff --git a/openapi_spec_validator/validation/exceptions.py b/openapi_spec_validator/validation/exceptions.py
new file mode 100644
index 0000000..e312782
--- /dev/null
+++ b/openapi_spec_validator/validation/exceptions.py
@@ -0,0 +1,27 @@
+from jsonschema.exceptions import ValidationError
+
+from openapi_spec_validator.exceptions import OpenAPISpecValidatorError
+
+
+class ValidatorDetectError(OpenAPISpecValidatorError):
+    pass
+
+
+class OpenAPIValidationError(ValidationError):  # type: ignore
+    pass
+
+
+class ExtraParametersError(OpenAPIValidationError):
+    pass
+
+
+class ParameterDuplicateError(OpenAPIValidationError):
+    pass
+
+
+class UnresolvableParameterError(OpenAPIValidationError):
+    pass
+
+
+class DuplicateOperationIDError(OpenAPIValidationError):
+    pass
diff --git a/openapi_spec_validator/validation/keywords.py b/openapi_spec_validator/validation/keywords.py
new file mode 100644
index 0000000..877a3fe
--- /dev/null
+++ b/openapi_spec_validator/validation/keywords.py
@@ -0,0 +1,450 @@
+import string
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import Sequence
+from typing import Iterator
+from typing import List
+from typing import Optional
+from typing import cast
+
+from jsonschema._format import FormatChecker
+from jsonschema.exceptions import ValidationError
+from jsonschema.protocols import Validator
+from jsonschema_path.paths import SchemaPath
+from openapi_schema_validator import oas30_format_checker
+from openapi_schema_validator import oas31_format_checker
+from openapi_schema_validator.validators import OAS30Validator
+from openapi_schema_validator.validators import OAS31Validator
+
+from openapi_spec_validator.validation.exceptions import (
+    DuplicateOperationIDError,
+)
+from openapi_spec_validator.validation.exceptions import ExtraParametersError
+from openapi_spec_validator.validation.exceptions import (
+    ParameterDuplicateError,
+)
+from openapi_spec_validator.validation.exceptions import (
+    UnresolvableParameterError,
+)
+
+if TYPE_CHECKING:
+    from openapi_spec_validator.validation.registries import (
+        KeywordValidatorRegistry,
+    )
+
+
+class KeywordValidator:
+    def __init__(self, registry: "KeywordValidatorRegistry"):
+        self.registry = registry
+
+
+class ValueValidator(KeywordValidator):
+    value_validator_cls: Validator = NotImplemented
+    value_validator_format_checker: FormatChecker = NotImplemented
+
+    def __call__(
+        self, schema: SchemaPath, value: Any
+    ) -> Iterator[ValidationError]:
+        with schema.resolve() as resolved:
+            value_validator = self.value_validator_cls(
+                resolved.contents,
+                _resolver=resolved.resolver,
+                format_checker=self.value_validator_format_checker,
+            )
+            yield from value_validator.iter_errors(value)
+
+
+class OpenAPIV30ValueValidator(ValueValidator):
+    value_validator_cls = OAS30Validator
+    value_validator_format_checker = oas30_format_checker
+
+
+class OpenAPIV31ValueValidator(ValueValidator):
+    value_validator_cls = OAS31Validator
+    value_validator_format_checker = oas31_format_checker
+
+
+class SchemaValidator(KeywordValidator):
+    def __init__(self, registry: "KeywordValidatorRegistry"):
+        super().__init__(registry)
+
+        self.schema_ids_registry: Optional[List[int]] = []
+
+    @property
+    def default_validator(self) -> ValueValidator:
+        return cast(ValueValidator, self.registry["default"])
+
+    def _collect_properties(self, schema: SchemaPath) -> set[str]:
+        """Return *all* property names reachable from this schema."""
+        props: set[str] = set()
+
+        if "properties" in schema:
+            schema_props = (schema / "properties").keys()
+            props.update(cast(Sequence[str], schema_props))
+
+        for kw in ("allOf", "anyOf", "oneOf"):
+            if kw in schema:
+                for sub in schema / kw:
+                    props.update(self._collect_properties(sub))
+
+        if "items" in schema:
+            props.update(self._collect_properties(schema / "items"))
+
+        if "not" in schema:
+            props.update(self._collect_properties(schema / "not"))
+
+        return props
+
+    def __call__(
+        self, schema: SchemaPath, require_properties: bool = True
+    ) -> Iterator[ValidationError]:
+        schema_value = schema.read_value()
+        if not hasattr(schema_value, "__getitem__"):
+            return
+
+        assert self.schema_ids_registry is not None
+        schema_id = id(schema_value)
+        if schema_id in self.schema_ids_registry:
+            return
+        self.schema_ids_registry.append(schema_id)
+
+        nested_properties = []
+        if "allOf" in schema:
+            all_of = schema / "allOf"
+            for inner_schema in all_of:
+                yield from self(inner_schema, require_properties=False)
+                nested_properties += list(self._collect_properties(inner_schema))
+
+
+        if "anyOf" in schema:
+            any_of = schema / "anyOf"
+            for inner_schema in any_of:
+                yield from self(
+                    inner_schema,
+                    require_properties=False,
+                )
+
+        if "oneOf" in schema:
+            one_of = schema / "oneOf"
+            for inner_schema in one_of:
+                yield from self(
+                    inner_schema,
+                    require_properties=False,
+                )
+
+        if "not" in schema:
+            not_schema = schema / "not"
+            yield from self(
+                not_schema,
+                require_properties=False,
+            )
+
+        if "items" in schema:
+            array_schema = schema / "items"
+            yield from self(
+                array_schema,
+                require_properties=False,
+            )
+
+        if "properties" in schema:
+            props = schema / "properties"
+            for _, prop_schema in props.items():
+                yield from self(
+                    prop_schema,
+                    require_properties=False,
+                )
+
+        required = "required" in schema and (schema / "required").read_value() or []
+        properties = "properties" in schema and (schema / "properties").keys() or []
+        if "allOf" in schema:
+            extra_properties = list(
+                set(required) - set(properties) - set(nested_properties)
+            )
+        else:
+            extra_properties = []
+
+        if extra_properties and require_properties:
+            yield ExtraParametersError(
+                f"Required list has not defined properties: {extra_properties}"
+            )
+
+        if "default" in schema:
+            default_value = (schema / "default").read_value()
+            nullable_value = False
+            if "nullable" in schema:
+                nullable_value = (schema / "nullable").read_value()
+            if default_value is not None or nullable_value is not True:
+                yield from self.default_validator(schema, default_value)
+
+
+class SchemasValidator(KeywordValidator):
+    @property
+    def schema_validator(self) -> SchemaValidator:
+        return cast(SchemaValidator, self.registry["schema"])
+
+    def __call__(self, schemas: SchemaPath) -> Iterator[ValidationError]:
+        for _, schema in schemas.items():
+            yield from self.schema_validator(schema)
+
+
+class ParameterValidator(KeywordValidator):
+    @property
+    def schema_validator(self) -> SchemaValidator:
+        return cast(SchemaValidator, self.registry["schema"])
+
+    def __call__(self, parameter: SchemaPath) -> Iterator[ValidationError]:
+        if "schema" in parameter:
+            schema = parameter / "schema"
+            yield from self.schema_validator(schema)
+
+
+class OpenAPIV2ParameterValidator(ParameterValidator):
+    @property
+    def default_validator(self) -> ValueValidator:
+        return cast(ValueValidator, self.registry["default"])
+
+    def __call__(self, parameter: SchemaPath) -> Iterator[ValidationError]:
+        yield from super().__call__(parameter)
+
+        if "default" in parameter:
+            # only possible in swagger 2.0
+            if "default" in parameter:
+                default_value = (parameter / "default").read_value()
+                yield from self.default_validator(parameter, default_value)
+
+
+class ParametersValidator(KeywordValidator):
+    @property
+    def parameter_validator(self) -> ParameterValidator:
+        return cast(ParameterValidator, self.registry["parameter"])
+
+    def __call__(self, parameters: SchemaPath) -> Iterator[ValidationError]:
+        seen = set()
+        for parameter in parameters:
+            yield from self.parameter_validator(parameter)
+
+            key = (parameter["name"], parameter["in"])
+            if key in seen:
+                yield ParameterDuplicateError(
+                    f"Duplicate parameter `{parameter['name']}`"
+                )
+            seen.add(key)
+
+
+class MediaTypeValidator(KeywordValidator):
+    @property
+    def schema_validator(self) -> SchemaValidator:
+        return cast(SchemaValidator, self.registry["schema"])
+
+    def __call__(
+        self, mimetype: str, media_type: SchemaPath
+    ) -> Iterator[ValidationError]:
+        if "schema" in media_type:
+            schema = media_type / "schema"
+            yield from self.schema_validator(schema)
+
+
+class ContentValidator(KeywordValidator):
+    @property
+    def media_type_validator(self) -> MediaTypeValidator:
+        return cast(MediaTypeValidator, self.registry["mediaType"])
+
+    def __call__(self, content: SchemaPath) -> Iterator[ValidationError]:
+        for mimetype, media_type in content.items():
+            assert isinstance(mimetype, str)
+            yield from self.media_type_validator(mimetype, media_type)
+
+
+class ResponseValidator(KeywordValidator):
+    def __call__(
+        self, response_code: str, response: SchemaPath
+    ) -> Iterator[ValidationError]:
+        raise NotImplementedError
+
+
+class OpenAPIV2ResponseValidator(ResponseValidator):
+    @property
+    def schema_validator(self) -> SchemaValidator:
+        return cast(SchemaValidator, self.registry["schema"])
+
+    def __call__(
+        self, response_code: str, response: SchemaPath
+    ) -> Iterator[ValidationError]:
+        # openapi 2
+        if "schema" in response:
+            schema = response / "schema"
+            yield from self.schema_validator(schema)
+
+
+class OpenAPIV3ResponseValidator(ResponseValidator):
+    @property
+    def content_validator(self) -> ContentValidator:
+        return cast(ContentValidator, self.registry["content"])
+
+    def __call__(
+        self, response_code: str, response: SchemaPath
+    ) -> Iterator[ValidationError]:
+        # openapi 3
+        if "content" in response:
+            content = response / "content"
+            yield from self.content_validator(content)
+
+
+class ResponsesValidator(KeywordValidator):
+    @property
+    def response_validator(self) -> ResponseValidator:
+        return cast(ResponseValidator, self.registry["response"])
+
+    def __call__(self, responses: SchemaPath) -> Iterator[ValidationError]:
+        for response_code, response in responses.items():
+            assert isinstance(response_code, str)
+            yield from self.response_validator(response_code, response)
+
+
+class OperationValidator(KeywordValidator):
+    def __init__(self, registry: "KeywordValidatorRegistry"):
+        super().__init__(registry)
+
+        self.operation_ids_registry: Optional[List[str]] = []
+
+    @property
+    def responses_validator(self) -> ResponsesValidator:
+        return cast(ResponsesValidator, self.registry["responses"])
+
+    @property
+    def parameters_validator(self) -> ParametersValidator:
+        return cast(ParametersValidator, self.registry["parameters"])
+
+    def __call__(
+        self,
+        url: str,
+        name: str,
+        operation: SchemaPath,
+        path_parameters: Optional[SchemaPath],
+    ) -> Iterator[ValidationError]:
+        assert self.operation_ids_registry is not None
+
+        if "operationId" in operation:
+            operation_id_value = (operation / "operationId").read_value()
+            if (
+                operation_id_value is not None
+                and operation_id_value in self.operation_ids_registry
+            ):
+                yield DuplicateOperationIDError(
+                    f"Operation ID '{operation_id_value}' for "
+                    f"'{name}' in '{url}' is not unique"
+                )
+            self.operation_ids_registry.append(operation_id_value)
+
+        if "responses" in operation:
+            responses = operation / "responses"
+            yield from self.responses_validator(responses)
+
+        names = []
+
+        parameters = None
+        if "parameters" in operation:
+            parameters = operation / "parameters"
+            yield from self.parameters_validator(parameters)
+            names += list(self._get_path_param_names(parameters))
+
+        if path_parameters is not None:
+            names += list(self._get_path_param_names(path_parameters))
+
+        all_params = list(set(names))
+
+        for path in self._get_path_params_from_url(url):
+            if path not in all_params:
+                yield UnresolvableParameterError(
+                    "Path parameter '{}' for '{}' operation in '{}' "
+                    "was not resolved".format(path, name, url)
+                )
+        return
+
+    def _get_path_param_names(self, params: SchemaPath) -> Iterator[str]:
+        for param in params:
+            if param["in"] == "path":
+                yield param["name"]
+
+    def _get_path_params_from_url(self, url: str) -> Iterator[str]:
+        formatter = string.Formatter()
+        path_params = [item[1] for item in formatter.parse(url)]
+        return filter(None, path_params)
+
+
+class PathValidator(KeywordValidator):
+    OPERATIONS = [
+        "get",
+        "put",
+        "post",
+        "delete",
+        "options",
+        "head",
+        "patch",
+        "trace",
+    ]
+
+    @property
+    def parameters_validator(self) -> ParametersValidator:
+        return cast(ParametersValidator, self.registry["parameters"])
+
+    @property
+    def operation_validator(self) -> OperationValidator:
+        return cast(OperationValidator, self.registry["operation"])
+
+    def __call__(
+        self, url: str, path_item: SchemaPath
+    ) -> Iterator[ValidationError]:
+        parameters = None
+        if "parameters" in path_item:
+            parameters = path_item / "parameters"
+            yield from self.parameters_validator(parameters)
+
+        for field_name, operation in path_item.items():
+            assert isinstance(field_name, str)
+            if field_name not in self.OPERATIONS:
+                continue
+
+            yield from self.operation_validator(
+                url, field_name, operation, parameters
+            )
+
+
+class PathsValidator(KeywordValidator):
+    @property
+    def path_validator(self) -> PathValidator:
+        return cast(PathValidator, self.registry["path"])
+
+    def __call__(self, paths: SchemaPath) -> Iterator[ValidationError]:
+        for url, path_item in paths.items():
+            assert isinstance(url, str)
+            yield from self.path_validator(url, path_item)
+
+
+class ComponentsValidator(KeywordValidator):
+    @property
+    def schemas_validator(self) -> SchemasValidator:
+        return cast(SchemasValidator, self.registry["schemas"])
+
+    def __call__(self, components: SchemaPath) -> Iterator[ValidationError]:
+        if "schemas" in components:
+            schemas = components / "schemas"
+            yield from self.schemas_validator(schemas)
+
+
+class RootValidator(KeywordValidator):
+    @property
+    def paths_validator(self) -> PathsValidator:
+        return cast(PathsValidator, self.registry["paths"])
+
+    @property
+    def components_validator(self) -> ComponentsValidator:
+        return cast(ComponentsValidator, self.registry["components"])
+
+    def __call__(self, spec: SchemaPath) -> Iterator[ValidationError]:
+        if "paths" in spec:
+            paths = spec / "paths"
+            yield from self.paths_validator(paths)
+        if "components" in spec:
+            components = spec / "components"
+            yield from self.components_validator(components)
diff --git a/openapi_spec_validator/validation/protocols.py b/openapi_spec_validator/validation/protocols.py
new file mode 100644
index 0000000..3e7403c
--- /dev/null
+++ b/openapi_spec_validator/validation/protocols.py
@@ -0,0 +1,30 @@
+from typing import Iterator
+from typing import Optional
+from typing import Protocol
+from typing import runtime_checkable
+
+from jsonschema_path.typing import Schema
+
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+
+
+@runtime_checkable
+class SupportsValidation(Protocol):
+    def is_valid(self, instance: Schema) -> bool:
+        ...
+
+    def iter_errors(
+        self,
+        instance: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> Iterator[OpenAPIValidationError]:
+        ...
+
+    def validate(
+        self,
+        instance: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> None:
+        ...
diff --git a/openapi_spec_validator/validation/proxies.py b/openapi_spec_validator/validation/proxies.py
new file mode 100644
index 0000000..964266d
--- /dev/null
+++ b/openapi_spec_validator/validation/proxies.py
@@ -0,0 +1,101 @@
+"""OpenAPI spec validator validation proxies module."""
+import warnings
+from collections.abc import Hashable
+from typing import Any
+from typing import Iterator
+from typing import Mapping
+from typing import Optional
+from typing import Tuple
+
+from jsonschema.exceptions import ValidationError
+from jsonschema_path.typing import Schema
+
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+from openapi_spec_validator.validation.exceptions import ValidatorDetectError
+from openapi_spec_validator.validation.types import SpecValidatorType
+
+
+class SpecValidatorProxy:
+    def __init__(
+        self,
+        cls: SpecValidatorType,
+        deprecated: str = "SpecValidator",
+        use: Optional[str] = None,
+    ):
+        self.cls = cls
+
+        self.deprecated = deprecated
+        self.use = use or self.cls.__name__
+
+    def validate(
+        self,
+        schema: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> None:
+        for err in self.iter_errors(
+            schema,
+            base_uri=base_uri,
+            spec_url=spec_url,
+        ):
+            raise err
+
+    def is_valid(self, schema: Schema) -> bool:
+        error = next(self.iter_errors(schema), None)
+        return error is None
+
+    def iter_errors(
+        self,
+        schema: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> Iterator[ValidationError]:
+        warnings.warn(
+            f"{self.deprecated} is deprecated. Use {self.use} instead.",
+            DeprecationWarning,
+        )
+        validator = self.cls(schema, base_uri=base_uri, spec_url=spec_url)
+        return validator.iter_errors()
+
+
+class DetectValidatorProxy:
+    def __init__(self, choices: Mapping[Tuple[str, str], SpecValidatorProxy]):
+        self.choices = choices
+
+    def detect(self, instance: Schema) -> SpecValidatorProxy:
+        for (key, value), validator in self.choices.items():
+            if key in instance and instance[key].startswith(value):
+                return validator
+        raise ValidatorDetectError("Spec schema version not detected")
+
+    def validate(
+        self,
+        instance: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> None:
+        validator = self.detect(instance)
+        for err in validator.iter_errors(
+            instance, base_uri=base_uri, spec_url=spec_url
+        ):
+            raise err
+
+    def is_valid(self, instance: Schema) -> bool:
+        validator = self.detect(instance)
+        error = next(validator.iter_errors(instance), None)
+        return error is None
+
+    def iter_errors(
+        self,
+        instance: Schema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> Iterator[OpenAPIValidationError]:
+        warnings.warn(
+            "openapi_spec_validator_proxy is deprecated.",
+            DeprecationWarning,
+        )
+        validator = self.detect(instance)
+        yield from validator.iter_errors(
+            instance, base_uri=base_uri, spec_url=spec_url
+        )
diff --git a/openapi_spec_validator/validation/registries.py b/openapi_spec_validator/validation/registries.py
new file mode 100644
index 0000000..b9ddc5e
--- /dev/null
+++ b/openapi_spec_validator/validation/registries.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from typing import DefaultDict
+from typing import Mapping
+from typing import Type
+
+from openapi_spec_validator.validation.keywords import KeywordValidator
+
+
+class KeywordValidatorRegistry(DefaultDict[str, KeywordValidator]):
+    def __init__(
+        self, keyword_validators: Mapping[str, Type[KeywordValidator]]
+    ):
+        super().__init__()
+        self.keyword_validators = keyword_validators
+
+    def __missing__(self, keyword: str) -> KeywordValidator:
+        if keyword not in self.keyword_validators:
+            raise KeyError(keyword)
+        cls = self.keyword_validators[keyword]
+        self[keyword] = cls(self)
+        return self[keyword]
diff --git a/openapi_spec_validator/validation/types.py b/openapi_spec_validator/validation/types.py
new file mode 100644
index 0000000..90d83ba
--- /dev/null
+++ b/openapi_spec_validator/validation/types.py
@@ -0,0 +1,5 @@
+from typing import Type
+
+from openapi_spec_validator.validation.validators import SpecValidator
+
+SpecValidatorType = Type[SpecValidator]
diff --git a/openapi_spec_validator/validation/validators.py b/openapi_spec_validator/validation/validators.py
new file mode 100644
index 0000000..397f022
--- /dev/null
+++ b/openapi_spec_validator/validation/validators.py
@@ -0,0 +1,152 @@
+"""OpenAPI spec validator validation validators module."""
+import logging
+import warnings
+from functools import lru_cache
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import Type
+from typing import cast
+
+from jsonschema.exceptions import ValidationError
+from jsonschema.protocols import Validator
+from jsonschema_path.handlers import default_handlers
+from jsonschema_path.paths import SchemaPath
+
+from openapi_spec_validator.schemas import openapi_v2_schema_validator
+from openapi_spec_validator.schemas import openapi_v30_schema_validator
+from openapi_spec_validator.schemas import openapi_v31_schema_validator
+from openapi_spec_validator.schemas.types import AnySchema
+from openapi_spec_validator.validation import keywords
+from openapi_spec_validator.validation.decorators import unwraps_iter
+from openapi_spec_validator.validation.decorators import wraps_cached_iter
+from openapi_spec_validator.validation.decorators import wraps_errors
+from openapi_spec_validator.validation.registries import (
+    KeywordValidatorRegistry,
+)
+
+log = logging.getLogger(__name__)
+
+
+class SpecValidator:
+    resolver_handlers = default_handlers
+    keyword_validators: Mapping[str, Type[keywords.KeywordValidator]] = {
+        "__root__": keywords.RootValidator,
+    }
+    root_keywords: List[str] = []
+    schema_validator: Validator = NotImplemented
+
+    def __init__(
+        self,
+        schema: AnySchema,
+        base_uri: str = "",
+        spec_url: Optional[str] = None,
+    ) -> None:
+        if spec_url is not None:
+            warnings.warn(
+                "spec_url parameter is deprecated. " "Use base_uri instead.",
+                DeprecationWarning,
+            )
+            base_uri = spec_url
+        self.base_uri = base_uri
+
+        if isinstance(schema, SchemaPath):
+            self.schema_path = schema
+            self.schema = schema.read_value()
+        else:
+            self.schema = schema
+            self.schema_path = SchemaPath.from_dict(
+                self.schema,
+                base_uri=self.base_uri,
+                handlers=self.resolver_handlers,
+            )
+
+        self.keyword_validators_registry = KeywordValidatorRegistry(
+            self.keyword_validators
+        )
+
+    def validate(self) -> None:
+        for err in self.iter_errors():
+            raise err
+
+    def is_valid(self) -> bool:
+        error = next(self.iter_errors(), None)
+        return error is None
+
+    @property
+    def root_validator(self) -> keywords.RootValidator:
+        return cast(
+            keywords.RootValidator,
+            self.keyword_validators_registry["__root__"],
+        )
+
+    @unwraps_iter
+    @lru_cache(maxsize=None)
+    @wraps_cached_iter
+    @wraps_errors
+    def iter_errors(self) -> Iterator[ValidationError]:
+        yield from self.schema_validator.iter_errors(self.schema)
+
+        yield from self.root_validator(self.schema_path)
+
+
+class OpenAPIV2SpecValidator(SpecValidator):
+    schema_validator = openapi_v2_schema_validator
+    keyword_validators = {
+        "__root__": keywords.RootValidator,
+        "components": keywords.ComponentsValidator,
+        "default": keywords.OpenAPIV30ValueValidator,
+        "operation": keywords.OperationValidator,
+        "parameter": keywords.OpenAPIV2ParameterValidator,
+        "parameters": keywords.ParametersValidator,
+        "paths": keywords.PathsValidator,
+        "path": keywords.PathValidator,
+        "response": keywords.OpenAPIV2ResponseValidator,
+        "responses": keywords.ResponsesValidator,
+        "schema": keywords.SchemaValidator,
+        "schemas": keywords.SchemasValidator,
+    }
+    root_keywords = ["paths", "components"]
+
+
+class OpenAPIV30SpecValidator(SpecValidator):
+    schema_validator = openapi_v30_schema_validator
+    keyword_validators = {
+        "__root__": keywords.RootValidator,
+        "components": keywords.ComponentsValidator,
+        "content": keywords.ContentValidator,
+        "default": keywords.OpenAPIV30ValueValidator,
+        "mediaType": keywords.MediaTypeValidator,
+        "operation": keywords.OperationValidator,
+        "parameter": keywords.ParameterValidator,
+        "parameters": keywords.ParametersValidator,
+        "paths": keywords.PathsValidator,
+        "path": keywords.PathValidator,
+        "response": keywords.OpenAPIV3ResponseValidator,
+        "responses": keywords.ResponsesValidator,
+        "schema": keywords.SchemaValidator,
+        "schemas": keywords.SchemasValidator,
+    }
+    root_keywords = ["paths", "components"]
+
+
+class OpenAPIV31SpecValidator(SpecValidator):
+    schema_validator = openapi_v31_schema_validator
+    keyword_validators = {
+        "__root__": keywords.RootValidator,
+        "components": keywords.ComponentsValidator,
+        "content": keywords.ContentValidator,
+        "default": keywords.OpenAPIV31ValueValidator,
+        "mediaType": keywords.MediaTypeValidator,
+        "operation": keywords.OperationValidator,
+        "parameter": keywords.ParameterValidator,
+        "parameters": keywords.ParametersValidator,
+        "paths": keywords.PathsValidator,
+        "path": keywords.PathValidator,
+        "response": keywords.OpenAPIV3ResponseValidator,
+        "responses": keywords.ResponsesValidator,
+        "schema": keywords.SchemaValidator,
+        "schemas": keywords.SchemasValidator,
+    }
+    root_keywords = ["paths", "components"]
diff --git a/openapi_spec_validator/validators.py b/openapi_spec_validator/validators.py
deleted file mode 100644
index 3733ced..0000000
--- a/openapi_spec_validator/validators.py
+++ /dev/null
@@ -1,240 +0,0 @@
-import logging
-import string
-
-from jsonschema.validators import RefResolver
-from six import iteritems
-
-from openapi_spec_validator.exceptions import (
-    ParameterDuplicateError, ExtraParametersError, UnresolvableParameterError,
-)
-from openapi_spec_validator.managers import ResolverManager
-
-log = logging.getLogger(__name__)
-
-
-def is_ref(spec):
-    return isinstance(spec, dict) and '$ref' in spec
-
-
-class Dereferencer(object):
-
-    def __init__(self, spec_resolver):
-        self.resolver_manager = ResolverManager(spec_resolver)
-
-    def dereference(self, item):
-        log.debug("Dereferencing %s", item)
-        if item is None or not is_ref(item):
-            return item
-
-        ref = item['$ref']
-        with self.resolver_manager.in_scope(item) as resolver:
-            with resolver.resolving(ref) as target:
-                return target
-
-
-class SpecValidator(object):
-
-    def __init__(self, validator_factory, resolver_handlers):
-        self.validator_factory = validator_factory
-        self.resolver_handlers = resolver_handlers
-
-    def validate(self, spec, spec_url=''):
-        for error in self.iter_errors(spec, spec_url=spec_url):
-            raise error
-
-    def iter_errors(self, spec, spec_url=''):
-        spec_resolver = self._get_resolver(spec_url, spec)
-        dereferencer = self._get_dereferencer(spec_resolver)
-
-        validator = self._get_validator(spec_resolver)
-        yield from validator.iter_errors(spec)
-
-        paths = spec.get('paths', {})
-        yield from self._iter_paths_errors(paths, dereferencer)
-
-        components = spec.get('components', {})
-        yield from self._iter_components_errors(components, dereferencer)
-
-    def _get_resolver(self, base_uri, referrer):
-        return RefResolver(
-            base_uri, referrer, handlers=self.resolver_handlers)
-
-    def _get_dereferencer(self, spec_resolver):
-        return Dereferencer(spec_resolver)
-
-    def _get_validator(self, spec_resolver):
-        return self.validator_factory.create(spec_resolver)
-
-    def _iter_paths_errors(self, paths, dereferencer):
-        return PathsValidator(dereferencer).iter_errors(paths)
-
-    def _iter_components_errors(self, components, dereferencer):
-        return ComponentsValidator(dereferencer).iter_errors(components)
-
-
-class ComponentsValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, components):
-        components_deref = self.dereferencer.dereference(components)
-
-        schemas = components_deref.get('schemas', {})
-        yield from self._iter_schemas_errors(schemas)
-
-    def _iter_schemas_errors(self, schemas):
-        return SchemasValidator(self.dereferencer).iter_errors(schemas)
-
-
-class SchemasValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, schemas):
-        schemas_deref = self.dereferencer.dereference(schemas)
-        for name, schema in iteritems(schemas_deref):
-            yield from self._iter_schem_errors(schema)
-
-    def _iter_schem_errors(self, schema):
-        return SchemaValidator(self.dereferencer).iter_errors(schema)
-
-
-class SchemaValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, schema):
-        schema_deref = self.dereferencer.dereference(schema)
-
-        if 'allOf' in schema_deref:
-            for inner_schema in schema_deref['allOf']:
-                yield from self.iter_errors(inner_schema)
-
-        required = schema_deref.get('required', [])
-        properties = schema_deref.get('properties', {}).keys()
-        extra_properties = list(set(required) - set(properties))
-        if extra_properties:
-            yield ExtraParametersError(
-                "Required list has not defined properties: {0}".format(
-                    extra_properties
-                )
-            )
-
-
-class PathsValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, paths):
-        paths_deref = self.dereferencer.dereference(paths)
-        for url, path_item in iteritems(paths_deref):
-            yield from self._iter_path_errors(url, path_item)
-
-    def _iter_path_errors(self, url, path_item):
-        return PathValidator(self.dereferencer).iter_errors(url, path_item)
-
-
-class PathValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, url, path_item):
-        path_item_deref = self.dereferencer.dereference(path_item)
-
-        yield from self._iter_path_item_errors(url, path_item_deref)
-
-    def _iter_path_item_errors(self, url, path_item):
-        return PathItemValidator(self.dereferencer).iter_errors(url, path_item)
-
-
-class PathItemValidator(object):
-
-    OPERATIONS = [
-        'get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace',
-    ]
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, url, path_item):
-        path_item_deref = self.dereferencer.dereference(path_item)
-
-        parameters = path_item_deref.get('parameters', [])
-        yield from self._iter_parameters_errors(parameters)
-
-        for field_name, operation in iteritems(path_item):
-            if field_name not in self.OPERATIONS:
-                continue
-
-            yield from self._iter_operation_errors(
-                url, field_name, operation, parameters)
-
-    def _iter_operation_errors(self, url, name, operation, path_parameters):
-        return OperationValidator(self.dereferencer).iter_errors(
-            url, name, operation, path_parameters)
-
-    def _iter_parameters_errors(self, parameters):
-        return ParametersValidator(self.dereferencer).iter_errors(parameters)
-
-
-class OperationValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, url, name, operation, path_parameters=None):
-        path_parameters = path_parameters or []
-        operation_deref = self.dereferencer.dereference(operation)
-
-        parameters = operation_deref.get('parameters', [])
-        yield from self._iter_parameters_errors(parameters)
-
-        all_params = list(set(
-            list(self._get_path_param_names(path_parameters)) +
-            list(self._get_path_param_names(parameters))
-        ))
-
-        for path in self._get_path_params_from_url(url):
-            if path not in all_params:
-                yield UnresolvableParameterError(
-                    "Path parameter '{0}' for '{1}' operation in '{2}' "
-                    "was not resolved".format(path, name, url)
-                )
-
-        return []
-
-    def _get_path_param_names(self, params):
-        for param in params:
-            param_deref = self.dereferencer.dereference(param)
-            if param_deref['in'] == 'path':
-                yield param_deref['name']
-
-    def _get_path_params_from_url(self, url):
-        formatter = string.Formatter()
-        path_params = [item[1] for item in formatter.parse(url)]
-        return filter(None, path_params)
-
-    def _iter_parameters_errors(self, parameters):
-        return ParametersValidator(self.dereferencer).iter_errors(parameters)
-
-
-class ParametersValidator(object):
-
-    def __init__(self, dereferencer):
-        self.dereferencer = dereferencer
-
-    def iter_errors(self, parameters):
-        seen = set()
-        for parameter in parameters:
-            parameter_deref = self.dereferencer.dereference(parameter)
-            key = (parameter_deref['name'], parameter_deref['in'])
-            if key in seen:
-                yield ParameterDuplicateError(
-                    "Duplicate parameter `{0}`".format(parameter_deref['name'])
-                )
-            seen.add(key)
diff --git a/openapi_spec_validator/versions/__init__.py b/openapi_spec_validator/versions/__init__.py
new file mode 100644
index 0000000..2203413
--- /dev/null
+++ b/openapi_spec_validator/versions/__init__.py
@@ -0,0 +1,13 @@
+from openapi_spec_validator.versions.consts import OPENAPIV2
+from openapi_spec_validator.versions.consts import OPENAPIV30
+from openapi_spec_validator.versions.consts import OPENAPIV31
+from openapi_spec_validator.versions.datatypes import SpecVersion
+from openapi_spec_validator.versions.shortcuts import get_spec_version
+
+__all__ = [
+    "OPENAPIV2",
+    "OPENAPIV30",
+    "OPENAPIV31",
+    "SpecVersion",
+    "get_spec_version",
+]
diff --git a/openapi_spec_validator/versions/consts.py b/openapi_spec_validator/versions/consts.py
new file mode 100644
index 0000000..6b5ea7d
--- /dev/null
+++ b/openapi_spec_validator/versions/consts.py
@@ -0,0 +1,23 @@
+from typing import List
+
+from openapi_spec_validator.versions.datatypes import SpecVersion
+
+OPENAPIV2 = SpecVersion(
+    keyword="swagger",
+    major="2",
+    minor="0",
+)
+
+OPENAPIV30 = SpecVersion(
+    keyword="openapi",
+    major="3",
+    minor="0",
+)
+
+OPENAPIV31 = SpecVersion(
+    keyword="openapi",
+    major="3",
+    minor="1",
+)
+
+VERSIONS: List[SpecVersion] = [OPENAPIV2, OPENAPIV30, OPENAPIV31]
diff --git a/openapi_spec_validator/versions/datatypes.py b/openapi_spec_validator/versions/datatypes.py
new file mode 100644
index 0000000..a869992
--- /dev/null
+++ b/openapi_spec_validator/versions/datatypes.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True)
+class SpecVersion:
+    """
+    Spec version designates the OAS feature set.
+    """
+
+    keyword: str
+    major: str
+    minor: str
+
+    def __str__(self) -> str:
+        return f"OpenAPIV{self.major}.{self.minor}"
diff --git a/openapi_spec_validator/versions/exceptions.py b/openapi_spec_validator/versions/exceptions.py
new file mode 100644
index 0000000..91a5cbb
--- /dev/null
+++ b/openapi_spec_validator/versions/exceptions.py
@@ -0,0 +1,6 @@
+from openapi_spec_validator.exceptions import OpenAPIError
+
+
+class OpenAPIVersionNotFound(OpenAPIError):
+    def __str__(self) -> str:
+        return "Specification version not found"
diff --git a/openapi_spec_validator/versions/finders.py b/openapi_spec_validator/versions/finders.py
new file mode 100644
index 0000000..803cabc
--- /dev/null
+++ b/openapi_spec_validator/versions/finders.py
@@ -0,0 +1,26 @@
+from re import compile
+from typing import List
+
+from jsonschema_path.typing import Schema
+
+from openapi_spec_validator.versions.datatypes import SpecVersion
+from openapi_spec_validator.versions.exceptions import OpenAPIVersionNotFound
+
+
+class SpecVersionFinder:
+    pattern = compile(r"(?P<major>\d+)\.(?P<minor>\d+)(\..*)?")
+
+    def __init__(self, versions: List[SpecVersion]) -> None:
+        self.versions = versions
+
+    def find(self, spec: Schema) -> SpecVersion:
+        for v in self.versions:
+            if v.keyword in spec:
+                version_str = spec[v.keyword]
+                m = self.pattern.match(version_str)
+                if m:
+                    version = SpecVersion(**m.groupdict(), keyword=v.keyword)
+                    if v == version:
+                        return v
+
+        raise OpenAPIVersionNotFound
diff --git a/openapi_spec_validator/versions/shortcuts.py b/openapi_spec_validator/versions/shortcuts.py
new file mode 100644
index 0000000..1ff16bf
--- /dev/null
+++ b/openapi_spec_validator/versions/shortcuts.py
@@ -0,0 +1,10 @@
+from jsonschema_path.typing import Schema
+
+from openapi_spec_validator.versions.consts import VERSIONS
+from openapi_spec_validator.versions.datatypes import SpecVersion
+from openapi_spec_validator.versions.finders import SpecVersionFinder
+
+
+def get_spec_version(spec: Schema) -> SpecVersion:
+    finder = SpecVersionFinder(VERSIONS)
+    return finder.find(spec)
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..55fec3c
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,2023 @@
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
+
+[[package]]
+name = "alabaster"
+version = "0.7.16"
+description = "A light, configurable Sphinx theme"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"},
+    {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+groups = ["docs"]
+files = [
+    {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+    {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+optional = false
+python-versions = "*"
+groups = ["docs"]
+files = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+
+[[package]]
+name = "astor"
+version = "0.8.1"
+description = "Read/rewrite/write Python ASTs"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+groups = ["dev"]
+files = [
+    {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"},
+    {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"},
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+    {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
+    {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
+tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.8"
+groups = ["docs"]
+files = [
+    {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
+    {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
+]
+
+[package.extras]
+dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
+
+[[package]]
+name = "black"
+version = "25.1.0"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
+    {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
+    {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
+    {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
+    {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
+    {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
+    {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
+    {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
+    {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
+    {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
+    {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
+    {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
+    {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
+    {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
+    {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
+    {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
+    {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
+    {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
+    {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
+    {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
+    {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
+    {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.10)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "cachetools"
+version = "6.1.0"
+description = "Extensible memoizing collections and decorators"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"},
+    {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"},
+]
+
+[[package]]
+name = "certifi"
+version = "2025.6.15"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.7"
+groups = ["docs"]
+files = [
+    {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"},
+    {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"},
+]
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+description = "Validate configuration and produce human readable error messages."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+    {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+    {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
+]
+
+[[package]]
+name = "chardet"
+version = "5.2.0"
+description = "Universal encoding detector for Python 3"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
+    {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.2"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7"
+groups = ["docs"]
+files = [
+    {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
+    {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
+    {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
+    {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
+    {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
+    {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
+    {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
+    {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
+    {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
+    {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
+]
+
+[[package]]
+name = "cli-ui"
+version = "0.19.0"
+description = "Build Nice User Interfaces In The Terminal"
+optional = false
+python-versions = "<4.0,>=3.9"
+groups = ["dev"]
+files = [
+    {file = "cli_ui-0.19.0-py3-none-any.whl", hash = "sha256:1cf1b93328f7377730db29507e10bcb29ccc1427ceef45714b522d1f2055e7cd"},
+    {file = "cli_ui-0.19.0.tar.gz", hash = "sha256:59cdab0c6a2a6703c61b31cb75a1943076888907f015fffe15c5a8eb41a933aa"},
+]
+
+[package.dependencies]
+colorama = ">=0.4.1,<0.5.0"
+tabulate = ">=0.9.0,<0.10.0"
+unidecode = ">=1.3.6,<2.0.0"
+
+[[package]]
+name = "click"
+version = "8.1.8"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
+    {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["dev", "docs"]
+files = [
+    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+markers = {docs = "sys_platform == \"win32\""}
+
+[[package]]
+name = "coverage"
+version = "7.9.1"
+description = "Code coverage measurement for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"},
+    {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"},
+    {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"},
+    {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"},
+    {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"},
+    {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"},
+    {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"},
+    {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"},
+    {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"},
+    {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"},
+    {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"},
+    {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"},
+    {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"},
+    {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"},
+    {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"},
+    {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"},
+    {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"},
+    {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"},
+    {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"},
+    {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"},
+    {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"},
+    {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"},
+    {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"},
+    {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"},
+    {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"},
+    {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"},
+    {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"},
+    {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"},
+    {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"},
+    {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"},
+    {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"},
+    {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"},
+    {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"},
+    {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"},
+    {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"},
+    {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"},
+    {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"},
+    {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"},
+    {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"},
+    {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"},
+    {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"},
+    {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"},
+    {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"},
+    {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"},
+    {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"},
+    {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"},
+    {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"},
+    {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"},
+    {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"},
+    {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"},
+    {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"},
+    {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"},
+    {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"},
+    {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"},
+    {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"},
+    {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"},
+    {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"},
+    {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"},
+    {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"},
+    {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"},
+    {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"},
+    {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"},
+    {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"},
+    {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"},
+    {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"},
+    {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"},
+    {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"},
+]
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
+
+[[package]]
+name = "deptry"
+version = "0.23.0"
+description = "A command line utility to check for unused, missing and transitive dependencies in a Python project."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "deptry-0.23.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1f2a6817a37d76e8f6b667381b7caf6ea3e6d6c18b5be24d36c625f387c79852"},
+    {file = "deptry-0.23.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9601b64cc0aed42687fdd5c912d5f1e90d7f7333fb589b14e35bfdfebae866f3"},
+    {file = "deptry-0.23.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6172b2205f6e84bcc9df25226693d4deb9576a6f746c2ace828f6d13401d357"},
+    {file = "deptry-0.23.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cfa4b3a46ee8a026eaa38e4b9ba43fe6036a07fe16bf0a663cb611b939f6af8"},
+    {file = "deptry-0.23.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9d03cc99a61c348df92074a50e0a71b28f264f0edbf686084ca90e6fd44e3abe"},
+    {file = "deptry-0.23.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9a46f78098f145100dc582a59af8548b26cdfa16cf0fbd85d2d44645e724cb6a"},
+    {file = "deptry-0.23.0-cp39-abi3-win_amd64.whl", hash = "sha256:d53e803b280791d89a051b6183d9dc40411200e22a8ab7e6c32c6b169822a664"},
+    {file = "deptry-0.23.0-cp39-abi3-win_arm64.whl", hash = "sha256:da7678624f4626d839c8c03675452cefc59d6cf57d25c84a9711dae514719279"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:40706dcbed54141f2d23afa70a272171c8c46531cd6f0f9c8ef482c906b3cee2"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:889541844092f18e7b48631852195f36c25c5afd4d7e074b19ba824b430add50"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aff9156228eb16cd81792f920c1623c00cb59091ae572600ba0eac587da33c0c"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:583154732cfd438a4a090b7d13d8b2016f1ac2732534f34fb689345768d8538b"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:736e7bc557aec6118b2a4d454f0d81f070782faeaa9d8d3c9a15985c9f265372"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5f7e4b1a5232ed6d352fca7173750610a169377d1951d3e9782947191942a765"},
+    {file = "deptry-0.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:04afae204654542406318fd3dd6f4a6697579597f37195437daf84a53ee0ebbf"},
+    {file = "deptry-0.23.0.tar.gz", hash = "sha256:4915a3590ccf38ad7a9176aee376745aa9de121f50f8da8fb9ccec87fa93e676"},
+]
+
+[package.dependencies]
+click = ">=8.0.0,<9"
+colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
+packaging = ">=23.2"
+requirements-parser = ">=0.11.0,<1"
+tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "distlib"
+version = "0.3.9"
+description = "Distribution utilities"
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+    {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
+    {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
+]
+
+[[package]]
+name = "docopt"
+version = "0.6.2"
+description = "Pythonic argument parser, that will make you smile"
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+    {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
+]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
+    {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+markers = "python_version < \"3.11\""
+files = [
+    {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
+    {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "filelock"
+version = "3.18.0"
+description = "A platform independent file lock."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
+    {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
+typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
+
+[[package]]
+name = "flake8"
+version = "5.0.4"
+description = "the modular source code checker: pep8 pyflakes and co"
+optional = false
+python-versions = ">=3.6.1"
+groups = ["dev"]
+files = [
+    {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
+    {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
+]
+
+[package.dependencies]
+mccabe = ">=0.7.0,<0.8.0"
+pycodestyle = ">=2.9.0,<2.10.0"
+pyflakes = ">=2.5.0,<2.6.0"
+
+[[package]]
+name = "flynt"
+version = "1.0.2"
+description = "CLI tool to convert a python project's %-formatted strings to f-strings."
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "flynt-1.0.2-py3-none-any.whl", hash = "sha256:0388f44acf5c5ccaf96dbd23b7817f56a1b0e54b36df7993b6e64e07939f7dab"},
+    {file = "flynt-1.0.2.tar.gz", hash = "sha256:e725eaf2d6b5de8ba356599b638bc3561c6a37c0b92f4979cd4cd451be2c0ce5"},
+]
+
+[package.dependencies]
+astor = "*"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["build", "pre-commit", "pytest", "pytest-cov", "twine"]
+
+[[package]]
+name = "identify"
+version = "2.6.12"
+description = "File identification library for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"},
+    {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"},
+]
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.6"
+groups = ["docs"]
+files = [
+    {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+    {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+]
+
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+groups = ["docs"]
+files = [
+    {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+    {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.0"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+markers = "python_version == \"3.9\""
+files = [
+    {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"},
+    {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"},
+]
+
+[package.dependencies]
+zipp = ">=3.20"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+perf = ["ipython"]
+test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+    {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
+    {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
+]
+
+[[package]]
+name = "isort"
+version = "6.0.1"
+description = "A Python utility / library to sort Python imports."
+optional = false
+python-versions = ">=3.9.0"
+groups = ["dev"]
+files = [
+    {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
+    {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
+]
+
+[package.extras]
+colors = ["colorama"]
+plugins = ["setuptools"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+groups = ["docs"]
+files = [
+    {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+    {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jsonschema"
+version = "4.24.0"
+description = "An implementation of JSON Schema validation for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+    {file = "jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d"},
+    {file = "jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+jsonschema-specifications = ">=2023.03.6"
+referencing = ">=0.28.4"
+rpds-py = ">=0.7.1"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
+
+[[package]]
+name = "jsonschema-path"
+version = "0.4.0b1"
+description = "JSONSchema Spec with object-oriented paths"
+optional = false
+python-versions = "<4.0.0,>=3.9.0"
+groups = ["main"]
+files = [
+    {file = "jsonschema_path-0.4.0b1-py3-none-any.whl", hash = "sha256:f0cd42238c1445cecac3b2796fa24dcc62b8426623d1cb4d8d755f3715abf3ba"},
+    {file = "jsonschema_path-0.4.0b1.tar.gz", hash = "sha256:4678dcb27e5fbc58b39baf7ca8c36a6a7de3bcafacaaafc1b8a1d54038ce8d8a"},
+]
+
+[package.dependencies]
+pathable = ">=0.5.0b2,<0.6.0"
+pyrsistent = ">=0.20.0,<0.21.0"
+PyYAML = ">=5.1"
+referencing = "<0.37.0"
+
+[package.extras]
+requests = ["requests (>=2.31.0,<3.0.0)"]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.4.1"
+description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+    {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"},
+    {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"},
+]
+
+[package.dependencies]
+referencing = ">=0.31.0"
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.11.0"
+description = "A fast and thorough lazy object proxy."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+    {file = "lazy_object_proxy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:132bc8a34f2f2d662a851acfd1b93df769992ed1b81e2b1fda7db3e73b0d5a18"},
+    {file = "lazy_object_proxy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:01261a3afd8621a1accb5682df2593dc7ec7d21d38f411011a5712dcd418fbed"},
+    {file = "lazy_object_proxy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:090935756cc041e191f22f4f9c7fd4fe9a454717067adf5b1bbd2ce3046b556e"},
+    {file = "lazy_object_proxy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:76ec715017f06410f57df442c1a8d66e6b5f7035077785b129817f5ae58810a4"},
+    {file = "lazy_object_proxy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a9f39098e93a63618a79eef2889ae3cf0605f676cd4797fdfd49fcd7ddc318b"},
+    {file = "lazy_object_proxy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee13f67f4fcd044ef27bfccb1c93d39c100046fec1fad6e9a1fcdfd17492aeb3"},
+    {file = "lazy_object_proxy-1.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4c84eafd8dd15ea16f7d580758bc5c2ce1f752faec877bb2b1f9f827c329cd"},
+    {file = "lazy_object_proxy-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d2503427bda552d3aefcac92f81d9e7ca631e680a2268cbe62cd6a58de6409b7"},
+    {file = "lazy_object_proxy-1.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0613116156801ab3fccb9e2b05ed83b08ea08c2517fdc6c6bc0d4697a1a376e3"},
+    {file = "lazy_object_proxy-1.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bb03c507d96b65f617a6337dedd604399d35face2cdf01526b913fb50c4cb6e8"},
+    {file = "lazy_object_proxy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28c174db37946f94b97a97b579932ff88f07b8d73a46b6b93322b9ac06794a3b"},
+    {file = "lazy_object_proxy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d662f0669e27704495ff1f647070eb8816931231c44e583f4d0701b7adf6272f"},
+    {file = "lazy_object_proxy-1.11.0-py3-none-any.whl", hash = "sha256:a56a5093d433341ff7da0e89f9b486031ccd222ec8e52ec84d0ec1cdc819674b"},
+    {file = "lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c"},
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
+    {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+]
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+description = "McCabe checker, plugin for flake8"
+optional = false
+python-versions = ">=3.6"
+groups = ["dev"]
+files = [
+    {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+    {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.16.1"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
+    {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
+    {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
+    {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
+    {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
+    {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
+    {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
+    {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
+    {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
+    {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
+    {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
+    {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
+    {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
+    {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
+    {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
+    {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
+    {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
+    {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
+    {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
+    {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
+    {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
+    {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
+    {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
+    {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
+    {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
+    {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
+    {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
+    {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
+    {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
+    {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
+    {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
+    {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
+]
+
+[package.dependencies]
+mypy_extensions = ">=1.0.0"
+pathspec = ">=0.9.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing_extensions = ">=4.6.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+faster-cache = ["orjson"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+    {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+    {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+description = "Node.js virtual environment builder"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["dev"]
+files = [
+    {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
+    {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
+]
+
+[[package]]
+name = "openapi-schema-validator"
+version = "0.6.3"
+description = "OpenAPI schema validation for Python"
+optional = false
+python-versions = "<4.0.0,>=3.8.0"
+groups = ["main"]
+files = [
+    {file = "openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3"},
+    {file = "openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee"},
+]
+
+[package.dependencies]
+jsonschema = ">=4.19.1,<5.0.0"
+jsonschema-specifications = ">=2023.5.2"
+rfc3339-validator = "*"
+
+[[package]]
+name = "packaging"
+version = "25.0"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev", "docs"]
+files = [
+    {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+    {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
+]
+
+[[package]]
+name = "pathable"
+version = "0.5.0b2"
+description = "Object-oriented paths"
+optional = false
+python-versions = "<4.0,>=3.9"
+groups = ["main"]
+files = [
+    {file = "pathable-0.5.0b2-py3-none-any.whl", hash = "sha256:4c78bc5dae0c64a6191b955b7975bab58f0e298e197f591d4f0172886824a52b"},
+    {file = "pathable-0.5.0b2.tar.gz", hash = "sha256:0005483148dc1991ab32e4cb4cde7e2b8f376f081a1639631e424db0792860e9"},
+]
+
+[package.dependencies]
+pyrsistent = ">=0.20.0,<0.21.0"
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+    {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+    {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
+    {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+    {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pre-commit"
+version = "4.2.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"},
+    {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"},
+]
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+virtualenv = ">=20.10.0"
+
+[[package]]
+name = "pycodestyle"
+version = "2.9.1"
+description = "Python style guide checker"
+optional = false
+python-versions = ">=3.6"
+groups = ["dev"]
+files = [
+    {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
+    {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.11.7"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
+    {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.33.2"
+typing-extensions = ">=4.12.2"
+typing-inspection = ">=0.4.0"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
+
+[[package]]
+name = "pydantic-core"
+version = "2.33.2"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
+    {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
+    {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
+    {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
+    {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
+    {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
+    {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
+    {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
+    {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
+    {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
+    {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
+    {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
+    {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "pydantic-extra-types"
+version = "2.10.5"
+description = "Extra Pydantic types."
+optional = false
+python-versions = ">=3.8"
+groups = ["docs"]
+files = [
+    {file = "pydantic_extra_types-2.10.5-py3-none-any.whl", hash = "sha256:b60c4e23d573a69a4f1a16dd92888ecc0ef34fb0e655b4f305530377fa70e7a8"},
+    {file = "pydantic_extra_types-2.10.5.tar.gz", hash = "sha256:1dcfa2c0cf741a422f088e0dbb4690e7bfadaaf050da3d6f80d6c3cf58a2bad8"},
+]
+
+[package.dependencies]
+pydantic = ">=2.5.2"
+typing-extensions = "*"
+
+[package.extras]
+all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<10)", "pycountry (>=23)", "pymongo (>=4.0.0,<5.0.0)", "python-ulid (>=1,<2) ; python_version < \"3.9\"", "python-ulid (>=1,<4) ; python_version >= \"3.9\"", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"]
+pendulum = ["pendulum (>=3.0.0,<4.0.0)"]
+phonenumbers = ["phonenumbers (>=8,<10)"]
+pycountry = ["pycountry (>=23)"]
+python-ulid = ["python-ulid (>=1,<2) ; python_version < \"3.9\"", "python-ulid (>=1,<4) ; python_version >= \"3.9\""]
+semver = ["semver (>=3.0.2)"]
+
+[[package]]
+name = "pyflakes"
+version = "2.5.0"
+description = "passive checker of Python programs"
+optional = false
+python-versions = ">=3.6"
+groups = ["dev"]
+files = [
+    {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
+    {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev", "docs"]
+files = [
+    {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+    {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pyproject-api"
+version = "1.9.1"
+description = "API to interact with the python pyproject.toml based projects"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948"},
+    {file = "pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335"},
+]
+
+[package.dependencies]
+packaging = ">=25"
+tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3.2)"]
+testing = ["covdefaults (>=2.3)", "pytest (>=8.3.5)", "pytest-cov (>=6.1.1)", "pytest-mock (>=3.14)", "setuptools (>=80.3.1)"]
+
+[[package]]
+name = "pyrsistent"
+version = "0.20.0"
+description = "Persistent/Functional/Immutable data structures"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+    {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"},
+    {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"},
+    {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"},
+    {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"},
+    {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"},
+    {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"},
+    {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"},
+    {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"},
+    {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"},
+    {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"},
+    {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"},
+    {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"},
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.1"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
+    {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
+iniconfig = ">=1"
+packaging = ">=20"
+pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "6.2.1"
+description = "Pytest plugin for measuring coverage."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"},
+    {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"},
+]
+
+[package.dependencies]
+coverage = {version = ">=7.5", extras = ["toml"]}
+pluggy = ">=1.2"
+pytest = ">=6.2.5"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "pytest-flake8"
+version = "1.3.0"
+description = "pytest plugin to check FLAKE8 requirements"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pytest_flake8-1.3.0-py3-none-any.whl", hash = "sha256:de10517c59fce25c0a7abb2a2b2a9d0b0ceb59ff0add7fa8e654d613bb25e218"},
+    {file = "pytest_flake8-1.3.0.tar.gz", hash = "sha256:88fb35562ce32d915c6ba41ef0d5e1cfcdd8ff884a32b7d46aa99fc77a3d1fe6"},
+]
+
+[package.dependencies]
+flake8 = ">=4.0"
+pytest = ">=7.0"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["pytest (>=6,!=8.1.*)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+files = [
+    {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
+    {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
+    {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
+    {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
+    {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
+    {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
+    {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
+    {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
+    {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
+    {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
+    {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
+    {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
+    {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
+    {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
+    {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
+    {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
+    {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
+    {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
+    {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
+    {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
+    {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
+    {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
+    {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
+    {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
+    {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
+    {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
+    {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
+    {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
+    {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
+    {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
+    {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
+    {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
+    {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
+    {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
+    {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+description = "JSON Referencing + Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+    {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"},
+    {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+rpds-py = ">=0.7.0"
+typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
+
+[[package]]
+name = "requests"
+version = "2.32.4"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.8"
+groups = ["docs"]
+files = [
+    {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
+    {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset_normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requirements-parser"
+version = "0.13.0"
+description = "This is a small Python module for parsing Pip requirement files."
+optional = false
+python-versions = "<4.0,>=3.8"
+groups = ["dev"]
+files = [
+    {file = "requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14"},
+    {file = "requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418"},
+]
+
+[package.dependencies]
+packaging = ">=23.2"
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+description = "A pure python RFC3339 validator"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+groups = ["main"]
+files = [
+    {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"},
+    {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"},
+]
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+name = "rpds-py"
+version = "0.25.1"
+description = "Python bindings to Rust's persistent data structures (rpds)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+    {file = "rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9"},
+    {file = "rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da"},
+    {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380"},
+    {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9"},
+    {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54"},
+    {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2"},
+    {file = "rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24"},
+    {file = "rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a"},
+    {file = "rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d"},
+    {file = "rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd"},
+    {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65"},
+    {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f"},
+    {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d"},
+    {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042"},
+    {file = "rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc"},
+    {file = "rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4"},
+    {file = "rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4"},
+    {file = "rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c"},
+    {file = "rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea"},
+    {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65"},
+    {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c"},
+    {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd"},
+    {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb"},
+    {file = "rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe"},
+    {file = "rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192"},
+    {file = "rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728"},
+    {file = "rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559"},
+    {file = "rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325"},
+    {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295"},
+    {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b"},
+    {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98"},
+    {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd"},
+    {file = "rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31"},
+    {file = "rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500"},
+    {file = "rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523"},
+    {file = "rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763"},
+    {file = "rpds_py-0.25.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ce4c8e485a3c59593f1a6f683cf0ea5ab1c1dc94d11eea5619e4fb5228b40fbd"},
+    {file = "rpds_py-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8222acdb51a22929c3b2ddb236b69c59c72af4019d2cba961e2f9add9b6e634"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4593c4eae9b27d22df41cde518b4b9e4464d139e4322e2127daa9b5b981b76be"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd035756830c712b64725a76327ce80e82ed12ebab361d3a1cdc0f51ea21acb0"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:114a07e85f32b125404f28f2ed0ba431685151c037a26032b213c882f26eb908"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dec21e02e6cc932538b5203d3a8bd6aa1480c98c4914cb88eea064ecdbc6396a"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eab132f41bf792c7a0ea1578e55df3f3e7f61888e340779b06050a9a3f16e9"},
+    {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c98f126c4fc697b84c423e387337d5b07e4a61e9feac494362a59fd7a2d9ed80"},
+    {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e6a327af8ebf6baba1c10fadd04964c1965d375d318f4435d5f3f9651550f4a"},
+    {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc120d1132cff853ff617754196d0ac0ae63befe7c8498bd67731ba368abe451"},
+    {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:140f61d9bed7839446bdd44852e30195c8e520f81329b4201ceead4d64eb3a9f"},
+    {file = "rpds_py-0.25.1-cp39-cp39-win32.whl", hash = "sha256:9c006f3aadeda131b438c3092124bd196b66312f0caa5823ef09585a669cf449"},
+    {file = "rpds_py-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:a61d0b2c7c9a0ae45732a77844917b427ff16ad5464b4d4f5e4adb955f582890"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11"},
+    {file = "rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf"},
+    {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50f2c501a89c9a5f4e454b126193c5495b9fb441a75b298c60591d8a2eb92e1b"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d779b325cc8238227c47fbc53964c8cc9a941d5dbae87aa007a1f08f2f77b23"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:036ded36bedb727beeabc16dc1dad7cb154b3fa444e936a03b67a86dc6a5066e"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245550f5a1ac98504147cba96ffec8fabc22b610742e9150138e5d60774686d7"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff7c23ba0a88cb7b104281a99476cccadf29de2a0ef5ce864959a52675b1ca83"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e37caa8cdb3b7cf24786451a0bdb853f6347b8b92005eeb64225ae1db54d1c2b"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2f48ab00181600ee266a095fe815134eb456163f7d6699f525dee471f312cf"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e5fc7484fa7dce57e25063b0ec9638ff02a908304f861d81ea49273e43838c1"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d3c10228d6cf6fe2b63d2e7985e94f6916fa46940df46b70449e9ff9297bd3d1"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5d9e40f32745db28c1ef7aad23f6fc458dc1e29945bd6781060f0d15628b8ddf"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:35a8d1a24b5936b35c5003313bc177403d8bdef0f8b24f28b1c4a255f94ea992"},
+    {file = "rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793"},
+    {file = "rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3"},
+]
+
+[[package]]
+name = "schema"
+version = "0.7.7"
+description = "Simple data validation library"
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+    {file = "schema-0.7.7-py2.py3-none-any.whl", hash = "sha256:5d976a5b50f36e74e2157b47097b60002bd4d42e65425fcc9c9befadb4255dde"},
+    {file = "schema-0.7.7.tar.gz", hash = "sha256:7da553abd2958a19dc2547c388cde53398b39196175a9be59ea1caf5ab0a1807"},
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+files = [
+    {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+    {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*"
+groups = ["docs"]
+files = [
+    {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"},
+    {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"},
+]
+
+[[package]]
+name = "sphinx"
+version = "7.4.7"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"},
+    {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7.14,<0.8.0"
+babel = ">=2.13"
+colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
+docutils = ">=0.20,<0.22"
+imagesize = ">=1.3"
+importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""}
+Jinja2 = ">=3.1"
+packaging = ">=23.0"
+Pygments = ">=2.17"
+requests = ">=2.30.0"
+snowballstemmer = ">=2.2"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.9"
+tomli = {version = ">=2", markers = "python_version < \"3.11\""}
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"]
+test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
+
+[[package]]
+name = "sphinx-immaterial"
+version = "0.12.5"
+description = "Adaptation of mkdocs-material theme for the Sphinx documentation system"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinx_immaterial-0.12.5-py3-none-any.whl", hash = "sha256:4173b22ad343fd9c75b51baf305851d89b98b94603c474b428e30e8c8476673b"},
+    {file = "sphinx_immaterial-0.12.5.tar.gz", hash = "sha256:a7c0c4be3dcb4960eb7b299dfee07cdf8a02bf56821f5d0d62e5d31b7b7b5ec5"},
+]
+
+[package.dependencies]
+appdirs = "*"
+markupsafe = "*"
+pydantic = ">=2.4"
+pydantic-extra-types = "*"
+requests = "*"
+sphinx = ">=4.5"
+typing-extensions = "*"
+
+[package.extras]
+black = ["black"]
+clang-format = ["clang-format"]
+cpp = ["libclang"]
+json = ["pyyaml"]
+jsonschema-validation = ["jsonschema"]
+keys = ["pymdown-extensions"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
+    {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
+    {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
+    {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+    {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
+    {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["defusedxml (>=0.7.1)", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
+    {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "tabulate"
+version = "0.9.0"
+description = "Pretty-print tabular data"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
+    {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
+]
+
+[package.extras]
+widechars = ["wcwidth"]
+
+[[package]]
+name = "tbump"
+version = "6.11.0"
+description = "Bump software releases"
+optional = false
+python-versions = ">=3.7,<4.0"
+groups = ["dev"]
+files = [
+    {file = "tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526"},
+    {file = "tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870"},
+]
+
+[package.dependencies]
+cli-ui = ">=0.10.3"
+docopt = ">=0.6.2,<0.7.0"
+schema = ">=0.7.1,<0.8.0"
+tomlkit = ">=0.11,<0.12"
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev", "docs"]
+files = [
+    {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+    {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+    {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+    {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+    {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+    {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+    {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+    {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+    {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+    {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+    {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+    {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+    {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+    {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+]
+markers = {dev = "python_full_version <= \"3.11.0a6\"", docs = "python_version < \"3.11\""}
+
+[[package]]
+name = "tomlkit"
+version = "0.11.8"
+description = "Style preserving TOML library"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"},
+    {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"},
+]
+
+[[package]]
+name = "tox"
+version = "4.27.0"
+description = "tox is a generic virtualenv management and test command line tool"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20"},
+    {file = "tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57"},
+]
+
+[package.dependencies]
+cachetools = ">=5.5.1"
+chardet = ">=5.2"
+colorama = ">=0.4.6"
+filelock = ">=3.16.1"
+packaging = ">=24.2"
+platformdirs = ">=4.3.6"
+pluggy = ">=1.5"
+pyproject-api = ">=1.8"
+tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""}
+virtualenv = ">=20.31"
+
+[package.extras]
+test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev", "docs"]
+files = [
+    {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
+    {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
+]
+markers = {main = "python_version < \"3.13\""}
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.1"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"},
+    {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.12.0"
+
+[[package]]
+name = "unidecode"
+version = "1.4.0"
+description = "ASCII transliterations of Unicode text"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021"},
+    {file = "Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+    {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
+    {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.31.2"
+description = "Virtual Python Environment builder"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+    {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"},
+    {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"},
+]
+
+[package.dependencies]
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+markers = "python_version == \"3.9\""
+files = [
+    {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
+    {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
+type = ["pytest-mypy"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = ">=3.9,<4.0"
+content-hash = "a380423a4badffedaeb02ef3aabe23e428e507ef6ca619806213fd2d739d90fe"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..1f9b180
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,147 @@
+[project]
+name = "openapi-spec-validator"
+version = "0.8.0b1"
+description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator"
+authors = [
+    { name = "Artur Maciag", email = "maciag.artur@gmail.com" },
+]
+license = "Apache-2.0"
+readme = "README.rst"
+requires-python = ">=3.9,<4.0"
+keywords = ["openapi", "swagger", "schema"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Intended Audience :: Developers",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Topic :: Software Development :: Libraries",
+    "Typing :: Typed",
+]
+dependencies = [
+    "jsonschema >=4.24.0,<4.25.0",
+    "openapi-schema-validator >=0.6.0,<0.7.0",
+    "jsonschema-path >=0.4.0b1,<0.5.0",
+    "lazy-object-proxy >=1.7.1,<2.0",
+]
+
+[project.urls]
+Source = "https://github.com/python-openapi/openapi-spec-validator"
+Documentation = "https://openapi-spec-validator.readthedocs.io/en/latest/"
+
+[project.scripts]
+openapi-spec-validator = "openapi_spec_validator.__main__:main"
+
+[build-system]
+requires = ["poetry-core>=2.0.0,<3.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry]
+include = [
+    {path = "tests", format = "sdist"},
+]
+
+[tool.poetry.group.docs.dependencies]
+sphinx = ">=5.3,<8.0"
+sphinx-immaterial = ">=0.11,<0.13"
+
+[tool.poetry.group.dev.dependencies]
+tbump = "^6.11.0"
+pre-commit =  "*"
+pytest = "^8.2.2"
+pytest-flake8 = "=1.3.0"
+pytest-cov = "^6.1.1"
+tox = "*"
+mypy = "^1.10"
+isort = "^6.0.1"
+black = "^25.1.0"
+flynt = "^1.0"
+deptry = "^0.23.0"
+flake8 = "^5.0.4"
+pyflakes = "^2.5.0"
+
+[tool.coverage.run]
+branch = true
+source =["openapi_spec_validator"]
+
+[tool.coverage.xml]
+output = "reports/coverage.xml"
+
+[tool.mypy]
+files = "openapi_spec_validator"
+strict = true
+
+[[tool.mypy.overrides]]
+module = "jsonschema.*"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "jsonschema_specifications"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "lazy_object_proxy.*"
+ignore_missing_imports = true
+
+[tool.pytest.ini_options]
+addopts = """
+--capture=no
+--verbose
+--showlocals
+--junitxml=reports/junit.xml
+--cov=openapi_spec_validator
+--cov-report=term-missing
+--cov-report=xml
+"""
+markers = [
+    "network: marks tests which do need network-enabled environment",
+]
+
+[tool.black]
+line-length = 79
+
+[tool.isort]
+profile = "black"
+line_length = 79
+force_single_line = true
+
+[tool.tbump]
+
+[tool.tbump.git]
+message_template = "Version {new_version}"
+tag_template = "{new_version}"
+
+[tool.tbump.version]
+current = "0.8.0b1"
+regex = '''
+  (?P<major>\d+)
+  \.
+  (?P<minor>\d+)
+  \.
+  (?P<patch>\d+)
+  (?P<prerelease>[a-z]+\d+)?
+'''
+
+[[tool.tbump.file]]
+src = "docs/index.rst"
+
+[[tool.tbump.file]]
+src = "docs/hook.rst"
+
+[[tool.tbump.file]]
+src = "openapi_spec_validator/__init__.py"
+
+[[tool.tbump.file]]
+src = "Dockerfile"
+
+[[tool.tbump.file]]
+src = "README.rst"
+
+[[tool.tbump.file]]
+src = "pyproject.toml"
+search = 'version = "{current_version}"'
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 44a0724..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-jsonschema==2.6.0
-pyaml==17.12.1
-six==1.11.0
diff --git a/requirements_dev.txt b/requirements_dev.txt
deleted file mode 100644
index 92b0650..0000000
--- a/requirements_dev.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-mock==2.0.0
-pytest==3.5.0
-pytest-pep8==1.0.6
-pytest-flakes==2.0.0
-pytest-cov==2.5.1
-tox==3.0.0rc4
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 05fe85b..0000000
--- a/setup.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import re
-import sys
-
-from setuptools import find_packages, setup
-from setuptools.command.test import test as TestCommand
-
-
-def read_file(filename):
-    """Open and a file, read it and return its contents."""
-    path = os.path.join(os.path.dirname(__file__), filename)
-    with open(path) as f:
-        return f.read()
-
-
-def get_metadata(init_file):
-    """Read metadata from a given file and return a dictionary of them"""
-    return dict(re.findall("__([a-z]+)__ = '([^']+)'", init_file))
-
-
-class PyTest(TestCommand):
-
-    """Command to run unit tests after in-place build."""
-
-    def finalize_options(self):
-        TestCommand.finalize_options(self)
-        self.test_args = [
-            '-sv',
-            '--pep8',
-            '--flakes',
-            '--cov', 'openapi_spec_validator',
-            '--cov-report', 'term-missing',
-        ]
-        self.test_suite = True
-
-    def run_tests(self):
-        # Importing here, `cause outside the eggs aren't loaded.
-        import pytest
-        errno = pytest.main(self.test_args)
-        sys.exit(errno)
-
-
-init_path = os.path.join('openapi_spec_validator', '__init__.py')
-init_py = read_file(init_path)
-metadata = get_metadata(init_py)
-
-
-setup(
-    name='openapi-spec-validator',
-    version=metadata['version'],
-    author=metadata['author'],
-    author_email=metadata['email'],
-    url=metadata['url'],
-    license=metadata['license'],
-    long_description=read_file('README.md'),
-    packages=find_packages(include=('openapi_spec_validator*',)),
-    package_data={
-        'openapi_spec_validator': [
-            'openapi_spec_validator/resources/schemas/v3.0.0/*',
-        ],
-    },
-    include_package_data=True,
-    entry_points={
-        'console_scripts': [
-            'openapi-spec-validator = openapi_spec_validator.__main__:main'
-        ]
-    },
-    install_requires=[
-        "jsonschema",
-        "pyaml",
-        "six",
-    ],
-    tests_require=[
-        "mock",
-        "pytest",
-        "pytest-pep8",
-        "pytest-flakes",
-        "pytest-cov",
-        "tox",
-    ],
-    cmdclass={'test': PyTest},
-    classifiers=[
-        "Development Status :: 4 - Beta",
-        "Intended Audience :: Developers",
-        "Topic :: Software Development :: Libraries :: Python Modules",
-        "Operating System :: OS Independent",
-        "Programming Language :: Python :: 3.3",
-        "Programming Language :: Python :: 3.4",
-        "Programming Language :: Python :: 3.5",
-        "Programming Language :: Python :: 3.6",
-    ],
-)
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index da04bc9..a420f6d 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -1,14 +1,13 @@
 from os import path
+from pathlib import PurePath
+from urllib.parse import urlunparse
 
 import pytest
-from six.moves.urllib import request
-from six.moves.urllib.parse import urlunparse
-from yaml import safe_load
+from jsonschema_path.handlers.file import FilePathHandler
+from jsonschema_path.handlers.urllib import UrllibHandler
 
-from openapi_spec_validator import openapi_v3_spec_validator
 
-
-def spec_url(spec_file, schema='file'):
+def spec_file_url(spec_file, schema="file"):
     directory = path.abspath(path.dirname(__file__))
     full_path = path.join(directory, spec_file)
     return urlunparse((schema, None, full_path, None, None, None))
@@ -17,13 +16,12 @@ def spec_url(spec_file, schema='file'):
 def spec_from_file(spec_file):
     directory = path.abspath(path.dirname(__file__))
     path_full = path.join(directory, spec_file)
-    with open(path_full) as fh:
-        return safe_load(fh)
+    uri = PurePath(path_full).as_uri()
+    return FilePathHandler()(uri)
 
 
 def spec_from_url(spec_url):
-    content = request.urlopen(spec_url)
-    return safe_load(content)
+    return UrllibHandler("http", "https")(spec_url)
 
 
 class Factory(dict):
@@ -34,12 +32,7 @@ class Factory(dict):
 @pytest.fixture
 def factory():
     return Factory(
-        spec_url=spec_url,
+        spec_file_url=spec_file_url,
         spec_from_file=spec_from_file,
         spec_from_url=spec_from_url,
     )
-
-
-@pytest.fixture
-def validator():
-    return openapi_v3_spec_validator
diff --git a/tests/integration/data/empty.yaml b/tests/integration/data/empty.yaml
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tests/integration/data/empty.yaml
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/tests/integration/data/v2.0/empty.yaml b/tests/integration/data/v2.0/empty.yaml
new file mode 100644
index 0000000..5b308b0
--- /dev/null
+++ b/tests/integration/data/v2.0/empty.yaml
@@ -0,0 +1 @@
+swagger: "2.0"
\ No newline at end of file
diff --git a/tests/integration/data/v2.0/missing-reference.yaml b/tests/integration/data/v2.0/missing-reference.yaml
new file mode 100644
index 0000000..e136aaf
--- /dev/null
+++ b/tests/integration/data/v2.0/missing-reference.yaml
@@ -0,0 +1,101 @@
+swagger: "2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+host: petstore.swagger.io
+basePath: /v1
+schemes:
+  - http
+consumes:
+  - application/json
+produces:
+  - application/json
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          type: integer
+          format: int32
+      responses:
+        200:
+          description: A paged array of pets
+          headers:
+            x-next:
+              type: string
+              description: A link to the next page of responses
+          schema:
+            $ref: 'definitions/Pets'
+        default:
+          description: unexpected error
+          schema:
+            $ref: '#/definitions/'
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      responses:
+        '201':
+          description: Null response
+        default:
+          description: unexpected error
+          schema:
+            $ref: '#/definitions/Error'
+  /pets/{petId}:
+    get:
+      summary: Info for a specific pet
+      operationId: showPetById
+      tags:
+        - pets
+      parameters:
+        - name: petId
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          type: string
+      responses:
+        '200':
+          description: Expected response to a valid request
+          schema:
+            $ref: '#/definitions/Pets'
+        default:
+          description: unexpected error
+          schema:
+            $ref: '#/definitions/Error'
+definitions:
+  Pet:
+    required:
+      - id
+      - name
+    properties:
+      id:
+        type: integer
+        format: int64
+      name:
+        type: string
+      tag:
+        type: string
+  Pets:
+    type: array
+    items:
+      $ref: '#/definitions/Pet'
+  Error:
+    required:
+      - code
+      - message
+    properties:
+      code:
+        type: integer
+        format: int32
+      message:
+        type: string
\ No newline at end of file
diff --git a/tests/integration/data/v2.0/petstore.yaml b/tests/integration/data/v2.0/petstore.yaml
index 4003794..46657ef 100644
--- a/tests/integration/data/v2.0/petstore.yaml
+++ b/tests/integration/data/v2.0/petstore.yaml
@@ -27,7 +27,7 @@ paths:
           type: integer
           format: int32
       responses:
-        "200":
+        200:
           description: A paged array of pets
           headers:
             x-next:
@@ -45,7 +45,7 @@ paths:
       tags:
         - pets
       responses:
-        "201":
+        '201':
           description: Null response
         default:
           description: unexpected error
@@ -64,7 +64,7 @@ paths:
           description: The id of the pet to retrieve
           type: string
       responses:
-        "200":
+        '200':
           description: Expected response to a valid request
           schema:
             $ref: '#/definitions/Pets'
diff --git a/tests/integration/data/v3.0/missing-description.yaml b/tests/integration/data/v3.0/missing-description.yaml
new file mode 100644
index 0000000..0fe1052
--- /dev/null
+++ b/tests/integration/data/v3.0/missing-description.yaml
@@ -0,0 +1,18 @@
+---
+openapi: 3.0.0
+
+info:
+  title: test
+  description: test
+  version: 0.0.1
+
+paths:
+  "/":
+    get:
+      description: Get the API root
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                type: string
diff --git a/tests/integration/data/v3.0/parent-reference/openapi.yaml b/tests/integration/data/v3.0/parent-reference/openapi.yaml
index 0f9c1be..dc78953 100644
--- a/tests/integration/data/v3.0/parent-reference/openapi.yaml
+++ b/tests/integration/data/v3.0/parent-reference/openapi.yaml
@@ -14,6 +14,7 @@ paths:
       tags:
         - pets
       parameters:
+        - $ref: "recursive.yaml#/parameters/RecursiveReference"
         - name: limit
           in: query
           description: How many items to return at one time (max 100)
diff --git a/tests/integration/data/v3.0/parent-reference/recursive.yaml b/tests/integration/data/v3.0/parent-reference/recursive.yaml
new file mode 100644
index 0000000..7277c37
--- /dev/null
+++ b/tests/integration/data/v3.0/parent-reference/recursive.yaml
@@ -0,0 +1,3 @@
+parameters:
+  RecursiveReference:
+    $ref : "recursive2.yaml#/parameters/RecursiveReference"
diff --git a/tests/integration/data/v3.0/parent-reference/recursive2.yaml b/tests/integration/data/v3.0/parent-reference/recursive2.yaml
new file mode 100644
index 0000000..307c89a
--- /dev/null
+++ b/tests/integration/data/v3.0/parent-reference/recursive2.yaml
@@ -0,0 +1,8 @@
+parameters:
+  RecursiveReference:
+    name: sampleParameter
+    in: query
+    description: Tests recursion
+    required: false
+    schema:
+      type: boolean
diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml
index 88099f1..a2ae6b2 100644
--- a/tests/integration/data/v3.0/petstore.yaml
+++ b/tests/integration/data/v3.0/petstore.yaml
@@ -22,7 +22,7 @@ paths:
             type: integer
             format: int32
       responses:
-        '200':
+        200:
           description: An paged array of pets
           headers:
             x-next:
@@ -93,6 +93,8 @@ components:
           type: string
         tag:
           type: string
+        $ref:
+          type: string
     Pets:
       type: array
       items:
diff --git a/tests/integration/data/v3.0/property-missing-reference.yaml b/tests/integration/data/v3.0/property-missing-reference.yaml
new file mode 100644
index 0000000..d6fbb07
--- /dev/null
+++ b/tests/integration/data/v3.0/property-missing-reference.yaml
@@ -0,0 +1,16 @@
+openapi: 3.0.0
+info:
+  version: '1.0.0'
+  title: 'Some Schema'
+paths: {}
+components:
+  schemas:    
+    SomeDataType:
+      type: object
+      properties:
+        id:
+          type: integer
+        prop1:
+          $ref: '<some-broken-uri>'
+        prop2:
+          type: string
\ No newline at end of file
diff --git a/tests/integration/data/v3.0/property-recursive.yaml b/tests/integration/data/v3.0/property-recursive.yaml
new file mode 100644
index 0000000..c1d704b
--- /dev/null
+++ b/tests/integration/data/v3.0/property-recursive.yaml
@@ -0,0 +1,16 @@
+openapi: 3.0.0
+info:
+  version: '1.0.0'
+  title: 'Some Schema'
+paths: {}
+components:
+  schemas:    
+    SomeDataType:
+      type: object
+      properties:
+        id:
+          type: integer
+        prop1:
+          $ref: '#/components/schemas/SomeDataType'
+        prop2:
+          type: string
\ No newline at end of file
diff --git a/tests/integration/data/v3.0/read-only-write-only.yaml b/tests/integration/data/v3.0/read-only-write-only.yaml
new file mode 100644
index 0000000..9af8465
--- /dev/null
+++ b/tests/integration/data/v3.0/read-only-write-only.yaml
@@ -0,0 +1,42 @@
+openapi: "3.0.0"
+info:
+  title: Specification Containing readOnly
+  version: "0.1"
+paths:
+  /users:
+    post:
+        operationId: createUser
+        requestBody:
+          description: Post data for creating a user
+          required: true
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/User'
+        responses:
+          default:
+            description: Create a user
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/User'
+components:
+  schemas:
+    User:
+      x-model: User
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int32
+          readOnly: true
+          default: 1
+        name:
+          type: string
+        hidden:
+          type: boolean
+          writeOnly: true
+          default: true
\ No newline at end of file
diff --git a/tests/integration/data/v3.1/petstore.yaml b/tests/integration/data/v3.1/petstore.yaml
new file mode 100644
index 0000000..3315706
--- /dev/null
+++ b/tests/integration/data/v3.1/petstore.yaml
@@ -0,0 +1,115 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT License
+    identifier: MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int32
+      responses:
+        200:
+          description: An paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:    
+              schema:
+                $ref: "#/components/schemas/Pets"
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      responses:
+        '201':
+          description: Null response
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+  /pets/{petId}:
+    $ref: "#/components/pathItems/PetPath"
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+        $ref:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: "#/components/schemas/Pet"
+    Error:
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
+  pathItems:
+    PetPath:
+      get:
+        summary: Info for a specific pet
+        operationId: showPetById
+        tags:
+          - pets
+        parameters:
+          - name: petId
+            in: path
+            required: true
+            description: The id of the pet to retrieve
+            schema:
+              type: string
+        responses:
+          '200':
+            description: Expected response to a valid request
+            content:
+              application/json:
+                schema:
+                  $ref: "#/components/schemas/Pets"
+          default:
+            description: unexpected error
+            content:
+              application/json:
+                schema:
+                  $ref: "#/components/schemas/Error"
\ No newline at end of file
diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py
index 6d890be..1527a83 100644
--- a/tests/integration/test_main.py
+++ b/tests/integration/test_main.py
@@ -1,46 +1,192 @@
+from io import StringIO
+from unittest import mock
+
 import pytest
 
 from openapi_spec_validator.__main__ import main
 
 
-def test_schema_default():
-    """Test default schema is 3.0.0"""
-    testargs = ['./tests/integration/data/v3.0/petstore.yaml']
+def test_schema_v2_detect(capsys):
+    """Test schema v2 is detected"""
+    testargs = ["./tests/integration/data/v2.0/petstore.yaml"]
+    main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v2.0/petstore.yaml: OK\n" in out
+
+
+def test_schema_v31_detect(capsys):
+    """Test schema v3.1 is detected"""
+    testargs = ["./tests/integration/data/v3.1/petstore.yaml"]
+    main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v3.1/petstore.yaml: OK\n" in out
+
+
+def test_schema_v31(capsys):
+    """No errors when calling proper v3.1 file."""
+    testargs = [
+        "--schema",
+        "3.1.0",
+        "./tests/integration/data/v3.1/petstore.yaml",
+    ]
     main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v3.1/petstore.yaml: OK\n" in out
 
 
-def test_schema_v3():
-    """No errors when calling proper v3 file."""
-    testargs = ['--schema', '3.0.0',
-                './tests/integration/data/v3.0/petstore.yaml']
+def test_schema_v30(capsys):
+    """No errors when calling proper v3.0 file."""
+    testargs = [
+        "--schema",
+        "3.0.0",
+        "./tests/integration/data/v3.0/petstore.yaml",
+    ]
     main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v3.0/petstore.yaml: OK\n" in out
 
 
-def test_schema_v2():
+def test_schema_v2(capsys):
     """No errors when calling with proper v2 file."""
-    testargs = ['--schema', '2.0',
-                './tests/integration/data/v2.0/petstore.yaml']
+    testargs = [
+        "--schema",
+        "2.0",
+        "./tests/integration/data/v2.0/petstore.yaml",
+    ]
     main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v2.0/petstore.yaml: OK\n" in out
+
+
+def test_many(capsys):
+    """No errors when calling with proper v2 and v3 files."""
+    testargs = [
+        "./tests/integration/data/v2.0/petstore.yaml",
+        "./tests/integration/data/v3.0/petstore.yaml",
+    ]
+    main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v2.0/petstore.yaml: OK\n" in out
+    assert "./tests/integration/data/v3.0/petstore.yaml: OK\n" in out
+
+
+def test_errors_on_missing_description_best(capsys):
+    """An error is obviously printed given an empty schema."""
+    testargs = [
+        "./tests/integration/data/v3.0/missing-description.yaml",
+        "--schema=3.0.0",
+    ]
+    with pytest.raises(SystemExit):
+        main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert (
+        "./tests/integration/data/v3.0/missing-description.yaml: Validation Error:"
+        in out
+    )
+    assert "Failed validating" in out
+    assert "'description' is a required property" in out
+    assert "'$ref' is a required property" not in out
+    assert "1 more subschemas errors" in out
+
 
+def test_errors_on_missing_description_full(capsys):
+    """An error is obviously printed given an empty schema."""
+    testargs = [
+        "./tests/integration/data/v3.0/missing-description.yaml",
+        "--errors=all",
+        "--schema=3.0.0",
+    ]
+    with pytest.raises(SystemExit):
+        main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert (
+        "./tests/integration/data/v3.0/missing-description.yaml: Validation Error:"
+        in out
+    )
+    assert "Failed validating" in out
+    assert "'description' is a required property" in out
+    assert "'$ref' is a required property" in out
+    assert "1 more subschema error" not in out
 
-def test_schema_unknown():
+
+def test_schema_unknown(capsys):
     """Errors on running with unknown schema."""
-    testargs = ['--schema', 'x.x',
-                './tests/integration/data/v2.0/petstore.yaml']
+    testargs = [
+        "--schema",
+        "x.x",
+        "./tests/integration/data/v2.0/petstore.yaml",
+    ]
     with pytest.raises(SystemExit):
         main(testargs)
+    out, err = capsys.readouterr()
+    assert "error: argument --schema" in err
+    assert not out
 
 
-def test_validation_error():
+def test_validation_error(capsys):
     """SystemExit on running with ValidationError."""
-    testargs = ['--schema', '3.0.0',
-                './tests/integration/data/v2.0/petstore.yaml']
+    testargs = [
+        "--schema",
+        "3.0.0",
+        "./tests/integration/data/v2.0/petstore.yaml",
+    ]
     with pytest.raises(SystemExit):
         main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert (
+        "./tests/integration/data/v2.0/petstore.yaml: Validation Error:" in out
+    )
+    assert "Failed validating" in out
+    assert "'openapi' is a required property" in out
 
 
-def test_nonexisting_file():
+@mock.patch(
+    "openapi_spec_validator.__main__.OpenAPIV30SpecValidator.validate",
+    side_effect=Exception,
+)
+def test_unknown_error(m_validate, capsys):
+    """SystemExit on running with unknown error."""
+    testargs = [
+        "--schema",
+        "3.0.0",
+        "./tests/integration/data/v2.0/petstore.yaml",
+    ]
+    with pytest.raises(SystemExit):
+        main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "./tests/integration/data/v2.0/petstore.yaml: Error:" in out
+
+
+def test_nonexisting_file(capsys):
     """Calling with non-existing file should sys.exit."""
-    testargs = ['i_dont_exist.yaml']
+    testargs = ["i_dont_exist.yaml"]
     with pytest.raises(SystemExit):
         main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "No such file: i_dont_exist.yaml\n" in out
+
+
+def test_schema_stdin(capsys):
+    """Test schema from STDIN"""
+    spes_path = "./tests/integration/data/v3.0/petstore.yaml"
+    with open(spes_path) as spec_file:
+        spec_lines = spec_file.readlines()
+    spec_io = StringIO("".join(spec_lines))
+
+    testargs = ["--schema", "3.0.0", "-"]
+    with mock.patch("openapi_spec_validator.__main__.sys.stdin", spec_io):
+        main(testargs)
+    out, err = capsys.readouterr()
+    assert not err
+    assert "stdin: OK\n" in out
diff --git a/tests/integration/test_shortcuts.py b/tests/integration/test_shortcuts.py
index ca0159e..f6939c6 100644
--- a/tests/integration/test_shortcuts.py
+++ b/tests/integration/test_shortcuts.py
@@ -1,145 +1,167 @@
 import pytest
 
-from jsonschema.exceptions import ValidationError
-
-from openapi_spec_validator import validate_spec, validate_spec_url
-from openapi_spec_validator import validate_v2_spec, validate_v2_spec_url
-
-
-class BaseTestValidValidteV2Spec:
-
-    def test_valid(self, spec):
-        validate_v2_spec(spec)
-
-
-class BaseTestFaliedValidateV2Spec:
-
-    def test_failed(self, spec):
-        with pytest.raises(ValidationError):
-            validate_v2_spec(spec)
-
-
-class BaseTestValidValidteSpec:
-
-    def test_valid(self, spec):
-        validate_spec(spec)
-
-
-class BaseTestFaliedValidateSpec:
-
-    def test_failed(self, spec):
-        with pytest.raises(ValidationError):
-            validate_spec(spec)
-
-
-class BaseTestValidValidteV2SpecUrl:
-
-    def test_valid(self, spec_url):
-        validate_v2_spec_url(spec_url)
-
-
-class BaseTestFaliedValidateV2SpecUrl:
-
-    def test_failed(self, spec_url):
-        with pytest.raises(ValidationError):
-            validate_v2_spec_url(spec_url)
-
-
-class BaseTestValidValidteSpecUrl:
-
-    def test_valid(self, spec_url):
-        validate_spec_url(spec_url)
-
-
-class BaseTestFaliedValidateSpecUrl:
-
-    def test_failed(self, spec_url):
-        with pytest.raises(ValidationError):
+from openapi_spec_validator import OpenAPIV2SpecValidator
+from openapi_spec_validator import OpenAPIV30SpecValidator
+from openapi_spec_validator import openapi_v2_spec_validator
+from openapi_spec_validator import openapi_v30_spec_validator
+from openapi_spec_validator import validate
+from openapi_spec_validator import validate_spec
+from openapi_spec_validator import validate_spec_url
+from openapi_spec_validator import validate_url
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+from openapi_spec_validator.validation.exceptions import ValidatorDetectError
+
+
+class TestValidateSpec:
+    def test_spec_schema_version_not_detected(self):
+        spec = {}
+
+        with pytest.raises(ValidatorDetectError):
+            validate(spec)
+
+
+class TestLocalValidateSpecUrl:
+    def test_spec_schema_version_not_detected(self, factory):
+        spec_path = "data/empty.yaml"
+        spec_url = factory.spec_file_url(spec_path)
+
+        with pytest.raises(ValidatorDetectError):
+            validate_url(spec_url)
+
+
+class TestLiocalValidatev2Spec:
+    LOCAL_SOURCE_DIRECTORY = "data/v2.0/"
+
+    def local_test_suite_file_path(self, test_file):
+        return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "petstore.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+
+        validate(spec)
+        validate(spec, cls=OpenAPIV2SpecValidator)
+        with pytest.warns(DeprecationWarning):
+            validate_spec(spec, validator=openapi_v2_spec_validator)
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "empty.yaml",
+        ],
+    )
+    def test_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+
+        with pytest.raises(OpenAPIValidationError):
+            validate(spec, cls=OpenAPIV2SpecValidator)
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(OpenAPIValidationError):
+                validate_spec(spec, validator=openapi_v2_spec_validator)
+
+
+class TestLocalValidatev30Spec:
+    LOCAL_SOURCE_DIRECTORY = "data/v3.0/"
+
+    def local_test_suite_file_path(self, test_file):
+        return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "petstore.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+
+        validate(spec)
+        with pytest.warns(DeprecationWarning):
+            validate_spec(spec, spec_url=spec_url)
+        validate(spec, cls=OpenAPIV30SpecValidator)
+        with pytest.warns(DeprecationWarning):
+            validate_spec(spec, validator=openapi_v30_spec_validator)
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "empty.yaml",
+        ],
+    )
+    def test_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+
+        with pytest.raises(OpenAPIValidationError):
+            validate(spec, cls=OpenAPIV30SpecValidator)
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(OpenAPIValidationError):
+                validate_spec(spec, validator=openapi_v30_spec_validator)
+
+
+@pytest.mark.network
+class TestRemoteValidatev2SpecUrl:
+    REMOTE_SOURCE_URL = (
+        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/"
+    )
+
+    def remote_test_suite_file_path(self, test_file):
+        return f"{self.REMOTE_SOURCE_URL}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/"
+            "yaml/petstore.yaml",
+            "f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/"
+            "yaml/api-with-examples.yaml",
+            "f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/"
+            "yaml/petstore-expanded.yaml",
+        ],
+    )
+    def test_valid(self, spec_file):
+        spec_url = self.remote_test_suite_file_path(spec_file)
+
+        validate_url(spec_url)
+        validate_url(spec_url, cls=OpenAPIV2SpecValidator)
+        with pytest.warns(DeprecationWarning):
             validate_spec_url(spec_url)
-
-
-class TestLocalEmptyExample(BaseTestFaliedValidateSpec):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/empty.yaml")
-
-
-class TestLocalPetstoreV2Example(BaseTestValidValidteV2Spec):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v2.0/petstore.yaml")
-
-
-class TestLocalPetstoreExample(BaseTestValidValidteSpec):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/petstore.yaml")
-
-
-class TestPetstoreV2Example(BaseTestValidValidteV2SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/'
-            'yaml/petstore.yaml'
-        )
-
-
-class TestApiV2WithExampe(BaseTestValidValidteV2SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/'
-            'yaml/api-with-examples.yaml'
-        )
-
-
-class TestPetstoreV2ExpandedExample(BaseTestValidValidteV2SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/'
-            'yaml/petstore-expanded.yaml'
-        )
-
-
-class TestPetstoreExample(BaseTestValidValidteSpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'petstore.yaml'
-        )
-
-
-class TestApiWithExampe(BaseTestValidValidteSpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
-
-
-class TestPetstoreExpandedExample(BaseTestValidValidteSpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
+            validate_spec_url(spec_url, validator=openapi_v2_spec_validator)
+
+
+@pytest.mark.network
+class TestRemoteValidatev30SpecUrl:
+    REMOTE_SOURCE_URL = (
+        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/"
+    )
+
+    def remote_test_suite_file_path(self, test_file):
+        return f"{self.REMOTE_SOURCE_URL}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/"
+            "petstore.yaml",
+            "f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/"
+            "api-with-examples.yaml",
+            "970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/"
+            "api-with-examples.yaml",
+        ],
+    )
+    def test_valid(self, spec_file):
+        spec_url = self.remote_test_suite_file_path(spec_file)
+
+        validate_url(spec_url)
+        validate_url(spec_url, cls=OpenAPIV30SpecValidator)
+        with pytest.warns(DeprecationWarning):
+            validate_spec_url(spec_url, validator=openapi_v30_spec_validator)
diff --git a/tests/integration/test_validate.py b/tests/integration/test_validate.py
deleted file mode 100644
index 6d2f13f..0000000
--- a/tests/integration/test_validate.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import pytest
-
-from jsonschema.exceptions import ValidationError
-
-
-class BaseTestValidOpeAPIv3Validator(object):
-
-    @pytest.fixture
-    def spec_url(self):
-        return ''
-
-    def test_valid(self, validator, spec, spec_url):
-        return validator.validate(spec, spec_url=spec_url)
-
-
-class BaseTestFailedOpeAPIv3Validator(object):
-
-    @pytest.fixture
-    def spec_url(self):
-        return ''
-
-    def test_failed(self, validator, spec, spec_url):
-        with pytest.raises(ValidationError):
-            validator.validate(spec, spec_url=spec_url)
-
-
-class TestLocalEmptyExample(BaseTestFailedOpeAPIv3Validator):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/empty.yaml")
-
-
-class TestLocalPetstoreExample(BaseTestValidOpeAPIv3Validator):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/petstore.yaml")
-
-
-class TestLocalPetstoreSeparateExample(BaseTestValidOpeAPIv3Validator):
-
-    spec_file = "data/v3.0/petstore-separate/spec/openapi.yaml"
-
-    @pytest.fixture
-    def spec_url(self, factory):
-        return factory.spec_url(self.spec_file)
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file(self.spec_file)
-
-
-class TestLocalParentReferenceExample(BaseTestValidOpeAPIv3Validator):
-
-    spec_file = "data/v3.0/parent-reference/openapi.yaml"
-
-    @pytest.fixture
-    def spec_url(self, factory):
-        return factory.spec_url(self.spec_file)
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file(self.spec_file)
-
-
-class TestPetstoreExample(BaseTestValidOpeAPIv3Validator):
-
-    @pytest.fixture
-    def spec(self, factory):
-        url = (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'petstore.yaml'
-        )
-        return factory.spec_from_url(url)
-
-
-class TestApiWithExampe(BaseTestValidOpeAPIv3Validator):
-
-    @pytest.fixture
-    def spec(self, factory):
-        url = (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
-        return factory.spec_from_url(url)
-
-
-class TestPetstoreExpandedExample(BaseTestValidOpeAPIv3Validator):
-
-    @pytest.fixture
-    def spec(self, factory):
-        url = (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
-        return factory.spec_from_url(url)
diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py
deleted file mode 100644
index a1e5acd..0000000
--- a/tests/integration/test_validators.py
+++ /dev/null
@@ -1,136 +0,0 @@
-from jsonschema.exceptions import ValidationError
-
-from openapi_spec_validator.exceptions import (
-    ExtraParametersError, UnresolvableParameterError,
-)
-
-
-class TestSpecValidatorIterErrors(object):
-
-    def test_empty(self, validator):
-        spec = {}
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list[0].__class__ == ValidationError
-        assert errors_list[0].message == "'openapi' is a required property"
-        assert errors_list[1].__class__ == ValidationError
-        assert errors_list[1].message == "'info' is a required property"
-        assert errors_list[2].__class__ == ValidationError
-        assert errors_list[2].message == "'paths' is a required property"
-
-    def test_info_empty(self, validator):
-        spec = {
-            'openapi': '3.0.0',
-            'info': {},
-            'paths': {},
-        }
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list[0].__class__ == ValidationError
-        assert errors_list[0].message == "'title' is a required property"
-
-    def test_minimalistic(self, validator):
-        spec = {
-            'openapi': '3.0.0',
-            'info': {
-                'title': 'Test Api',
-                'version': '0.0.1',
-            },
-            'paths': {},
-        }
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list == []
-
-    def test_same_parameters_names(self, validator):
-        spec = {
-            'openapi': '3.0.0',
-            'info': {
-                'title': 'Test Api',
-                'version': '0.0.1',
-            },
-            'paths': {
-                '/test/{param1}': {
-                    'parameters': [
-                        {
-                            'name': 'param1',
-                            'in': 'query',
-                        },
-                        {
-                            'name': 'param1',
-                            'in': 'path',
-                        },
-                    ],
-                },
-            },
-        }
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list == []
-
-    def test_extra_parameters_in_required(self, validator):
-        spec = {
-            'openapi': '3.0.0',
-            'info': {
-                'title': 'Test Api',
-                'version': '0.0.1',
-            },
-            'paths': {},
-            'components': {
-                'schemas': {
-                    'testSchema': {
-                        'type': 'object',
-                        'required': [
-                            'testparam1',
-                        ]
-                    }
-                },
-            },
-        }
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list[0].__class__ == ExtraParametersError
-        assert errors_list[0].message == (
-            "Required list has not defined properties: ['testparam1']"
-        )
-
-    def test_undocumented_parameter(self, validator):
-        spec = {
-            'openapi': '3.0.0',
-            'info': {
-                'title': 'Test Api',
-                'version': '0.0.1',
-            },
-            'paths': {
-                '/test/{param1}/{param2}': {
-                    'get': {
-                        'responses': {},
-                    },
-                    'parameters': [
-                        {
-                            'name': 'param1',
-                            'in': 'path',
-                        },
-                    ],
-                },
-            },
-        }
-
-        errors = validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert errors_list[0].__class__ == UnresolvableParameterError
-        assert errors_list[0].message == (
-            "Path parameter 'param2' for 'get' operation in "
-            "'/test/{param1}/{param2}' was not resolved"
-        )
diff --git a/tests/integration/test_versions.py b/tests/integration/test_versions.py
new file mode 100644
index 0000000..891dd50
--- /dev/null
+++ b/tests/integration/test_versions.py
@@ -0,0 +1,43 @@
+import pytest
+
+from openapi_spec_validator.versions import consts as versions
+from openapi_spec_validator.versions.exceptions import OpenAPIVersionNotFound
+from openapi_spec_validator.versions.shortcuts import get_spec_version
+
+
+class TestGetSpecVersion:
+    def test_no_keyword(self):
+        spec = {}
+
+        with pytest.raises(OpenAPIVersionNotFound):
+            get_spec_version(spec)
+
+    @pytest.mark.parametrize("keyword", ["swagger", "openapi"])
+    @pytest.mark.parametrize("version", ["x.y.z", "xyz2.0.0", "2.xyz0.0"])
+    def test_invalid(self, keyword, version):
+        spec = {
+            keyword: version,
+        }
+
+        with pytest.raises(OpenAPIVersionNotFound):
+            get_spec_version(spec)
+
+    @pytest.mark.parametrize(
+        "keyword,version,expected",
+        [
+            ("swagger", "2.0", versions.OPENAPIV2),
+            ("openapi", "3.0.0", versions.OPENAPIV30),
+            ("openapi", "3.0.1", versions.OPENAPIV30),
+            ("openapi", "3.0.2", versions.OPENAPIV30),
+            ("openapi", "3.0.3", versions.OPENAPIV30),
+            ("openapi", "3.1.0", versions.OPENAPIV31),
+        ],
+    )
+    def test_valid(self, keyword, version, expected):
+        spec = {
+            keyword: version,
+        }
+
+        result = get_spec_version(spec)
+
+        assert result == expected
diff --git a/tests/integration/validation/test_exceptions.py b/tests/integration/validation/test_exceptions.py
new file mode 100644
index 0000000..c8f5961
--- /dev/null
+++ b/tests/integration/validation/test_exceptions.py
@@ -0,0 +1,498 @@
+from openapi_spec_validator import OpenAPIV2SpecValidator
+from openapi_spec_validator import OpenAPIV30SpecValidator
+from openapi_spec_validator.validation.exceptions import (
+    DuplicateOperationIDError,
+)
+from openapi_spec_validator.validation.exceptions import ExtraParametersError
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+from openapi_spec_validator.validation.exceptions import (
+    UnresolvableParameterError,
+)
+
+
+class TestSpecValidatorIterErrors:
+    def test_empty(self):
+        spec = {}
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == "'openapi' is a required property"
+        assert errors_list[1].__class__ == OpenAPIValidationError
+        assert errors_list[1].message == "'info' is a required property"
+        assert errors_list[2].__class__ == OpenAPIValidationError
+        assert errors_list[2].message == "'paths' is a required property"
+
+    def test_info_empty(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {},
+            "paths": {},
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == "'title' is a required property"
+
+    def test_minimalistic(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {},
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_same_parameters_names(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/{param1}": {
+                    "parameters": [
+                        {
+                            "name": "param1",
+                            "in": "query",
+                            "schema": {
+                                "type": "integer",
+                            },
+                        },
+                        {
+                            "name": "param1",
+                            "in": "path",
+                            "schema": {
+                                "type": "integer",
+                            },
+                            "required": True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_same_operation_ids(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test": {
+                    "get": {
+                        "operationId": "operation1",
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                    },
+                    "post": {
+                        "operationId": "operation1",
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                    },
+                },
+                "/test2": {
+                    "get": {
+                        "operationId": "operation1",
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 2
+        assert errors_list[0].__class__ == DuplicateOperationIDError
+        assert errors_list[1].__class__ == DuplicateOperationIDError
+
+    def test_allow_allof_required_no_properties(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {},
+            "components": {
+                "schemas": {
+                    "Credit": {
+                        "type": "object",
+                        "properties": {
+                            "clientId": {"type": "string"},
+                        },
+                    },
+                    "CreditCreate": {
+                        "allOf": [
+                            {"$ref": "#/components/schemas/Credit"},
+                            {"required": ["clientId"]},
+                        ]
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_allow_allof_when_required_is_linked_to_the_parent_object(self):
+        spec = {
+            "openapi": "3.0.1",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {},
+            "components": {
+                "schemas": {
+                    "Address": {
+                        "type": "object",
+                        "properties": {
+                            "SubdivisionCode": {
+                                "type": "string",
+                                "description": "State or region",
+                            },
+                            "Town": {
+                                "type": "string",
+                                "description": "Town or city",
+                            },
+                            "CountryCode": {
+                                "type": "string",
+                            },
+                        },
+                    },
+                    "AddressCreation": {
+                        "required": ["CountryCode", "Town"],
+                        "type": "object",
+                        "allOf": [{"$ref": "#/components/schemas/Address"}],
+                    },
+                }
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_allow_extra_parameters_in_required(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {},
+            "components": {
+                "schemas": {
+                    "testSchema": {
+                        "type": "object",
+                        "required": [
+                            "testparam1",
+                        ],
+                    }
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 0
+
+    def test_undocumented_parameter(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/{param1}/{param2}": {
+                    "get": {
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                    },
+                    "parameters": [
+                        {
+                            "name": "param1",
+                            "in": "path",
+                            "schema": {
+                                "type": "integer",
+                            },
+                            "required": True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == UnresolvableParameterError
+        assert errors_list[0].message == (
+            "Path parameter 'param2' for 'get' operation in "
+            "'/test/{param1}/{param2}' was not resolved"
+        )
+
+    def test_default_value_wrong_type(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {},
+            "components": {
+                "schemas": {
+                    "test": {
+                        "type": "integer",
+                        "default": "invaldtype",
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type 'integer'"
+        )
+
+    def test_parameter_default_value_wrong_type(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/{param1}": {
+                    "get": {
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                    },
+                    "parameters": [
+                        {
+                            "name": "param1",
+                            "in": "path",
+                            "schema": {
+                                "type": "integer",
+                                "default": "invaldtype",
+                            },
+                            "required": True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type 'integer'"
+        )
+
+    def test_parameter_default_value_wrong_type_swagger(self):
+        spec = {
+            "swagger": "2.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/": {
+                    "get": {
+                        "responses": {
+                            "200": {
+                                "description": "OK",
+                                "schema": {"type": "object"},
+                            },
+                        },
+                        "parameters": [
+                            {
+                                "name": "param1",
+                                "in": "query",
+                                "type": "integer",
+                                "default": "invaldtype",
+                            },
+                        ],
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV2SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type 'integer'"
+        )
+
+    def test_parameter_default_value_with_reference(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/": {
+                    "get": {
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                        "parameters": [
+                            {
+                                "name": "param1",
+                                "in": "query",
+                                "schema": {
+                                    "allOf": [
+                                        {
+                                            "$ref": "#/components/schemas/type",
+                                        }
+                                    ],
+                                    "default": 1,
+                                },
+                            },
+                        ],
+                    },
+                },
+            },
+            "components": {
+                "schemas": {
+                    "type": {
+                        "type": "integer",
+                    }
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_parameter_custom_format_checker_not_found(self):
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/": {
+                    "get": {
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                        "parameters": [
+                            {
+                                "name": "param1",
+                                "in": "query",
+                                "schema": {
+                                    "type": "string",
+                                    "format": "custom",
+                                    "default": "customvalue",
+                                },
+                            },
+                        ],
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_parameter_default_value_custom_format_invalid(self):
+        from openapi_schema_validator import oas30_format_checker
+
+        @oas30_format_checker.checks("custom")
+        def validate(to_validate) -> bool:
+            return to_validate == "valid"
+
+        spec = {
+            "openapi": "3.0.0",
+            "info": {
+                "title": "Test Api",
+                "version": "0.0.1",
+            },
+            "paths": {
+                "/test/": {
+                    "get": {
+                        "responses": {
+                            "default": {
+                                "description": "default response",
+                            },
+                        },
+                        "parameters": [
+                            {
+                                "name": "param1",
+                                "in": "query",
+                                "schema": {
+                                    "type": "string",
+                                    "format": "custom",
+                                    "default": "invalid",
+                                },
+                            },
+                        ],
+                    },
+                },
+            },
+        }
+
+        errors = OpenAPIV30SpecValidator(spec).iter_errors()
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == ("'invalid' is not a 'custom'")
diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py
new file mode 100644
index 0000000..305e1f8
--- /dev/null
+++ b/tests/integration/validation/test_validators.py
@@ -0,0 +1,207 @@
+import pytest
+from jsonschema_path import SchemaPath
+from referencing.exceptions import Unresolvable
+
+from openapi_spec_validator import OpenAPIV2SpecValidator
+from openapi_spec_validator import OpenAPIV30SpecValidator
+from openapi_spec_validator import OpenAPIV31SpecValidator
+from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
+
+
+class TestLocalOpenAPIv2Validator:
+    LOCAL_SOURCE_DIRECTORY = "data/v2.0/"
+
+    def local_test_suite_file_path(self, test_file):
+        return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "petstore.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+        schema_path = SchemaPath.from_dict(
+            spec,
+            base_uri=spec_url,
+        )
+        validator = OpenAPIV2SpecValidator(schema_path)
+
+        validator.validate()
+
+        assert validator.is_valid() == True
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "empty.yaml",
+        ],
+    )
+    def test_validation_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+        validator = OpenAPIV2SpecValidator(spec, base_uri=spec_url)
+
+        with pytest.raises(OpenAPIValidationError):
+            validator.validate()
+
+        assert validator.is_valid() == False
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "missing-reference.yaml",
+        ],
+    )
+    def test_ref_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+
+        with pytest.raises(Unresolvable):
+            OpenAPIV2SpecValidator(spec, base_uri=spec_url).validate()
+
+
+class TestLocalOpenAPIv30Validator:
+    LOCAL_SOURCE_DIRECTORY = "data/v3.0/"
+
+    def local_test_suite_file_path(self, test_file):
+        return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "petstore.yaml",
+            "petstore-separate/spec/openapi.yaml",
+            "parent-reference/openapi.yaml",
+            "property-recursive.yaml",
+            "read-only-write-only.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+        validator = OpenAPIV30SpecValidator(spec, base_uri=spec_url)
+
+        validator.validate()
+
+        assert validator.is_valid() == True
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "empty.yaml",
+        ],
+    )
+    def test_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+        validator = OpenAPIV30SpecValidator(spec, base_uri=spec_url)
+
+        with pytest.raises(OpenAPIValidationError):
+            validator.validate()
+
+        assert validator.is_valid() == False
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "property-missing-reference.yaml",
+        ],
+    )
+    def test_ref_failed(self, factory, spec_file):
+        spec_path = self.local_test_suite_file_path(spec_file)
+        spec = factory.spec_from_file(spec_path)
+        spec_url = factory.spec_file_url(spec_path)
+
+        with pytest.raises(Unresolvable):
+            OpenAPIV30SpecValidator(spec, base_uri=spec_url).validate()
+
+
+@pytest.mark.network
+class TestRemoteOpenAPIv30Validator:
+    REMOTE_SOURCE_URL = (
+        "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/"
+    )
+
+    def remote_test_suite_file_path(self, test_file):
+        return f"{self.REMOTE_SOURCE_URL}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/"
+            "petstore.yaml",
+            "f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/"
+            "api-with-examples.yaml",
+            "970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/"
+            "api-with-examples.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_url = self.remote_test_suite_file_path(spec_file)
+        spec = factory.spec_from_url(spec_url)
+
+        OpenAPIV30SpecValidator(spec, base_uri=spec_url).validate()
+
+
+@pytest.mark.network
+class TestRemoteOpenAPIv31Validator:
+    REMOTE_SOURCE_URL = (
+        "https://raw.githubusercontent.com/"
+        "OAI/OpenAPI-Specification/"
+        "d9ac75b00c8bf405c2c90cfa9f20370564371dec/"
+    )
+
+    def remote_test_suite_file_path(self, test_file):
+        return f"{self.REMOTE_SOURCE_URL}{test_file}"
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "comp_pathitems.yaml",
+            "info_summary.yaml",
+            "license_identifier.yaml",
+            "mega.yaml",
+            "minimal_comp.yaml",
+            "minimal_hooks.yaml",
+            "minimal_paths.yaml",
+            "path_no_response.yaml",
+            "path_var_empty_pathitem.yaml",
+            "schema.yaml",
+            "servers.yaml",
+            "valid_schema_types.yaml",
+        ],
+    )
+    def test_valid(self, factory, spec_file):
+        spec_url = self.remote_test_suite_file_path(
+            f"tests/v3.1/pass/{spec_file}"
+        )
+        spec = factory.spec_from_url(spec_url)
+
+        OpenAPIV31SpecValidator(spec, base_uri=spec_url).validate()
+
+    @pytest.mark.parametrize(
+        "spec_file",
+        [
+            "invalid_schema_types.yaml",
+            "no_containers.yaml",
+            "server_enum_empty.yaml",
+            "servers.yaml",
+            "unknown_container.yaml",
+        ],
+    )
+    def test_failed(self, factory, spec_file):
+        spec_url = self.remote_test_suite_file_path(
+            f"tests/v3.1/fail/{spec_file}"
+        )
+        spec = factory.spec_from_url(spec_url)
+
+        with pytest.raises(OpenAPIValidationError):
+            OpenAPIV31SpecValidator(spec, base_uri=spec_url).validate()
diff --git a/tox.ini b/tox.ini
index 1ace75b..0f99522 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = {py33,py34,py35,py36}-{default,simplejson}
+envlist = {py37,py38,py39,py310}-{default,simplejson}
 
 [testenv]
 deps =
