├── .bandit.yml
├── .env
├── .flake8
├── .github
└── workflows
│ ├── publish.yml
│ └── tests.yml
├── .gitignore
├── .readthedocs.yml
├── LICENSE
├── README.md
├── docs
├── Makefile
├── _config.yml
├── conf.py
└── index.rst
├── example-docs.png
├── example
├── Dockerfile
├── README.md
├── app
│ ├── __init__.py
│ ├── config.py
│ └── main.py
├── docker-compose.yml
├── poetry.lock
└── pyproject.toml
├── fastapi_third_party_auth
├── __init__.py
├── auth.py
├── discovery.py
├── grant_types.py
└── idtoken_types.py
├── poetry.lock
├── pyproject.toml
└── tests
├── __init__.py
├── conftest.py
├── fixtures
└── AuthServerDiscovery.json
├── test_auth.py
└── test_types.py
/.bandit.yml:
--------------------------------------------------------------------------------
1 | ### This config may optionally select a subset of tests to run or skip by
2 | ### filling out the 'tests' and 'skips' lists given below. If no tests are
3 | ### specified for inclusion then it is assumed all tests are desired. The skips
4 | ### set will remove specific tests from the include set. This can be controlled
5 | ### using the -t/-s CLI options. Note that the same test ID should not appear
6 | ### in both 'tests' and 'skips', this would be nonsensical and is detected by
7 | ### Bandit at runtime.
8 |
9 | # Available tests:
10 | # B101 : assert_used
11 | # B102 : exec_used
12 | # B103 : set_bad_file_permissions
13 | # B104 : hardcoded_bind_all_interfaces
14 | # B105 : hardcoded_password_string
15 | # B106 : hardcoded_password_funcarg
16 | # B107 : hardcoded_password_default
17 | # B108 : hardcoded_tmp_directory
18 | # B110 : try_except_pass
19 | # B112 : try_except_continue
20 | # B201 : flask_debug_true
21 | # B301 : pickle
22 | # B302 : marshal
23 | # B303 : md5
24 | # B304 : ciphers
25 | # B305 : cipher_modes
26 | # B306 : mktemp_q
27 | # B307 : eval
28 | # B308 : mark_safe
29 | # B309 : httpsconnection
30 | # B310 : urllib_urlopen
31 | # B311 : random
32 | # B312 : telnetlib
33 | # B313 : xml_bad_cElementTree
34 | # B314 : xml_bad_ElementTree
35 | # B315 : xml_bad_expatreader
36 | # B316 : xml_bad_expatbuilder
37 | # B317 : xml_bad_sax
38 | # B318 : xml_bad_minidom
39 | # B319 : xml_bad_pulldom
40 | # B320 : xml_bad_etree
41 | # B321 : ftplib
42 | # B322 : input
43 | # B323 : unverified_context
44 | # B324 : hashlib_new_insecure_functions
45 | # B325 : tempnam
46 | # B401 : import_telnetlib
47 | # B402 : import_ftplib
48 | # B403 : import_pickle
49 | # B404 : import_subprocess
50 | # B405 : import_xml_etree
51 | # B406 : import_xml_sax
52 | # B407 : import_xml_expat
53 | # B408 : import_xml_minidom
54 | # B409 : import_xml_pulldom
55 | # B410 : import_lxml
56 | # B411 : import_xmlrpclib
57 | # B412 : import_httpoxy
58 | # B413 : import_pycrypto
59 | # B501 : request_with_no_cert_validation
60 | # B502 : ssl_with_bad_version
61 | # B503 : ssl_with_bad_defaults
62 | # B504 : ssl_with_no_version
63 | # B505 : weak_cryptographic_key
64 | # B506 : yaml_load
65 | # B507 : ssh_no_host_key_verification
66 | # B601 : paramiko_calls
67 | # B602 : subprocess_popen_with_shell_equals_true
68 | # B603 : subprocess_without_shell_equals_true
69 | # B604 : any_other_function_with_shell_equals_true
70 | # B605 : start_process_with_a_shell
71 | # B606 : start_process_with_no_shell
72 | # B607 : start_process_with_partial_path
73 | # B608 : hardcoded_sql_expressions
74 | # B609 : linux_commands_wildcard_injection
75 | # B610 : django_extra_used
76 | # B611 : django_rawsql_used
77 | # B701 : jinja2_autoescape_false
78 | # B702 : use_of_mako_templates
79 | # B703 : django_mark_safe
80 |
81 | # (optional) list included test IDs here, eg '[B101, B406]':
82 | tests:
83 |
84 | # (optional) list skipped test IDs here, eg '[B101, B406]':
85 | skips: [B101, B104]
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | PYPI_USERNAME=
2 | PYPI_PASSWORD=
3 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = W291, W293, E501, E731, W503
3 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | python-version: [3.9]
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up Python
17 | uses: actions/setup-python@v1
18 | with:
19 | python-version: ${{ matrix.python-version }}
20 |
21 | - name: Install Poetry
22 | uses: snok/install-poetry@v1
23 | with:
24 | virtualenvs-create: true
25 | virtualenvs-in-project: true
26 |
27 | - name: Load cached venv
28 | id: cached-poetry-dependencies
29 | uses: actions/cache@v2
30 | with:
31 | path: .venv
32 | key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
33 |
34 | - name: Install dependencies
35 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
36 | run: poetry install --no-interaction --no-root
37 |
38 | - name: Build wheels
39 | run: |
40 | poetry version $(git tag --points-at HEAD)
41 | poetry build
42 |
43 | - name: Upload
44 | env:
45 | USERNAME: __token__
46 | PASSWORD: ${{ secrets.PYPI_TOKEN }}
47 | run: |
48 | poetry publish --username=$USERNAME --password=$PASSWORD
49 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | # Run tests and check pre-commit
2 | name: Test
3 |
4 | on:
5 | push:
6 | pull_request:
7 | types: [opened, synchronize]
8 | workflow_dispatch:
9 |
10 | jobs:
11 | test:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | python-version: [3.8, 3.9]
16 | fail-fast: false
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Set up Python
21 | uses: actions/setup-python@v1
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 |
25 | - name: Install Poetry
26 | uses: snok/install-poetry@v1
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .task
3 | scratch.py
4 | .env
5 |
6 | # Sphinx docs
7 | docs/_build
8 | docs/_static
9 | docs/_templates
10 | docs/make.bat
11 |
12 | # Byte-compiled / optimized / DLL files
13 | __pycache__/
14 | *.py[cod]
15 | *$py.class
16 |
17 | # C extensions
18 | *.so
19 |
20 | # Distribution / packaging
21 | .Python
22 | build/
23 | develop-eggs/
24 | dist/
25 | downloads/
26 | eggs/
27 | .eggs/
28 | lib/
29 | lib64/
30 | parts/
31 | sdist/
32 | var/
33 | wheels/
34 | share/python-wheels/
35 | *.egg-info/
36 | .installed.cfg
37 | *.egg
38 | MANIFEST
39 |
40 | # PyInstaller
41 | # Usually these files are written by a python script from a template
42 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
43 | *.manifest
44 | *.spec
45 |
46 | # Installer logs
47 | pip-log.txt
48 | pip-delete-this-directory.txt
49 |
50 | # Unit test / coverage reports
51 | htmlcov/
52 | .tox/
53 | .nox/
54 | .coverage
55 | .coverage.*
56 | .cache
57 | nosetests.xml
58 | coverage.xml
59 | *.cover
60 | *.py,cover
61 | .hypothesis/
62 | .pytest_cache/
63 | cover/
64 |
65 | # Translations
66 | *.mo
67 | *.pot
68 |
69 | # Django stuff:
70 | *.log
71 | local_settings.py
72 | db.sqlite3
73 | db.sqlite3-journal
74 |
75 | # Flask stuff:
76 | instance/
77 | .webassets-cache
78 |
79 | # Scrapy stuff:
80 | .scrapy
81 |
82 | # Sphinx documentation
83 | docs/_build/
84 |
85 | # PyBuilder
86 | .pybuilder/
87 | target/
88 |
89 | # Jupyter Notebook
90 | .ipynb_checkpoints
91 |
92 | # IPython
93 | profile_default/
94 | ipython_config.py
95 |
96 | # pyenv
97 | # For a library or package, you might want to ignore these files since the code is
98 | # intended to run in multiple environments; otherwise, check them in:
99 | # .python-version
100 |
101 | # pipenv
102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
105 | # install all needed dependencies.
106 | #Pipfile.lock
107 |
108 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
109 | __pypackages__/
110 |
111 | # Celery stuff
112 | celerybeat-schedule
113 | celerybeat.pid
114 |
115 | # SageMath parsed files
116 | *.sage.py
117 |
118 | # Environments
119 | .env
120 | .venv
121 | env/
122 | venv/
123 | ENV/
124 | env.bak/
125 | venv.bak/
126 |
127 | # Spyder project settings
128 | .spyderproject
129 | .spyproject
130 |
131 | # Rope project settings
132 | .ropeproject
133 |
134 | # mkdocs documentation
135 | /site
136 |
137 | # mypy
138 | .mypy_cache/
139 | .dmypy.json
140 | dmypy.json
141 |
142 | # Pyre type checker
143 | .pyre/
144 |
145 | # pytype static type analyzer
146 | .pytype/
147 |
148 | # Cython debug symbols
149 | cython_debug/
150 |
151 | .python-version
152 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | sphinx:
4 | configuration: docs/conf.py
5 |
6 | python:
7 | version: 3.8
8 | install:
9 | - method: pip
10 | path: .
11 | extra_requirements:
12 | - docs
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Harry
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FastAPI Third Party Auth
2 |
3 |
4 |
6 |
8 |
9 |
10 |
11 |
12 |
14 |
16 |
17 |
18 |
19 | ---
20 |
21 | **Documentation**: https://fastapi-third-party-auth.readthedocs.io/
22 |
23 | **Source Code**: https://github.com/aiwizo/fastapi-third-party-auth
24 |
25 | ---
26 |
27 | Simple library for using a third party authentication service with
28 | [FastAPI](https://github.com/tiangolo/fastapi). Verifies and decrypts 3rd party
29 | OpenID Connect tokens to protect your endpoints.
30 |
31 | Easily used with authentication services such as:
32 |
33 | - [Keycloak](https://www.keycloak.org/) (open source)
34 | - [SuperTokens](https://supertokens.com/) (open source)
35 | - [Auth0](https://auth0.com/)
36 | - [Okta](https://www.okta.com/products/authentication/)
37 |
38 | FastAPI's generated interactive documentation supports the grant flows:
39 |
40 | ```python3
41 | GrantType.AUTHORIZATION_CODE
42 | GrantType.IMPLICIT
43 | GrantType.PASSWORD
44 | GrantType.CLIENT_CREDENTIALS
45 | ```
46 |
47 | 
48 |
49 | ## Installation
50 |
51 | ```
52 | poetry add fastapi-third-party-auth
53 | ```
54 |
55 | Or, for the old-timers:
56 |
57 | ```
58 | pip install fastapi-third-party-auth
59 | ```
60 |
61 | ## Usage
62 |
63 | See [this example](tree/master/example) for how to use
64 | `docker-compose` to set up authentication with `fastapi-third-party-auth` +
65 | [Keycloak](https://www.keycloak.org/).
66 |
67 | ### Standard usage
68 |
69 | ```python3
70 | from fastapi import Depends
71 | from fastapi import FastAPI
72 | from fastapi import Security
73 | from fastapi import status
74 |
75 | from fastapi_third_party_auth import Auth
76 | from fastapi_third_party_auth import GrantType
77 | from fastapi_third_party_auth import KeycloakIDToken
78 |
79 | auth = Auth(
80 | openid_connect_url="http://localhost:8080/auth/realms/my-realm/.well-known/openid-configuration",
81 | issuer="http://localhost:8080/auth/realms/my-realm", # optional, verification only
82 | client_id="my-client", # optional, verification only
83 | scopes=["email"], # optional, verification only
84 | grant_types=[GrantType.IMPLICIT], # optional, docs only
85 | idtoken_model=KeycloakIDToken, # optional, verification only
86 | )
87 |
88 | app = FastAPI(
89 | title="Example",
90 | version="dev",
91 | dependencies=[Depends(auth)],
92 | )
93 |
94 | @app.get("/protected")
95 | def protected(id_token: KeycloakIDToken = Security(auth.required)):
96 | return dict(message=f"You are {id_token.email}")
97 | ```
98 |
99 | ### Optional: Custom token validation
100 |
101 | The IDToken class will accept any number of extra fields but you can also
102 | validate fields in the token like this:
103 |
104 | ```python3
105 | class MyAuthenticatedUser(IDToken):
106 | custom_field: str
107 | custom_default: float = 3.14
108 |
109 | auth = Auth(
110 | ...,
111 | idtoken_model=MyAuthenticatedUser,
112 | )
113 | ```
114 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-midnight
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 | from typing import List
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = "fastapi-third-party-auth"
21 | copyright = "2020, Harry M. Winters, Richard Löwenström"
22 | author = "Harry M. Winters, Richard Löwenström"
23 |
24 | # The full version, including alpha/beta/rc tags
25 | release = "0.0.0"
26 |
27 |
28 | # -- General configuration ---------------------------------------------------
29 |
30 | # Add any Sphinx extension module names here, as strings. They can be
31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32 | # ones.
33 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"]
34 |
35 | # Add any paths that contain templates here, relative to this directory.
36 | templates_path = ["_templates"]
37 |
38 | # List of patterns, relative to source directory, that match files and
39 | # directories to ignore when looking for source files.
40 | # This pattern also affects html_static_path and html_extra_path.
41 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
42 |
43 |
44 | # -- Options for HTML output -------------------------------------------------
45 |
46 | # The theme to use for HTML and HTML Help pages. See the documentation for
47 | # a list of builtin themes.
48 | #
49 | html_theme = "sphinx_rtd_theme"
50 |
51 | # Add any paths that contain custom static files (such as style sheets) here,
52 | # relative to this directory. They are copied after the builtin static files,
53 | # so a file named "default.css" will overwrite the builtin "default.css".
54 | html_static_path: List[str] = []
55 |
56 | autodoc_default_options = {
57 | "members": True,
58 | "member-order": "bysource",
59 | "special-members": "__init__",
60 | "undoc-members": True,
61 | "exclude-members": "__weakref__",
62 | }
63 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to fastapi-third-party-auth's documentation!
2 | ====================================================
3 |
4 | Verify and decrypt 3rd party OpenID Connect tokens to protect your
5 | `FastAPI `_ endpoints.
6 |
7 | Easily used with authenticators such as:
8 |
9 | - `Keycloak `_ (open source)
10 | - `SuperTokens `_ (open source)
11 | - `Auth0 `_
12 | - `Okta `_
13 |
14 |
15 | FastAPI's generated interactive documentation supports the grant types
16 | ``authorization_code``, ``implicit``, ``password`` and ``client_credentials``.
17 |
18 | .. toctree::
19 | :maxdepth: 2
20 | :caption: Contents:
21 |
22 | Indices and tables
23 | ==================
24 |
25 | * :ref:`genindex`
26 | * :ref:`modindex`
27 | * :ref:`search`
28 |
29 | Installation
30 | ------------
31 |
32 | .. code-block:: bash
33 |
34 | poetry add fastapi-third-party-auth
35 |
36 | Or, for the old-timers:
37 |
38 | .. code-block:: bash
39 |
40 | pip install fastapi-third-party-auth
41 |
42 | Example
43 | -------
44 |
45 | Basic configuration for verifying OIDC tokens.
46 |
47 | .. code-block:: python3
48 |
49 | from fastapi import Depends
50 | from fastapi import FastAPI
51 | from fastapi import Security
52 | from fastapi import status
53 |
54 | from fastapi_third_party_auth import Auth
55 | from fastapi_third_party_auth import GrantType
56 | from fastapi_third_party_auth import KeycloakIDToken
57 |
58 | auth = Auth(
59 | openid_connect_url="http://localhost:8080/auth/realms/my-realm/.well-known/openid-configuration",
60 | issuer="http://localhost:8080/auth/realms/my-realm", # optional, verification only
61 | client_id="my-client", # optional, verification only
62 | scopes=["email"], # optional, verification only
63 | grant_types=[GrantType.IMPLICIT], # optional, docs only
64 | idtoken_model=KeycloakIDToken, # optional, verification only
65 | )
66 |
67 | app = FastAPI(
68 | title="Example",
69 | version="dev",
70 | dependencies=[Depends(auth)],
71 | )
72 |
73 | @app.get("/protected")
74 | def protected(id_token: KeycloakIDToken = Security(auth.required)):
75 | return dict(message=f"You are {id_token.email}")
76 |
77 |
78 | API Reference
79 | =============
80 |
81 | Auth
82 | ----
83 | .. automodule:: fastapi_third_party_auth.auth
84 | :members:
85 |
86 | Grant Types
87 | -----------
88 | .. automodule:: fastapi_third_party_auth.grant_types
89 | :members:
90 |
91 | IDToken Types
92 | -------------
93 | .. automodule:: fastapi_third_party_auth.idtoken_types
94 | :members:
95 |
--------------------------------------------------------------------------------
/example-docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextml-code/fastapi-third-party-auth/039ee19eef78889f4a09342f18680ecccd7e7879/example-docs.png
--------------------------------------------------------------------------------
/example/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:experimental
2 | FROM python:3.8.8
3 |
4 | RUN apt-get update \
5 | && apt-get install -y \
6 | gcc \
7 | ffmpeg \
8 | libsm6 \
9 | libxext6 \
10 | git \
11 | curl
12 |
13 | RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
14 |
15 | WORKDIR /app
16 | COPY pyproject.toml .
17 | COPY poetry.lock .
18 |
19 | RUN ~/.poetry/bin/poetry install
20 |
21 | COPY app app
22 |
23 | EXPOSE 8000
24 |
25 | CMD ["/root/.poetry/bin/poetry", "run", "python", "-m", "app.main", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
26 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example with keycloak
2 |
3 | ## Setup your realm
4 |
5 | 1. Start up keycloak with `docker-compose up` (the fastapi app will crash since
6 | we do not have a realm yet).
7 | 2. Log into keycloak at http://localhost:8080 with username/password `admin/admin`.
8 | 3. Create a realm `my-realm`. This will set your `openid_connect_url` to `http://localhost:8080/auth/realms/my-realm/.well-known/openid-configuration`
9 | and your issuer to `http://localhost:8080/auth/realms/my-realm`.
10 | 4. Allow implicit flow (in order for login in interactive docs to work).
11 | 5. Create a user and add credentials (password).
12 |
13 | ## Login into docs with your credentials
14 |
15 | 1. Kill app and then restart with `docker-compose up`.
16 | 2. Go to `http://localhost:8000/docs` and login with your credentials by
17 | clicking `authorize` in the top right corner.
18 |
--------------------------------------------------------------------------------
/example/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextml-code/fastapi-third-party-auth/039ee19eef78889f4a09342f18680ecccd7e7879/example/app/__init__.py
--------------------------------------------------------------------------------
/example/app/config.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseSettings
2 | from pydantic import Field
3 |
4 |
5 | class Config(BaseSettings):
6 | openid_connect_url: str = Field(..., env="AUTH_OPENID_CONNECT_URL")
7 | issuer: str = Field(..., env="AUTH_ISSUER")
8 | client_id: str = Field(..., env="AUTH_CLIENT_ID")
9 |
10 |
11 | config = Config()
12 |
--------------------------------------------------------------------------------
/example/app/main.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | import uvicorn
4 | from app.config import config
5 | from fastapi import Depends
6 | from fastapi import FastAPI
7 | from fastapi import Security
8 | from fastapi import status
9 | from fastapi.middleware.cors import CORSMiddleware
10 | from starlette.responses import RedirectResponse
11 |
12 | from fastapi_third_party_auth import Auth
13 | from fastapi_third_party_auth import KeycloakIDToken
14 |
15 | auth = Auth(
16 | openid_connect_url=config.openid_connect_url,
17 | issuer=config.issuer, # optional, verification only
18 | client_id=config.client_id, # optional, verification only
19 | scopes=["email"], # optional, verification only
20 | idtoken_model=KeycloakIDToken, # optional, verification only
21 | )
22 |
23 | app = FastAPI(
24 | title="Example",
25 | version="dev",
26 | dependencies=[Depends(auth)],
27 | )
28 |
29 | # CORS errors instead of seeing internal exceptions
30 | # https://stackoverflow.com/questions/63606055/why-do-i-get-cors-error-reason-cors-request-did-not-succeed
31 | cors = CORSMiddleware(
32 | app=app,
33 | allow_origins=["*"],
34 | allow_credentials=True,
35 | allow_methods=["*"],
36 | allow_headers=["*"],
37 | )
38 |
39 |
40 | @app.get("/", status_code=status.HTTP_303_SEE_OTHER)
41 | def redirect_to_docs():
42 | return RedirectResponse(url="/docs")
43 |
44 |
45 | @app.get("/protected")
46 | def protected(id_token: KeycloakIDToken = Security(auth.required)):
47 | print(id_token)
48 | return dict(message=f"You are {id_token.email}")
49 |
50 |
51 | @app.get("/mixed")
52 | def mixed(id_token: Optional[KeycloakIDToken] = Security(auth.optional)):
53 | if id_token is None:
54 | return dict(message="Welcome guest user!")
55 | else:
56 | return dict(message=f"Welcome {id_token.email}!")
57 |
58 |
59 | if __name__ == "__main__":
60 | uvicorn.run("app.main:cors", host="0.0.0.0", port=8000, loop="asyncio", reload=True)
61 |
--------------------------------------------------------------------------------
/example/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | test-fastapi-keycloak:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | depends_on:
9 | keycloak:
10 | condition: service_healthy
11 | network_mode: host
12 |
13 | keycloak:
14 | image: jboss/keycloak:15.0.2
15 | volumes:
16 | - ./my-realm-export.json:/tmp/my-realm-export.json
17 | environment:
18 | - DB_VENDOR=POSTGRES
19 | - DB_ADDR=keycloak-postgres
20 | - DB_DATABASE=keycloak
21 | - DB_USER=keycloak
22 | - DB_SCHEMA=public
23 | - DB_PASSWORD=password
24 | - KEYCLOAK_USER=admin
25 | - KEYCLOAK_PASSWORD=admin
26 | ports:
27 | - 8080:8080
28 | depends_on:
29 | - keycloak-postgres
30 | environment:
31 | - AUTH_OPENID_CONNECT_URL=http://localhost:8080/auth/realms/my-realm/.well-known/openid-configuration
32 | - AUTH_ISSUER=http://localhost:8080/auth/realms/my-realm
33 | - AUTH_CLIENT_ID=my-client
34 | healthcheck:
35 | test: "curl http://localhost:8080"
36 | interval: 30s
37 | timeout: 30s
38 | retries: 3
39 | start_period: 2m
40 |
41 | keycloak-postgres:
42 | image: postgres:13.4-alpine3.14
43 | volumes:
44 | - ./data/keycloak-postgres:/var/lib/postgresql/data/
45 | environment:
46 | - POSTGRES_USER=keycloak
47 | - POSTGRES_PASSWORD=password
48 | - POSTGRES_DB=keycloak
49 |
--------------------------------------------------------------------------------
/example/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "cachetools"
3 | version = "4.2.4"
4 | description = "Extensible memoizing collections and decorators"
5 | category = "main"
6 | optional = false
7 | python-versions = "~=3.5"
8 |
9 | [[package]]
10 | name = "certifi"
11 | version = "2021.10.8"
12 | description = "Python package for providing Mozilla's CA Bundle."
13 | category = "main"
14 | optional = false
15 | python-versions = "*"
16 |
17 | [[package]]
18 | name = "cffi"
19 | version = "1.15.0"
20 | description = "Foreign Function Interface for Python calling C code."
21 | category = "main"
22 | optional = false
23 | python-versions = "*"
24 |
25 | [package.dependencies]
26 | pycparser = "*"
27 |
28 | [[package]]
29 | name = "charset-normalizer"
30 | version = "2.0.9"
31 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
32 | category = "main"
33 | optional = false
34 | python-versions = ">=3.5.0"
35 |
36 | [package.extras]
37 | unicode_backport = ["unicodedata2"]
38 |
39 | [[package]]
40 | name = "click"
41 | version = "7.1.2"
42 | description = "Composable command line interface toolkit"
43 | category = "main"
44 | optional = false
45 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
46 |
47 | [[package]]
48 | name = "cryptography"
49 | version = "36.0.0"
50 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
51 | category = "main"
52 | optional = false
53 | python-versions = ">=3.6"
54 |
55 | [package.dependencies]
56 | cffi = ">=1.12"
57 |
58 | [package.extras]
59 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
60 | docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
61 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
62 | sdist = ["setuptools_rust (>=0.11.4)"]
63 | ssh = ["bcrypt (>=3.1.5)"]
64 | test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
65 |
66 | [[package]]
67 | name = "ecdsa"
68 | version = "0.17.0"
69 | description = "ECDSA cryptographic signature library (pure python)"
70 | category = "main"
71 | optional = false
72 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
73 |
74 | [package.dependencies]
75 | six = ">=1.9.0"
76 |
77 | [package.extras]
78 | gmpy = ["gmpy"]
79 | gmpy2 = ["gmpy2"]
80 |
81 | [[package]]
82 | name = "fastapi"
83 | version = "0.63.0"
84 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
85 | category = "main"
86 | optional = false
87 | python-versions = ">=3.6"
88 |
89 | [package.dependencies]
90 | pydantic = ">=1.0.0,<2.0.0"
91 | starlette = "0.13.6"
92 |
93 | [package.extras]
94 | all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"]
95 | dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"]
96 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"]
97 | test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.790)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"]
98 |
99 | [[package]]
100 | name = "fastapi-third-party-auth"
101 | version = "0.1.1"
102 | description = "Simple library for using a third party authentication service like Keycloak or Auth0 with FastAPI"
103 | category = "main"
104 | optional = false
105 | python-versions = ">=3.8,<4.0"
106 |
107 | [package.dependencies]
108 | cachetools = ">=4.1.1"
109 | fastapi = ">=0.61.0"
110 | pydantic = ">=1.6.1"
111 | python-jose = {version = ">=3.2.0", extras = ["cryptography"]}
112 | requests = ">=2.24.0"
113 |
114 | [[package]]
115 | name = "h11"
116 | version = "0.12.0"
117 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
118 | category = "main"
119 | optional = false
120 | python-versions = ">=3.6"
121 |
122 | [[package]]
123 | name = "idna"
124 | version = "3.3"
125 | description = "Internationalized Domain Names in Applications (IDNA)"
126 | category = "main"
127 | optional = false
128 | python-versions = ">=3.5"
129 |
130 | [[package]]
131 | name = "pyasn1"
132 | version = "0.4.8"
133 | description = "ASN.1 types and codecs"
134 | category = "main"
135 | optional = false
136 | python-versions = "*"
137 |
138 | [[package]]
139 | name = "pycparser"
140 | version = "2.21"
141 | description = "C parser in Python"
142 | category = "main"
143 | optional = false
144 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
145 |
146 | [[package]]
147 | name = "pydantic"
148 | version = "1.8.2"
149 | description = "Data validation and settings management using python 3.6 type hinting"
150 | category = "main"
151 | optional = false
152 | python-versions = ">=3.6.1"
153 |
154 | [package.dependencies]
155 | typing-extensions = ">=3.7.4.3"
156 |
157 | [package.extras]
158 | dotenv = ["python-dotenv (>=0.10.4)"]
159 | email = ["email-validator (>=1.0.3)"]
160 |
161 | [[package]]
162 | name = "python-jose"
163 | version = "3.3.0"
164 | description = "JOSE implementation in Python"
165 | category = "main"
166 | optional = false
167 | python-versions = "*"
168 |
169 | [package.dependencies]
170 | cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryptography\""}
171 | ecdsa = "!=0.15"
172 | pyasn1 = "*"
173 | rsa = "*"
174 |
175 | [package.extras]
176 | cryptography = ["cryptography (>=3.4.0)"]
177 | pycrypto = ["pycrypto (>=2.6.0,<2.7.0)", "pyasn1"]
178 | pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"]
179 |
180 | [[package]]
181 | name = "requests"
182 | version = "2.26.0"
183 | description = "Python HTTP for Humans."
184 | category = "main"
185 | optional = false
186 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
187 |
188 | [package.dependencies]
189 | certifi = ">=2017.4.17"
190 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
191 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
192 | urllib3 = ">=1.21.1,<1.27"
193 |
194 | [package.extras]
195 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
196 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
197 |
198 | [[package]]
199 | name = "rsa"
200 | version = "4.8"
201 | description = "Pure-Python RSA implementation"
202 | category = "main"
203 | optional = false
204 | python-versions = ">=3.6,<4"
205 |
206 | [package.dependencies]
207 | pyasn1 = ">=0.1.3"
208 |
209 | [[package]]
210 | name = "six"
211 | version = "1.16.0"
212 | description = "Python 2 and 3 compatibility utilities"
213 | category = "main"
214 | optional = false
215 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
216 |
217 | [[package]]
218 | name = "starlette"
219 | version = "0.13.6"
220 | description = "The little ASGI library that shines."
221 | category = "main"
222 | optional = false
223 | python-versions = ">=3.6"
224 |
225 | [package.extras]
226 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
227 |
228 | [[package]]
229 | name = "typing-extensions"
230 | version = "4.0.1"
231 | description = "Backported and Experimental Type Hints for Python 3.6+"
232 | category = "main"
233 | optional = false
234 | python-versions = ">=3.6"
235 |
236 | [[package]]
237 | name = "urllib3"
238 | version = "1.26.7"
239 | description = "HTTP library with thread-safe connection pooling, file post, and more."
240 | category = "main"
241 | optional = false
242 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
243 |
244 | [package.extras]
245 | brotli = ["brotlipy (>=0.6.0)"]
246 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
247 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
248 |
249 | [[package]]
250 | name = "uvicorn"
251 | version = "0.13.4"
252 | description = "The lightning-fast ASGI server."
253 | category = "main"
254 | optional = false
255 | python-versions = "*"
256 |
257 | [package.dependencies]
258 | click = ">=7.0.0,<8.0.0"
259 | h11 = ">=0.8"
260 |
261 | [package.extras]
262 | standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
263 |
264 | [metadata]
265 | lock-version = "1.1"
266 | python-versions = "^3.8"
267 | content-hash = "16e8e1eeccb4e7c62035e7571890d53c61a5e0cbe686a2e35621c97811b81f32"
268 |
269 | [metadata.files]
270 | cachetools = [
271 | {file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"},
272 | {file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"},
273 | ]
274 | certifi = [
275 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
276 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
277 | ]
278 | cffi = [
279 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
280 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
281 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
282 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
283 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
284 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
285 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
286 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
287 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
288 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
289 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
290 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
291 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
292 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
293 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
294 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
295 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
296 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
297 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
298 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
299 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
300 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
301 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
302 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
303 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
304 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
305 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
306 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
307 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
308 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
309 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
310 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
311 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
312 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
313 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
314 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
315 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
316 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
317 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
318 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
319 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
320 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
321 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
322 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
323 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
324 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
325 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
326 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
327 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
328 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
329 | ]
330 | charset-normalizer = [
331 | {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"},
332 | {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"},
333 | ]
334 | click = [
335 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
336 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
337 | ]
338 | cryptography = [
339 | {file = "cryptography-36.0.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6"},
340 | {file = "cryptography-36.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d"},
341 | {file = "cryptography-36.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e"},
342 | {file = "cryptography-36.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58"},
343 | {file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e"},
344 | {file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f"},
345 | {file = "cryptography-36.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d"},
346 | {file = "cryptography-36.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120"},
347 | {file = "cryptography-36.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44"},
348 | {file = "cryptography-36.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4"},
349 | {file = "cryptography-36.0.0-cp36-abi3-win32.whl", hash = "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81"},
350 | {file = "cryptography-36.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568"},
351 | {file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681"},
352 | {file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636"},
353 | {file = "cryptography-36.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba"},
354 | {file = "cryptography-36.0.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712"},
355 | {file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b"},
356 | {file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed"},
357 | {file = "cryptography-36.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"},
358 | {file = "cryptography-36.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3"},
359 | {file = "cryptography-36.0.0.tar.gz", hash = "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f"},
360 | ]
361 | ecdsa = [
362 | {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
363 | {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"},
364 | ]
365 | fastapi = [
366 | {file = "fastapi-0.63.0-py3-none-any.whl", hash = "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e"},
367 | {file = "fastapi-0.63.0.tar.gz", hash = "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb"},
368 | ]
369 | fastapi-third-party-auth = [
370 | {file = "fastapi-third-party-auth-0.1.1.tar.gz", hash = "sha256:1eb3c2e67b55451f8bd6aa6ee798ee72c48571d67e58ec5faacc8401c3c9700c"},
371 | {file = "fastapi_third_party_auth-0.1.1-py3-none-any.whl", hash = "sha256:27b5141a2c15bec525297e75c8d2b63d4abf4d59e374aa4af429f0e058809c8f"},
372 | ]
373 | h11 = [
374 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
375 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
376 | ]
377 | idna = [
378 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
379 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
380 | ]
381 | pyasn1 = [
382 | {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
383 | {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
384 | {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
385 | {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
386 | {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
387 | {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
388 | {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
389 | {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
390 | {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
391 | {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
392 | {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
393 | {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
394 | {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
395 | ]
396 | pycparser = [
397 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
398 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
399 | ]
400 | pydantic = [
401 | {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
402 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
403 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
404 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
405 | {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
406 | {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
407 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
408 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
409 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
410 | {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
411 | {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
412 | {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
413 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
414 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
415 | {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
416 | {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
417 | {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
418 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
419 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
420 | {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
421 | {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
422 | {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
423 | ]
424 | python-jose = [
425 | {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
426 | {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
427 | ]
428 | requests = [
429 | {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
430 | {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
431 | ]
432 | rsa = [
433 | {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"},
434 | {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"},
435 | ]
436 | six = [
437 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
438 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
439 | ]
440 | starlette = [
441 | {file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"},
442 | {file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"},
443 | ]
444 | typing-extensions = [
445 | {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
446 | {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
447 | ]
448 | urllib3 = [
449 | {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
450 | {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
451 | ]
452 | uvicorn = [
453 | {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"},
454 | {file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"},
455 | ]
456 |
--------------------------------------------------------------------------------
/example/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "keycloak-example"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Richard Löwenström "]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | fastapi = "^0.63.0"
10 | uvicorn = "^0.13.4"
11 | fastapi-third-party-auth = "^0.1.1"
12 |
13 | [tool.poetry.dev-dependencies]
14 |
15 | [build-system]
16 | requires = ["poetry-core>=1.0.0"]
17 | build-backend = "poetry.core.masonry.api"
18 |
--------------------------------------------------------------------------------
/fastapi_third_party_auth/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi_third_party_auth.auth import Auth # noqa
2 | from fastapi_third_party_auth.grant_types import GrantType # noqa
3 | from fastapi_third_party_auth.idtoken_types import IDToken # noqa
4 | from fastapi_third_party_auth.idtoken_types import KeycloakIDToken # noqa
5 | from fastapi_third_party_auth.idtoken_types import OktaIDToken # noqa
6 |
--------------------------------------------------------------------------------
/fastapi_third_party_auth/auth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Module for validating Open ID Connect tokens.
4 |
5 | Usage
6 | =====
7 |
8 | .. code-block:: python3
9 |
10 | # This assumes you've already configured Auth in your_app/auth.py
11 | from your_app.auth import auth
12 |
13 | @app.get("/auth")
14 | def test_auth(authenticated_user: IDToken = Security(auth.required)):
15 | return f"Hello {authenticated_user.preferred_username}"
16 | """
17 |
18 | from typing import List
19 | from typing import Optional
20 | from typing import Type
21 |
22 | from fastapi import Depends
23 | from fastapi import HTTPException
24 | from fastapi import Request
25 | from fastapi import status
26 | from fastapi.openapi.models import OAuthFlowAuthorizationCode
27 | from fastapi.openapi.models import OAuthFlowClientCredentials
28 | from fastapi.openapi.models import OAuthFlowImplicit
29 | from fastapi.openapi.models import OAuthFlowPassword
30 | from fastapi.openapi.models import OAuthFlows
31 | from fastapi.security import HTTPAuthorizationCredentials
32 | from fastapi.security import HTTPBearer
33 | from fastapi.security import OAuth2
34 | from fastapi.security import SecurityScopes
35 | from jose import ExpiredSignatureError
36 | from jose import JWTError
37 | from jose import jwt
38 | from jose.exceptions import JWTClaimsError
39 |
40 | from fastapi_third_party_auth import discovery
41 | from fastapi_third_party_auth.grant_types import GrantType
42 | from fastapi_third_party_auth.idtoken_types import IDToken
43 |
44 |
45 | class Auth(OAuth2):
46 | def __init__(
47 | self,
48 | openid_connect_url: str,
49 | issuer: Optional[str] = None,
50 | client_id: Optional[str] = None,
51 | scopes: List[str] = list(),
52 | grant_types: List[GrantType] = [GrantType.IMPLICIT],
53 | signature_cache_ttl: int = 3600,
54 | idtoken_model: Type[IDToken] = IDToken,
55 | ):
56 | """Configure authentication :func:`auth = Auth(...) ` and then:
57 |
58 | 1. Show authentication in the interactive docs with :func:`Depends(auth) `
59 | when setting up FastAPI.
60 | 2. Use :func:`Security(auth.required) ` or
61 | :func:`Security(auth.optional) ` in your endpoints to
62 | check user credentials.
63 |
64 | Args:
65 | openid_connect_url (URL): URL to the "well known" openid connect config
66 | e.g. https://dev-123456.okta.com/.well-known/openid-configuration
67 | issuer (URL): (Optional) The issuer URL from your auth server.
68 | client_id (str): (Optional) The client_id configured by your auth server.
69 | scopes (Dict[str, str]): (Optional) A dictionary of scopes and their descriptions.
70 | grant_types (List[GrantType]): (Optional) Grant types shown in docs.
71 | signature_cache_ttl (int): (Optional) How many seconds your app should
72 | cache the authorization server's public signatures.
73 | idtoken_model (Type): (Optional) The model to use for validating the ID Token.
74 |
75 | Raises:
76 | Nothing intentional
77 | """
78 |
79 | self.openid_connect_url = openid_connect_url
80 | self.issuer = issuer
81 | self.client_id = client_id
82 | self.idtoken_model = idtoken_model
83 | self.scopes = scopes
84 |
85 | self.discover = discovery.configure(cache_ttl=signature_cache_ttl)
86 | oidc_discoveries = self.discover.auth_server(
87 | openid_connect_url=self.openid_connect_url
88 | )
89 | # scopes_dict = {
90 | # scope: "" for scope in self.discover.supported_scopes(oidc_discoveries)
91 | # }
92 |
93 | flows = OAuthFlows()
94 | if GrantType.AUTHORIZATION_CODE in grant_types:
95 | flows.authorizationCode = OAuthFlowAuthorizationCode(
96 | authorizationUrl=self.discover.authorization_url(oidc_discoveries),
97 | tokenUrl=self.discover.token_url(oidc_discoveries),
98 | # scopes=scopes_dict,
99 | )
100 |
101 | if GrantType.CLIENT_CREDENTIALS in grant_types:
102 | flows.clientCredentials = OAuthFlowClientCredentials(
103 | tokenUrl=self.discover.token_url(oidc_discoveries),
104 | # scopes=scopes_dict,
105 | )
106 |
107 | if GrantType.PASSWORD in grant_types:
108 | flows.password = OAuthFlowPassword(
109 | tokenUrl=self.discover.token_url(oidc_discoveries),
110 | # scopes=scopes_dict,
111 | )
112 |
113 | if GrantType.IMPLICIT in grant_types:
114 | flows.implicit = OAuthFlowImplicit(
115 | authorizationUrl=self.discover.authorization_url(oidc_discoveries),
116 | # scopes=scopes_dict,
117 | )
118 |
119 | super().__init__(
120 | scheme_name="OIDC",
121 | flows=flows,
122 | auto_error=False,
123 | )
124 |
125 | async def __call__(self, request: Request) -> None:
126 | return None
127 |
128 | def required(
129 | self,
130 | security_scopes: SecurityScopes,
131 | authorization_credentials: Optional[HTTPAuthorizationCredentials] = Depends(
132 | HTTPBearer()
133 | ),
134 | ) -> IDToken:
135 | """Validate and parse OIDC ID token against configuration.
136 | Note this function caches the signatures and algorithms of the issuing
137 | server for signature_cache_ttl seconds.
138 |
139 | Args:
140 | security_scopes (SecurityScopes): Security scopes
141 | auth_header (str): Base64 encoded OIDC Token. This is invoked
142 | behind the scenes by Depends.
143 |
144 | Return:
145 | IDToken (self.idtoken_model): User information
146 |
147 | raises:
148 | HTTPException(status_code=401, detail=f"Unauthorized: {err}")
149 | IDToken validation errors
150 | """
151 |
152 | id_token = self.authenticate_user(
153 | security_scopes,
154 | authorization_credentials,
155 | auto_error=True,
156 | )
157 | if id_token is None:
158 | raise HTTPException(status.HTTP_401_UNAUTHORIZED)
159 | else:
160 | return id_token
161 |
162 | def optional(
163 | self,
164 | security_scopes: SecurityScopes,
165 | authorization_credentials: Optional[HTTPAuthorizationCredentials] = Depends(
166 | HTTPBearer(auto_error=False)
167 | ),
168 | ) -> Optional[IDToken]:
169 | """Optionally validate and parse OIDC ID token against configuration.
170 | Will not raise if the user is not authenticated. Note this function
171 | caches the signatures and algorithms of the issuing server for
172 | signature_cache_ttl seconds.
173 |
174 | Args:
175 | security_scopes (SecurityScopes): Security scopes
176 | auth_header (str): Base64 encoded OIDC Token. This is invoked
177 | behind the scenes by Depends.
178 |
179 | Return:
180 | IDToken (self.idtoken_model): User information
181 |
182 | raises:
183 | IDToken validation errors
184 | """
185 |
186 | return self.authenticate_user(
187 | security_scopes,
188 | authorization_credentials,
189 | auto_error=False,
190 | )
191 |
192 | def authenticate_user(
193 | self,
194 | security_scopes: SecurityScopes,
195 | authorization_credentials: Optional[HTTPAuthorizationCredentials],
196 | auto_error: bool,
197 | ) -> Optional[IDToken]:
198 | """Validate and parse OIDC ID token against against configuration.
199 | Note this function caches the signatures and algorithms of the issuing server
200 | for signature_cache_ttl seconds.
201 |
202 | Args:
203 | security_scopes (SecurityScopes): Security scopes
204 | auth_header (str): Base64 encoded OIDC Token
205 | auto_error (bool): If True, will raise an HTTPException if the user
206 | is not authenticated.
207 |
208 | Return:
209 | IDToken (self.idtoken_model): User information
210 |
211 | raises:
212 | HTTPException(status_code=401, detail=f"Unauthorized: {err}")
213 | """
214 |
215 | if (
216 | authorization_credentials is None
217 | or authorization_credentials.scheme.lower() != "bearer"
218 | ):
219 | if auto_error:
220 | raise HTTPException(
221 | status.HTTP_401_UNAUTHORIZED, detail="Missing bearer token"
222 | )
223 | else:
224 | return None
225 |
226 | oidc_discoveries = self.discover.auth_server(
227 | openid_connect_url=self.openid_connect_url
228 | )
229 | key = self.discover.public_keys(oidc_discoveries)
230 | algorithms = self.discover.signing_algos(oidc_discoveries)
231 |
232 | try:
233 | id_token = jwt.decode(
234 | authorization_credentials.credentials,
235 | key,
236 | algorithms,
237 | issuer=self.issuer,
238 | audience=self.client_id,
239 | options={
240 | # Disabled at_hash check since we aren't using the access token
241 | "verify_at_hash": False,
242 | "verify_iss": self.issuer is not None,
243 | "verify_aud": self.client_id is not None,
244 | },
245 | )
246 |
247 | if (
248 | type(id_token["aud"]) == list
249 | and len(id_token["aud"]) >= 1
250 | and "azp" not in id_token
251 | ):
252 | raise JWTError(
253 | 'Missing authorized party "azp" in IDToken when there '
254 | "are multiple audiences"
255 | )
256 |
257 | except (ExpiredSignatureError, JWTError, JWTClaimsError) as error:
258 | raise HTTPException(status_code=401, detail=f"Unauthorized: {error}")
259 |
260 | expected_scopes = set(self.scopes + security_scopes.scopes)
261 | token_scopes = id_token.get("scope", "").split(" ")
262 | if not expected_scopes.issubset(token_scopes):
263 | raise HTTPException(
264 | status.HTTP_401_UNAUTHORIZED,
265 | detail=(
266 | f"Missing scope token, expected {expected_scopes} to be a "
267 | f"subset of received {token_scopes}",
268 | ),
269 | )
270 |
271 | return self.idtoken_model(**id_token)
272 |
--------------------------------------------------------------------------------
/fastapi_third_party_auth/discovery.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 | import requests
3 | from cachetools import TTLCache
4 | from cachetools import cached
5 | from threading import Lock
6 |
7 |
8 | def configure(*_, cache_ttl: int):
9 | @cached(TTLCache(1, cache_ttl), key=lambda d: d["jwks_uri"], lock=Lock())
10 | def get_authentication_server_public_keys(OIDC_spec: Dict):
11 | """
12 | Retrieve the public keys used by the authentication server
13 | for signing OIDC ID tokens.
14 | """
15 | keys_uri = OIDC_spec["jwks_uri"]
16 | r = requests.get(keys_uri)
17 | keys = r.json()
18 | return keys
19 |
20 | def get_signing_algos(OIDC_spec: Dict):
21 | algos = OIDC_spec["id_token_signing_alg_values_supported"]
22 | return algos
23 |
24 | @cached(TTLCache(1, cache_ttl), lock=Lock())
25 | def discover_auth_server(*_, openid_connect_url: str) -> Dict:
26 | r = requests.get(openid_connect_url)
27 | # Raise if the auth server is failing since we can't verify tokens
28 | r.raise_for_status()
29 | configuration = r.json()
30 | return configuration
31 |
32 | def get_authorization_url(OIDC_spec: Dict) -> str:
33 | return OIDC_spec["authorization_endpoint"]
34 |
35 | def get_token_url(OIDC_spec: Dict) -> str:
36 | return OIDC_spec["token_endpoint"]
37 |
38 | def get_supported_scopes(OIDC_spec: Dict) -> str:
39 | return OIDC_spec["scopes_supported"]
40 |
41 | class functions:
42 | auth_server = discover_auth_server
43 | public_keys = get_authentication_server_public_keys
44 | signing_algos = get_signing_algos
45 | authorization_url = get_authorization_url
46 | token_url = get_token_url
47 | supported_scopes = get_supported_scopes
48 |
49 | return functions
50 |
--------------------------------------------------------------------------------
/fastapi_third_party_auth/grant_types.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class GrantType(str, Enum):
5 | """Grant types that can be used in the interactive documentation."""
6 |
7 | AUTHORIZATION_CODE = "authorization_code"
8 | CLIENT_CREDENTIALS = "client_credentials"
9 | IMPLICIT = "implicit"
10 | PASSWORD = "password" # nosec
11 |
--------------------------------------------------------------------------------
/fastapi_third_party_auth/idtoken_types.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from typing import Union
3 |
4 | from pydantic import BaseModel
5 | from pydantic import Extra
6 |
7 |
8 | class IDToken(BaseModel):
9 | """Pydantic model representing an OIDC ID Token.
10 |
11 | ID Tokens are polymorphic and may have many attributes not defined in the spec thus this model accepts
12 | all addition fields. Only required fields are listed in the attributes section of this docstring or
13 | enforced by pydantic.
14 |
15 | See the specifications here. https://openid.net/specs/openid-connect-core-1_0.html#IDToken
16 |
17 | Parameters:
18 | iss (str): Issuer Identifier for the Issuer of the response.
19 | sub (str): Subject Identifier.
20 | aud (Union[str, List[str]]): Audience(s) that this ID Token is intended for.
21 | exp (str): Expiration time on or after which the ID Token MUST NOT be accepted for processing.
22 | iat (iat): Time at which the JWT was issued.
23 |
24 | """
25 |
26 | iss: str
27 | sub: str
28 | aud: Union[str, List[str]]
29 | exp: int
30 | iat: int
31 |
32 | class Config:
33 | extra = Extra.allow
34 |
35 |
36 | class OktaIDToken(IDToken):
37 | """Pydantic Model for the IDToken returned by Okta's OIDC implementation."""
38 |
39 | auth_time: int
40 | ver: int
41 | jti: str
42 | amr: List[str]
43 | idp: str
44 | nonce: str
45 | at_hash: str
46 | name: str
47 | email: str
48 | preferred_username: str
49 |
50 |
51 | class KeycloakIDToken(IDToken):
52 | """Pydantic Model for the IDToken returned by Keycloak's OIDC implementation."""
53 |
54 | jti: str
55 | name: str
56 | email: str
57 | email_verified: bool
58 | preferred_username: str
59 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "alabaster"
3 | version = "0.7.12"
4 | description = "A configurable sidebar-enabled Sphinx theme"
5 | category = "dev"
6 | optional = false
7 | python-versions = "*"
8 |
9 | [[package]]
10 | name = "anyio"
11 | version = "3.6.1"
12 | description = "High level compatibility layer for multiple asynchronous event loop implementations"
13 | category = "main"
14 | optional = false
15 | python-versions = ">=3.6.2"
16 |
17 | [package.dependencies]
18 | idna = ">=2.8"
19 | sniffio = ">=1.1"
20 |
21 | [package.extras]
22 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
23 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
24 | trio = ["trio (>=0.16)"]
25 |
26 | [[package]]
27 | name = "appdirs"
28 | version = "1.4.4"
29 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
30 | category = "dev"
31 | optional = false
32 | python-versions = "*"
33 |
34 | [[package]]
35 | name = "asgiref"
36 | version = "3.5.2"
37 | description = "ASGI specs, helper code, and adapters"
38 | category = "dev"
39 | optional = false
40 | python-versions = ">=3.7"
41 |
42 | [package.extras]
43 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
44 |
45 | [[package]]
46 | name = "astroid"
47 | version = "2.12.10"
48 | description = "An abstract syntax tree for Python with inference support."
49 | category = "dev"
50 | optional = false
51 | python-versions = ">=3.7.2"
52 |
53 | [package.dependencies]
54 | lazy-object-proxy = ">=1.4.0"
55 | typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
56 | wrapt = [
57 | {version = ">=1.11,<2", markers = "python_version < \"3.11\""},
58 | {version = ">=1.14,<2", markers = "python_version >= \"3.11\""},
59 | ]
60 |
61 | [[package]]
62 | name = "atomicwrites"
63 | version = "1.4.1"
64 | description = "Atomic file writes."
65 | category = "dev"
66 | optional = false
67 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
68 |
69 | [[package]]
70 | name = "attrs"
71 | version = "22.1.0"
72 | description = "Classes Without Boilerplate"
73 | category = "dev"
74 | optional = false
75 | python-versions = ">=3.5"
76 |
77 | [package.extras]
78 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
79 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
80 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
81 | tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
82 |
83 | [[package]]
84 | name = "Babel"
85 | version = "2.10.3"
86 | description = "Internationalization utilities"
87 | category = "dev"
88 | optional = false
89 | python-versions = ">=3.6"
90 |
91 | [package.dependencies]
92 | pytz = ">=2015.7"
93 |
94 | [[package]]
95 | name = "black"
96 | version = "21.7b0"
97 | description = "The uncompromising code formatter."
98 | category = "dev"
99 | optional = false
100 | python-versions = ">=3.6.2"
101 |
102 | [package.dependencies]
103 | appdirs = "*"
104 | click = ">=7.1.2"
105 | mypy-extensions = ">=0.4.3"
106 | pathspec = ">=0.8.1,<1"
107 | regex = ">=2020.1.8"
108 | tomli = ">=0.2.6,<2.0.0"
109 |
110 | [package.extras]
111 | colorama = ["colorama (>=0.4.3)"]
112 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
113 | python2 = ["typed-ast (>=1.4.2)"]
114 | uvloop = ["uvloop (>=0.15.2)"]
115 |
116 | [[package]]
117 | name = "cachetools"
118 | version = "5.2.0"
119 | description = "Extensible memoizing collections and decorators"
120 | category = "main"
121 | optional = false
122 | python-versions = "~=3.7"
123 |
124 | [[package]]
125 | name = "certifi"
126 | version = "2022.9.24"
127 | description = "Python package for providing Mozilla's CA Bundle."
128 | category = "main"
129 | optional = false
130 | python-versions = ">=3.6"
131 |
132 | [[package]]
133 | name = "cffi"
134 | version = "1.15.1"
135 | description = "Foreign Function Interface for Python calling C code."
136 | category = "main"
137 | optional = false
138 | python-versions = "*"
139 |
140 | [package.dependencies]
141 | pycparser = "*"
142 |
143 | [[package]]
144 | name = "charset-normalizer"
145 | version = "2.1.1"
146 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
147 | category = "main"
148 | optional = false
149 | python-versions = ">=3.6.0"
150 |
151 | [package.extras]
152 | unicode_backport = ["unicodedata2"]
153 |
154 | [[package]]
155 | name = "click"
156 | version = "8.1.3"
157 | description = "Composable command line interface toolkit"
158 | category = "dev"
159 | optional = false
160 | python-versions = ">=3.7"
161 |
162 | [package.dependencies]
163 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
164 |
165 | [[package]]
166 | name = "colorama"
167 | version = "0.4.5"
168 | description = "Cross-platform colored terminal text."
169 | category = "dev"
170 | optional = false
171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
172 |
173 | [[package]]
174 | name = "cryptography"
175 | version = "38.0.1"
176 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
177 | category = "main"
178 | optional = false
179 | python-versions = ">=3.6"
180 |
181 | [package.dependencies]
182 | cffi = ">=1.12"
183 |
184 | [package.extras]
185 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
186 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
187 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
188 | sdist = ["setuptools-rust (>=0.11.4)"]
189 | ssh = ["bcrypt (>=3.1.5)"]
190 | test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
191 |
192 | [[package]]
193 | name = "dill"
194 | version = "0.3.5.1"
195 | description = "serialize all of python"
196 | category = "dev"
197 | optional = false
198 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
199 |
200 | [package.extras]
201 | graph = ["objgraph (>=1.7.2)"]
202 |
203 | [[package]]
204 | name = "docutils"
205 | version = "0.16"
206 | description = "Docutils -- Python Documentation Utilities"
207 | category = "dev"
208 | optional = false
209 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
210 |
211 | [[package]]
212 | name = "ecdsa"
213 | version = "0.18.0"
214 | description = "ECDSA cryptographic signature library (pure python)"
215 | category = "main"
216 | optional = false
217 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
218 |
219 | [package.dependencies]
220 | six = ">=1.9.0"
221 |
222 | [package.extras]
223 | gmpy = ["gmpy"]
224 | gmpy2 = ["gmpy2"]
225 |
226 | [[package]]
227 | name = "fastapi"
228 | version = "0.85.0"
229 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
230 | category = "main"
231 | optional = false
232 | python-versions = ">=3.7"
233 |
234 | [package.dependencies]
235 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
236 | starlette = "0.20.4"
237 |
238 | [package.extras]
239 | all = ["email-validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"]
240 | dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"]
241 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.7.0)"]
242 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-orjson (==3.6.2)", "types-ujson (==5.4.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
243 |
244 | [[package]]
245 | name = "h11"
246 | version = "0.14.0"
247 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
248 | category = "dev"
249 | optional = false
250 | python-versions = ">=3.7"
251 |
252 | [[package]]
253 | name = "idna"
254 | version = "3.4"
255 | description = "Internationalized Domain Names in Applications (IDNA)"
256 | category = "main"
257 | optional = false
258 | python-versions = ">=3.5"
259 |
260 | [[package]]
261 | name = "imagesize"
262 | version = "1.4.1"
263 | description = "Getting image size from png/jpeg/jpeg2000/gif file"
264 | category = "dev"
265 | optional = false
266 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
267 |
268 | [[package]]
269 | name = "iniconfig"
270 | version = "1.1.1"
271 | description = "iniconfig: brain-dead simple config-ini parsing"
272 | category = "dev"
273 | optional = false
274 | python-versions = "*"
275 |
276 | [[package]]
277 | name = "isort"
278 | version = "5.10.1"
279 | description = "A Python utility / library to sort Python imports."
280 | category = "dev"
281 | optional = false
282 | python-versions = ">=3.6.1,<4.0"
283 |
284 | [package.extras]
285 | colors = ["colorama (>=0.4.3,<0.5.0)"]
286 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
287 | plugins = ["setuptools"]
288 | requirements_deprecated_finder = ["pip-api", "pipreqs"]
289 |
290 | [[package]]
291 | name = "Jinja2"
292 | version = "3.1.2"
293 | description = "A very fast and expressive template engine."
294 | category = "dev"
295 | optional = false
296 | python-versions = ">=3.7"
297 |
298 | [package.dependencies]
299 | MarkupSafe = ">=2.0"
300 |
301 | [package.extras]
302 | i18n = ["Babel (>=2.7)"]
303 |
304 | [[package]]
305 | name = "lazy-object-proxy"
306 | version = "1.7.1"
307 | description = "A fast and thorough lazy object proxy."
308 | category = "dev"
309 | optional = false
310 | python-versions = ">=3.6"
311 |
312 | [[package]]
313 | name = "MarkupSafe"
314 | version = "2.1.1"
315 | description = "Safely add untrusted strings to HTML/XML markup."
316 | category = "dev"
317 | optional = false
318 | python-versions = ">=3.7"
319 |
320 | [[package]]
321 | name = "mccabe"
322 | version = "0.7.0"
323 | description = "McCabe checker, plugin for flake8"
324 | category = "dev"
325 | optional = false
326 | python-versions = ">=3.6"
327 |
328 | [[package]]
329 | name = "mypy"
330 | version = "0.910"
331 | description = "Optional static typing for Python"
332 | category = "dev"
333 | optional = false
334 | python-versions = ">=3.5"
335 |
336 | [package.dependencies]
337 | mypy-extensions = ">=0.4.3,<0.5.0"
338 | toml = "*"
339 | typing-extensions = ">=3.7.4"
340 |
341 | [package.extras]
342 | dmypy = ["psutil (>=4.0)"]
343 | python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
344 |
345 | [[package]]
346 | name = "mypy-extensions"
347 | version = "0.4.3"
348 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
349 | category = "dev"
350 | optional = false
351 | python-versions = "*"
352 |
353 | [[package]]
354 | name = "packaging"
355 | version = "21.3"
356 | description = "Core utilities for Python packages"
357 | category = "dev"
358 | optional = false
359 | python-versions = ">=3.6"
360 |
361 | [package.dependencies]
362 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
363 |
364 | [[package]]
365 | name = "pathspec"
366 | version = "0.10.1"
367 | description = "Utility library for gitignore style pattern matching of file paths."
368 | category = "dev"
369 | optional = false
370 | python-versions = ">=3.7"
371 |
372 | [[package]]
373 | name = "platformdirs"
374 | version = "2.5.2"
375 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
376 | category = "dev"
377 | optional = false
378 | python-versions = ">=3.7"
379 |
380 | [package.extras]
381 | docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
382 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
383 |
384 | [[package]]
385 | name = "pluggy"
386 | version = "1.0.0"
387 | description = "plugin and hook calling mechanisms for python"
388 | category = "dev"
389 | optional = false
390 | python-versions = ">=3.6"
391 |
392 | [package.extras]
393 | dev = ["pre-commit", "tox"]
394 | testing = ["pytest", "pytest-benchmark"]
395 |
396 | [[package]]
397 | name = "py"
398 | version = "1.11.0"
399 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
400 | category = "dev"
401 | optional = false
402 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
403 |
404 | [[package]]
405 | name = "pyasn1"
406 | version = "0.4.8"
407 | description = "ASN.1 types and codecs"
408 | category = "main"
409 | optional = false
410 | python-versions = "*"
411 |
412 | [[package]]
413 | name = "pycparser"
414 | version = "2.21"
415 | description = "C parser in Python"
416 | category = "main"
417 | optional = false
418 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
419 |
420 | [[package]]
421 | name = "pydantic"
422 | version = "1.10.2"
423 | description = "Data validation and settings management using python type hints"
424 | category = "main"
425 | optional = false
426 | python-versions = ">=3.7"
427 |
428 | [package.dependencies]
429 | typing-extensions = ">=4.1.0"
430 |
431 | [package.extras]
432 | dotenv = ["python-dotenv (>=0.10.4)"]
433 | email = ["email-validator (>=1.0.3)"]
434 |
435 | [[package]]
436 | name = "Pygments"
437 | version = "2.13.0"
438 | description = "Pygments is a syntax highlighting package written in Python."
439 | category = "dev"
440 | optional = false
441 | python-versions = ">=3.6"
442 |
443 | [package.extras]
444 | plugins = ["importlib-metadata"]
445 |
446 | [[package]]
447 | name = "PyJWT"
448 | version = "1.7.1"
449 | description = "JSON Web Token implementation in Python"
450 | category = "dev"
451 | optional = false
452 | python-versions = "*"
453 |
454 | [package.extras]
455 | crypto = ["cryptography (>=1.4)"]
456 | flake8 = ["flake8", "flake8-import-order", "pep8-naming"]
457 | test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"]
458 |
459 | [[package]]
460 | name = "pylint"
461 | version = "2.15.3"
462 | description = "python code static checker"
463 | category = "dev"
464 | optional = false
465 | python-versions = ">=3.7.2"
466 |
467 | [package.dependencies]
468 | astroid = ">=2.12.10,<=2.14.0-dev0"
469 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
470 | dill = ">=0.2"
471 | isort = ">=4.2.5,<6"
472 | mccabe = ">=0.6,<0.8"
473 | platformdirs = ">=2.2.0"
474 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
475 | tomlkit = ">=0.10.1"
476 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
477 |
478 | [package.extras]
479 | spelling = ["pyenchant (>=3.2,<4.0)"]
480 | testutils = ["gitpython (>3)"]
481 |
482 | [[package]]
483 | name = "pyparsing"
484 | version = "3.0.9"
485 | description = "pyparsing module - Classes and methods to define and execute parsing grammars"
486 | category = "dev"
487 | optional = false
488 | python-versions = ">=3.6.8"
489 |
490 | [package.extras]
491 | diagrams = ["jinja2", "railroad-diagrams"]
492 |
493 | [[package]]
494 | name = "pytest"
495 | version = "6.2.5"
496 | description = "pytest: simple powerful testing with Python"
497 | category = "dev"
498 | optional = false
499 | python-versions = ">=3.6"
500 |
501 | [package.dependencies]
502 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
503 | attrs = ">=19.2.0"
504 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
505 | iniconfig = "*"
506 | packaging = "*"
507 | pluggy = ">=0.12,<2.0"
508 | py = ">=1.8.2"
509 | toml = "*"
510 |
511 | [package.extras]
512 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
513 |
514 | [[package]]
515 | name = "python-jose"
516 | version = "3.3.0"
517 | description = "JOSE implementation in Python"
518 | category = "main"
519 | optional = false
520 | python-versions = "*"
521 |
522 | [package.dependencies]
523 | cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryptography\""}
524 | ecdsa = "!=0.15"
525 | pyasn1 = "*"
526 | rsa = "*"
527 |
528 | [package.extras]
529 | cryptography = ["cryptography (>=3.4.0)"]
530 | pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"]
531 | pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"]
532 |
533 | [[package]]
534 | name = "pytz"
535 | version = "2022.4"
536 | description = "World timezone definitions, modern and historical"
537 | category = "dev"
538 | optional = false
539 | python-versions = "*"
540 |
541 | [[package]]
542 | name = "regex"
543 | version = "2022.9.13"
544 | description = "Alternative regular expression module, to replace re."
545 | category = "dev"
546 | optional = false
547 | python-versions = ">=3.6"
548 |
549 | [[package]]
550 | name = "requests"
551 | version = "2.28.1"
552 | description = "Python HTTP for Humans."
553 | category = "main"
554 | optional = false
555 | python-versions = ">=3.7, <4"
556 |
557 | [package.dependencies]
558 | certifi = ">=2017.4.17"
559 | charset-normalizer = ">=2,<3"
560 | idna = ">=2.5,<4"
561 | urllib3 = ">=1.21.1,<1.27"
562 |
563 | [package.extras]
564 | socks = ["PySocks (>=1.5.6,!=1.5.7)"]
565 | use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
566 |
567 | [[package]]
568 | name = "rsa"
569 | version = "4.9"
570 | description = "Pure-Python RSA implementation"
571 | category = "main"
572 | optional = false
573 | python-versions = ">=3.6,<4"
574 |
575 | [package.dependencies]
576 | pyasn1 = ">=0.1.3"
577 |
578 | [[package]]
579 | name = "setuptools"
580 | version = "65.4.1"
581 | description = "Easily download, build, install, upgrade, and uninstall Python packages"
582 | category = "dev"
583 | optional = false
584 | python-versions = ">=3.7"
585 |
586 | [package.extras]
587 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
588 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
589 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
590 |
591 | [[package]]
592 | name = "six"
593 | version = "1.16.0"
594 | description = "Python 2 and 3 compatibility utilities"
595 | category = "main"
596 | optional = false
597 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
598 |
599 | [[package]]
600 | name = "sniffio"
601 | version = "1.3.0"
602 | description = "Sniff out which async library your code is running under"
603 | category = "main"
604 | optional = false
605 | python-versions = ">=3.7"
606 |
607 | [[package]]
608 | name = "snowballstemmer"
609 | version = "2.2.0"
610 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
611 | category = "dev"
612 | optional = false
613 | python-versions = "*"
614 |
615 | [[package]]
616 | name = "Sphinx"
617 | version = "3.5.4"
618 | description = "Python documentation generator"
619 | category = "dev"
620 | optional = false
621 | python-versions = ">=3.5"
622 |
623 | [package.dependencies]
624 | alabaster = ">=0.7,<0.8"
625 | babel = ">=1.3"
626 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
627 | docutils = ">=0.12,<0.17"
628 | imagesize = "*"
629 | Jinja2 = ">=2.3"
630 | packaging = "*"
631 | Pygments = ">=2.0"
632 | requests = ">=2.5.0"
633 | setuptools = "*"
634 | snowballstemmer = ">=1.1"
635 | sphinxcontrib-applehelp = "*"
636 | sphinxcontrib-devhelp = "*"
637 | sphinxcontrib-htmlhelp = "*"
638 | sphinxcontrib-jsmath = "*"
639 | sphinxcontrib-qthelp = "*"
640 | sphinxcontrib-serializinghtml = "*"
641 |
642 | [package.extras]
643 | docs = ["sphinxcontrib-websupport"]
644 | lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.800)"]
645 | test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"]
646 |
647 | [[package]]
648 | name = "sphinx-rtd-theme"
649 | version = "1.0.0"
650 | description = "Read the Docs theme for Sphinx"
651 | category = "dev"
652 | optional = false
653 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
654 |
655 | [package.dependencies]
656 | docutils = "<0.18"
657 | sphinx = ">=1.6"
658 |
659 | [package.extras]
660 | dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"]
661 |
662 | [[package]]
663 | name = "sphinxcontrib-applehelp"
664 | version = "1.0.2"
665 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
666 | category = "dev"
667 | optional = false
668 | python-versions = ">=3.5"
669 |
670 | [package.extras]
671 | lint = ["docutils-stubs", "flake8", "mypy"]
672 | test = ["pytest"]
673 |
674 | [[package]]
675 | name = "sphinxcontrib-devhelp"
676 | version = "1.0.2"
677 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
678 | category = "dev"
679 | optional = false
680 | python-versions = ">=3.5"
681 |
682 | [package.extras]
683 | lint = ["docutils-stubs", "flake8", "mypy"]
684 | test = ["pytest"]
685 |
686 | [[package]]
687 | name = "sphinxcontrib-htmlhelp"
688 | version = "2.0.0"
689 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
690 | category = "dev"
691 | optional = false
692 | python-versions = ">=3.6"
693 |
694 | [package.extras]
695 | lint = ["docutils-stubs", "flake8", "mypy"]
696 | test = ["html5lib", "pytest"]
697 |
698 | [[package]]
699 | name = "sphinxcontrib-jsmath"
700 | version = "1.0.1"
701 | description = "A sphinx extension which renders display math in HTML via JavaScript"
702 | category = "dev"
703 | optional = false
704 | python-versions = ">=3.5"
705 |
706 | [package.extras]
707 | test = ["flake8", "mypy", "pytest"]
708 |
709 | [[package]]
710 | name = "sphinxcontrib-qthelp"
711 | version = "1.0.3"
712 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
713 | category = "dev"
714 | optional = false
715 | python-versions = ">=3.5"
716 |
717 | [package.extras]
718 | lint = ["docutils-stubs", "flake8", "mypy"]
719 | test = ["pytest"]
720 |
721 | [[package]]
722 | name = "sphinxcontrib-serializinghtml"
723 | version = "1.1.5"
724 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
725 | category = "dev"
726 | optional = false
727 | python-versions = ">=3.5"
728 |
729 | [package.extras]
730 | lint = ["docutils-stubs", "flake8", "mypy"]
731 | test = ["pytest"]
732 |
733 | [[package]]
734 | name = "starlette"
735 | version = "0.20.4"
736 | description = "The little ASGI library that shines."
737 | category = "main"
738 | optional = false
739 | python-versions = ">=3.7"
740 |
741 | [package.dependencies]
742 | anyio = ">=3.4.0,<5"
743 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
744 |
745 | [package.extras]
746 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
747 |
748 | [[package]]
749 | name = "toml"
750 | version = "0.10.2"
751 | description = "Python Library for Tom's Obvious, Minimal Language"
752 | category = "dev"
753 | optional = false
754 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
755 |
756 | [[package]]
757 | name = "tomli"
758 | version = "1.2.3"
759 | description = "A lil' TOML parser"
760 | category = "dev"
761 | optional = false
762 | python-versions = ">=3.6"
763 |
764 | [[package]]
765 | name = "tomlkit"
766 | version = "0.11.5"
767 | description = "Style preserving TOML library"
768 | category = "dev"
769 | optional = false
770 | python-versions = ">=3.6,<4.0"
771 |
772 | [[package]]
773 | name = "types-cachetools"
774 | version = "0.1.10"
775 | description = "Typing stubs for cachetools"
776 | category = "dev"
777 | optional = false
778 | python-versions = "*"
779 |
780 | [[package]]
781 | name = "types-requests"
782 | version = "2.28.11.1"
783 | description = "Typing stubs for requests"
784 | category = "dev"
785 | optional = false
786 | python-versions = "*"
787 |
788 | [package.dependencies]
789 | types-urllib3 = "<1.27"
790 |
791 | [[package]]
792 | name = "types-urllib3"
793 | version = "1.26.25"
794 | description = "Typing stubs for urllib3"
795 | category = "dev"
796 | optional = false
797 | python-versions = "*"
798 |
799 | [[package]]
800 | name = "typing-extensions"
801 | version = "4.3.0"
802 | description = "Backported and Experimental Type Hints for Python 3.7+"
803 | category = "main"
804 | optional = false
805 | python-versions = ">=3.7"
806 |
807 | [[package]]
808 | name = "urllib3"
809 | version = "1.26.12"
810 | description = "HTTP library with thread-safe connection pooling, file post, and more."
811 | category = "main"
812 | optional = false
813 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
814 |
815 | [package.extras]
816 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
817 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
818 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
819 |
820 | [[package]]
821 | name = "uvicorn"
822 | version = "0.15.0"
823 | description = "The lightning-fast ASGI server."
824 | category = "dev"
825 | optional = false
826 | python-versions = "*"
827 |
828 | [package.dependencies]
829 | asgiref = ">=3.4.0"
830 | click = ">=7.0"
831 | h11 = ">=0.8"
832 |
833 | [package.extras]
834 | standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.2.0,<0.3.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchgod (>=0.6)", "websockets (>=9.1)"]
835 |
836 | [[package]]
837 | name = "wrapt"
838 | version = "1.14.1"
839 | description = "Module for decorators, wrappers and monkey patching."
840 | category = "dev"
841 | optional = false
842 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
843 |
844 | [extras]
845 | docs = []
846 |
847 | [metadata]
848 | lock-version = "1.1"
849 | python-versions = "^3.8"
850 | content-hash = "76eba1fbd3b013060c6a9e967cd2df4feaac84eb587d3b6b6434900a7beb398c"
851 |
852 | [metadata.files]
853 | alabaster = [
854 | {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
855 | {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
856 | ]
857 | anyio = [
858 | {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
859 | {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
860 | ]
861 | appdirs = [
862 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
863 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
864 | ]
865 | asgiref = [
866 | {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
867 | {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
868 | ]
869 | astroid = [
870 | {file = "astroid-2.12.10-py3-none-any.whl", hash = "sha256:997e0c735df60d4a4caff27080a3afc51f9bdd693d3572a4a0b7090b645c36c5"},
871 | {file = "astroid-2.12.10.tar.gz", hash = "sha256:81f870105d892e73bf535da77a8261aa5bde838fa4ed12bb2f435291a098c581"},
872 | ]
873 | atomicwrites = [
874 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
875 | ]
876 | attrs = [
877 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
878 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
879 | ]
880 | Babel = [
881 | {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
882 | {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
883 | ]
884 | black = [
885 | {file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"},
886 | {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"},
887 | ]
888 | cachetools = [
889 | {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
890 | {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
891 | ]
892 | certifi = [
893 | {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
894 | {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
895 | ]
896 | cffi = [
897 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
898 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
899 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
900 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
901 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
902 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
903 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
904 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
905 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
906 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
907 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
908 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
909 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
910 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
911 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
912 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
913 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
914 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
915 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
916 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
917 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
918 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
919 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
920 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
921 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
922 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
923 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
924 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
925 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
926 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
927 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
928 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
929 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
930 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
931 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
932 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
933 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
934 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
935 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
936 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
937 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
938 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
939 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
940 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
941 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
942 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
943 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
944 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
945 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
946 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
947 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
948 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
949 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
950 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
951 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
952 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
953 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
954 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
955 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
956 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
957 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
958 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
959 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
960 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
961 | ]
962 | charset-normalizer = [
963 | {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
964 | {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
965 | ]
966 | click = [
967 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
968 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
969 | ]
970 | colorama = [
971 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
972 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
973 | ]
974 | cryptography = [
975 | {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
976 | {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
977 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
978 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
979 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
980 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
981 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
982 | {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
983 | {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
984 | {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
985 | {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
986 | {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
987 | {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
988 | {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
989 | {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
990 | {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
991 | {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
992 | {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
993 | {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
994 | {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
995 | {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
996 | {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
997 | {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
998 | {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
999 | {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
1000 | {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
1001 | ]
1002 | dill = [
1003 | {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
1004 | {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
1005 | ]
1006 | docutils = [
1007 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
1008 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
1009 | ]
1010 | ecdsa = [
1011 | {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
1012 | {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
1013 | ]
1014 | fastapi = [
1015 | {file = "fastapi-0.85.0-py3-none-any.whl", hash = "sha256:1803d962f169dc9f8dde54a64b22eb16f6d81573f54401971f90f0a67234a8b4"},
1016 | {file = "fastapi-0.85.0.tar.gz", hash = "sha256:bb219cfafd0d2ccf8f32310c9a257a06b0210bd8e2a03706a6f5a9f9f1416878"},
1017 | ]
1018 | h11 = [
1019 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
1020 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
1021 | ]
1022 | idna = [
1023 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
1024 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
1025 | ]
1026 | imagesize = [
1027 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
1028 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
1029 | ]
1030 | iniconfig = [
1031 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
1032 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
1033 | ]
1034 | isort = [
1035 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
1036 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
1037 | ]
1038 | Jinja2 = [
1039 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
1040 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
1041 | ]
1042 | lazy-object-proxy = [
1043 | {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
1044 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
1045 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
1046 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"},
1047 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
1048 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
1049 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
1050 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
1051 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
1052 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
1053 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"},
1054 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
1055 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
1056 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
1057 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
1058 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
1059 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
1060 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"},
1061 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
1062 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
1063 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
1064 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
1065 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
1066 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
1067 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"},
1068 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
1069 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
1070 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
1071 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
1072 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
1073 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
1074 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"},
1075 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
1076 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
1077 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
1078 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
1079 | {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
1080 | ]
1081 | MarkupSafe = [
1082 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
1083 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
1084 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
1085 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
1086 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
1087 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
1088 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
1089 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
1090 | {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
1091 | {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
1092 | {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
1093 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
1094 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
1095 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
1096 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
1097 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
1098 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
1099 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
1100 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
1101 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
1102 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
1103 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
1104 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
1105 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
1106 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
1107 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
1108 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
1109 | {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
1110 | {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
1111 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
1112 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
1113 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
1114 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
1115 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
1116 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
1117 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
1118 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
1119 | {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
1120 | {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
1121 | {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
1122 | ]
1123 | mccabe = [
1124 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
1125 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
1126 | ]
1127 | mypy = [
1128 | {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
1129 | {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
1130 | {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
1131 | {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
1132 | {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
1133 | {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
1134 | {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
1135 | {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
1136 | {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
1137 | {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
1138 | {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
1139 | {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
1140 | {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
1141 | {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
1142 | {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
1143 | {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
1144 | {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
1145 | {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
1146 | {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
1147 | {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
1148 | {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
1149 | {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
1150 | {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
1151 | ]
1152 | mypy-extensions = [
1153 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
1154 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
1155 | ]
1156 | packaging = [
1157 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
1158 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
1159 | ]
1160 | pathspec = [
1161 | {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
1162 | {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
1163 | ]
1164 | platformdirs = [
1165 | {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
1166 | {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
1167 | ]
1168 | pluggy = [
1169 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
1170 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
1171 | ]
1172 | py = [
1173 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
1174 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
1175 | ]
1176 | pyasn1 = [
1177 | {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
1178 | {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
1179 | ]
1180 | pycparser = [
1181 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
1182 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
1183 | ]
1184 | pydantic = [
1185 | {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
1186 | {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
1187 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
1188 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
1189 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
1190 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
1191 | {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
1192 | {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
1193 | {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
1194 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
1195 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
1196 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
1197 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
1198 | {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
1199 | {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
1200 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
1201 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
1202 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
1203 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
1204 | {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
1205 | {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
1206 | {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
1207 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
1208 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
1209 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
1210 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
1211 | {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
1212 | {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
1213 | {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
1214 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
1215 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
1216 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
1217 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
1218 | {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
1219 | {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
1220 | {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
1221 | ]
1222 | Pygments = [
1223 | {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
1224 | {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
1225 | ]
1226 | PyJWT = [
1227 | {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"},
1228 | {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"},
1229 | ]
1230 | pylint = [
1231 | {file = "pylint-2.15.3-py3-none-any.whl", hash = "sha256:7f6aad1d8d50807f7bc64f89ac75256a9baf8e6ed491cc9bc65592bc3f462cf1"},
1232 | {file = "pylint-2.15.3.tar.gz", hash = "sha256:5fdfd44af182866999e6123139d265334267339f29961f00c89783155eacc60b"},
1233 | ]
1234 | pyparsing = [
1235 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
1236 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
1237 | ]
1238 | pytest = [
1239 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
1240 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
1241 | ]
1242 | python-jose = [
1243 | {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
1244 | {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
1245 | ]
1246 | pytz = [
1247 | {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"},
1248 | {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"},
1249 | ]
1250 | regex = [
1251 | {file = "regex-2022.9.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0394265391a86e2bbaa7606e59ac71bd9f1edf8665a59e42771a9c9adbf6fd4f"},
1252 | {file = "regex-2022.9.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86df2049b18745f3cd4b0f4c4ef672bfac4b80ca488e6ecfd2bbfe68d2423a2c"},
1253 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce331b076b2b013e7d7f07157f957974ef0b0881a808e8a4a4b3b5105aee5d04"},
1254 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:360ffbc9357794ae41336b681dff1c0463193199dfb91fcad3ec385ea4972f46"},
1255 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18e503b1e515a10282b3f14f1b3d856194ecece4250e850fad230842ed31227f"},
1256 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e167d1ccd41d27b7b6655bb7a2dcb1b1eb1e0d2d662043470bd3b4315d8b2b"},
1257 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4146cb7ae6029fc83b5c905ec6d806b7e5568dc14297c423e66b86294bad6c39"},
1258 | {file = "regex-2022.9.13-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a1aec4ae549fd7b3f52ceaf67e133010e2fba1538bf4d5fc5cd162a5e058d5df"},
1259 | {file = "regex-2022.9.13-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cab548d6d972e1de584161487b2ac1aa82edd8430d1bde69587ba61698ad1cfb"},
1260 | {file = "regex-2022.9.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d64e1a7e6d98a4cdc8b29cb8d8ed38f73f49e55fbaa737bdb5933db99b9de22"},
1261 | {file = "regex-2022.9.13-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:67a4c625361db04ae40ef7c49d3cbe2c1f5ff10b5a4491327ab20f19f2fb5d40"},
1262 | {file = "regex-2022.9.13-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:5d0dd8b06896423211ce18fba0c75dacc49182a1d6514c004b535be7163dca0f"},
1263 | {file = "regex-2022.9.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4318f69b79f9f7d84a7420e97d4bfe872dc767c72f891d4fea5fa721c74685f7"},
1264 | {file = "regex-2022.9.13-cp310-cp310-win32.whl", hash = "sha256:26df88c9636a0c3f3bd9189dd435850a0c49d0b7d6e932500db3f99a6dd604d1"},
1265 | {file = "regex-2022.9.13-cp310-cp310-win_amd64.whl", hash = "sha256:6fe1dd1021e0f8f3f454ce2811f1b0b148f2d25bb38c712fec00316551e93650"},
1266 | {file = "regex-2022.9.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83cc32a1a2fa5bac00f4abc0e6ce142e3c05d3a6d57e23bd0f187c59b4e1e43b"},
1267 | {file = "regex-2022.9.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2effeaf50a6838f3dd4d3c5d265f06eabc748f476e8441892645ae3a697e273"},
1268 | {file = "regex-2022.9.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a786a55d00439d8fae4caaf71581f2aaef7297d04ee60345c3594efef5648a"},
1269 | {file = "regex-2022.9.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b701dbc124558fd2b1b08005eeca6c9160e209108fbcbd00091fcfac641ac7"},
1270 | {file = "regex-2022.9.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab81cc4d58026861445230cfba27f9825e9223557926e7ec22156a1a140d55c"},
1271 | {file = "regex-2022.9.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0c5cc3d1744a67c3b433dce91e5ef7c527d612354c1f1e8576d9e86bc5c5e2"},
1272 | {file = "regex-2022.9.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:518272f25da93e02af4f1e94985f5042cec21557ef3591027d0716f2adda5d0a"},
1273 | {file = "regex-2022.9.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8418ee2cb857b83881b8f981e4c636bc50a0587b12d98cb9b947408a3c484fe7"},
1274 | {file = "regex-2022.9.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cfa4c956ff0a977c4823cb3b930b0a4e82543b060733628fec7ab3eb9b1abe37"},
1275 | {file = "regex-2022.9.13-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a1c4d17879dd4c4432c08a1ca1ab379f12ab54af569e945b6fc1c4cf6a74ca45"},
1276 | {file = "regex-2022.9.13-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:77c2879d3ba51e5ca6c2b47f2dcf3d04a976a623a8fc8236010a16c9e0b0a3c7"},
1277 | {file = "regex-2022.9.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2885ec6eea629c648ecc9bde0837ec6b92208b7f36381689937fe5d64a517e8"},
1278 | {file = "regex-2022.9.13-cp311-cp311-win32.whl", hash = "sha256:2dda4b096a6f630d6531728a45bd12c67ec3badf44342046dc77d4897277d4f2"},
1279 | {file = "regex-2022.9.13-cp311-cp311-win_amd64.whl", hash = "sha256:592b9e2e1862168e71d9e612bfdc22c451261967dbd46681f14e76dfba7105fd"},
1280 | {file = "regex-2022.9.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:df8fe00b60e4717662c7f80c810ba66dcc77309183c76b7754c0dff6f1d42054"},
1281 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995e70bb8c91d1b99ed2aaf8ec44863e06ad1dfbb45d7df95f76ef583ec323a9"},
1282 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad75173349ad79f9d21e0d0896b27dcb37bfd233b09047bc0b4d226699cf5c87"},
1283 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7681c49da1a2d4b905b4f53d86c9ba4506e79fba50c4a664d9516056e0f7dfcc"},
1284 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bc8edc5f8ef0ebb46f3fa0d02bd825bbe9cc63d59e428ffb6981ff9672f6de1"},
1285 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bee775ff05c9d519195bd9e8aaaccfe3971db60f89f89751ee0f234e8aeac5"},
1286 | {file = "regex-2022.9.13-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1a901ce5cd42658ab8f8eade51b71a6d26ad4b68c7cfc86b87efc577dfa95602"},
1287 | {file = "regex-2022.9.13-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:14a7ab070fa3aec288076eed6ed828587b805ef83d37c9bfccc1a4a7cfbd8111"},
1288 | {file = "regex-2022.9.13-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d23ac6b4bf9e32fcde5fcdb2e1fd5e7370d6693fcac51ee1d340f0e886f50d1f"},
1289 | {file = "regex-2022.9.13-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:4cdbfa6d2befeaee0c899f19222e9b20fc5abbafe5e9c43a46ef819aeb7b75e5"},
1290 | {file = "regex-2022.9.13-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ab07934725e6f25c6f87465976cc69aef1141e86987af49d8c839c3ffd367c72"},
1291 | {file = "regex-2022.9.13-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d2a1371dc73e921f3c2e087c05359050f3525a9a34b476ebc8130e71bec55e97"},
1292 | {file = "regex-2022.9.13-cp36-cp36m-win32.whl", hash = "sha256:fcbd1edff1473d90dc5cf4b52d355cf1f47b74eb7c85ba6e45f45d0116b8edbd"},
1293 | {file = "regex-2022.9.13-cp36-cp36m-win_amd64.whl", hash = "sha256:fe428822b7a8c486bcd90b334e9ab541ce6cc0d6106993d59f201853e5e14121"},
1294 | {file = "regex-2022.9.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d7430f041755801b712ec804aaf3b094b9b5facbaa93a6339812a8e00d7bd53a"},
1295 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:079c182f99c89524069b9cd96f5410d6af437e9dca576a7d59599a574972707e"},
1296 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59bac44b5a07b08a261537f652c26993af9b1bbe2a29624473968dd42fc29d56"},
1297 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a59d0377e58d96a6f11636e97992f5b51b7e1e89eb66332d1c01b35adbabfe8a"},
1298 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d68eb704b24bc4d441b24e4a12653acd07d2c39940548761e0985a08bc1fff"},
1299 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0385d66e73cdd4462f3cc42c76a6576ddcc12472c30e02a2ae82061bff132c32"},
1300 | {file = "regex-2022.9.13-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:db45016364eec9ddbb5af93c8740c5c92eb7f5fc8848d1ae04205a40a1a2efc6"},
1301 | {file = "regex-2022.9.13-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:03ff695518482b946a6d3d4ce9cbbd99a21320e20d94913080aa3841f880abcd"},
1302 | {file = "regex-2022.9.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6b32b45433df1fad7fed738fe15200b6516da888e0bd1fdd6aa5e50cc16b76bc"},
1303 | {file = "regex-2022.9.13-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:003a2e1449d425afc817b5f0b3d4c4aa9072dd5f3dfbf6c7631b8dc7b13233de"},
1304 | {file = "regex-2022.9.13-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a9eb9558e1d0f78e07082d8a70d5c4d631c8dd75575fae92105df9e19c736730"},
1305 | {file = "regex-2022.9.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f6e0321921d2fdc082ef90c1fd0870f129c2e691bfdc4937dcb5cd308aba95c4"},
1306 | {file = "regex-2022.9.13-cp37-cp37m-win32.whl", hash = "sha256:3f3b4594d564ed0b2f54463a9f328cf6a5b2a32610a90cdff778d6e3e561d08b"},
1307 | {file = "regex-2022.9.13-cp37-cp37m-win_amd64.whl", hash = "sha256:8aba0d01e3dfd335f2cb107079b07fdddb4cd7fb2d8c8a1986f9cb8ce9246c24"},
1308 | {file = "regex-2022.9.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:944567bb08f52268d8600ee5bdf1798b2b62ea002cc692a39cec113244cbdd0d"},
1309 | {file = "regex-2022.9.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b664a4d33ffc6be10996606dfc25fd3248c24cc589c0b139feb4c158053565e"},
1310 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f06cc1190f3db3192ab8949e28f2c627e1809487e2cfc435b6524c1ce6a2f391"},
1311 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c57d50d4d5eb0c862569ca3c840eba2a73412f31d9ecc46ef0d6b2e621a592b"},
1312 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19a4da6f513045f5ba00e491215bd00122e5bd131847586522463e5a6b2bd65f"},
1313 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a926339356fe29595f8e37af71db37cd87ff764e15da8ad5129bbaff35bcc5a6"},
1314 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:091efcfdd4178a7e19a23776dc2b1fafb4f57f4d94daf340f98335817056f874"},
1315 | {file = "regex-2022.9.13-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:880dbeb6bdde7d926b4d8e41410b16ffcd4cb3b4c6d926280fea46e2615c7a01"},
1316 | {file = "regex-2022.9.13-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:73b985c9fc09a7896846e26d7b6f4d1fd5a20437055f4ef985d44729f9f928d0"},
1317 | {file = "regex-2022.9.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c0b7cb9598795b01f9a3dd3f770ab540889259def28a3bf9b2fa24d52edecba3"},
1318 | {file = "regex-2022.9.13-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:37e5a26e76c46f54b3baf56a6fdd56df9db89758694516413757b7d127d4c57b"},
1319 | {file = "regex-2022.9.13-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:99945ddb4f379bb9831c05e9f80f02f079ba361a0fb1fba1fc3b267639b6bb2e"},
1320 | {file = "regex-2022.9.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dcbcc9e72a791f622a32d17ff5011326a18996647509cac0609a7fc43adc229"},
1321 | {file = "regex-2022.9.13-cp38-cp38-win32.whl", hash = "sha256:d3102ab9bf16bf541ca228012d45d88d2a567c9682a805ae2c145a79d3141fdd"},
1322 | {file = "regex-2022.9.13-cp38-cp38-win_amd64.whl", hash = "sha256:14216ea15efc13f28d0ef1c463d86d93ca7158a79cd4aec0f9273f6d4c6bb047"},
1323 | {file = "regex-2022.9.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9a165a05979e212b2c2d56a9f40b69c811c98a788964e669eb322de0a3e420b4"},
1324 | {file = "regex-2022.9.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:14c71437ffb89479c89cc7022a5ea2075a842b728f37205e47c824cc17b30a42"},
1325 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee7045623a5ace70f3765e452528b4c1f2ce669ed31959c63f54de64fe2f6ff7"},
1326 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e521d9db006c5e4a0f8acfef738399f72b704913d4e083516774eb51645ad7c"},
1327 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86548b8234b2be3985dbc0b385e35f5038f0f3e6251464b827b83ebf4ed90e5"},
1328 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b39ee3b280e15824298b97cec3f7cbbe6539d8282cc8a6047a455b9a72c598"},
1329 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6e6e61e9a38b6cc60ca3e19caabc90261f070f23352e66307b3d21a24a34aaf"},
1330 | {file = "regex-2022.9.13-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d837ccf3bd2474feabee96cd71144e991472e400ed26582edc8ca88ce259899c"},
1331 | {file = "regex-2022.9.13-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6adfe300848d61a470ec7547adc97b0ccf86de86a99e6830f1d8c8d19ecaf6b3"},
1332 | {file = "regex-2022.9.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5b003d248e6f292475cd24b04e5f72c48412231961a675edcb653c70730e79e"},
1333 | {file = "regex-2022.9.13-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d5edd3eb877c9fc2e385173d4a4e1d792bf692d79e25c1ca391802d36ecfaa01"},
1334 | {file = "regex-2022.9.13-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:50e764ffbd08b06aa8c4e86b8b568b6722c75d301b33b259099f237c46b2134e"},
1335 | {file = "regex-2022.9.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d43bd402b27e0e7eae85c612725ba1ce7798f20f6fab4e8bc3de4f263294f03"},
1336 | {file = "regex-2022.9.13-cp39-cp39-win32.whl", hash = "sha256:7fcf7f94ccad19186820ac67e2ec7e09e0ac2dac39689f11cf71eac580503296"},
1337 | {file = "regex-2022.9.13-cp39-cp39-win_amd64.whl", hash = "sha256:322bd5572bed36a5b39952d88e072738926759422498a96df138d93384934ff8"},
1338 | {file = "regex-2022.9.13.tar.gz", hash = "sha256:f07373b6e56a6f3a0df3d75b651a278ca7bd357a796078a26a958ea1ce0588fd"},
1339 | ]
1340 | requests = [
1341 | {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
1342 | {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
1343 | ]
1344 | rsa = [
1345 | {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
1346 | {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
1347 | ]
1348 | setuptools = [
1349 | {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
1350 | {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
1351 | ]
1352 | six = [
1353 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
1354 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
1355 | ]
1356 | sniffio = [
1357 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
1358 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
1359 | ]
1360 | snowballstemmer = [
1361 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
1362 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
1363 | ]
1364 | Sphinx = [
1365 | {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"},
1366 | {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"},
1367 | ]
1368 | sphinx-rtd-theme = [
1369 | {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
1370 | {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
1371 | ]
1372 | sphinxcontrib-applehelp = [
1373 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
1374 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
1375 | ]
1376 | sphinxcontrib-devhelp = [
1377 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
1378 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
1379 | ]
1380 | sphinxcontrib-htmlhelp = [
1381 | {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
1382 | {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
1383 | ]
1384 | sphinxcontrib-jsmath = [
1385 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
1386 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
1387 | ]
1388 | sphinxcontrib-qthelp = [
1389 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
1390 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
1391 | ]
1392 | sphinxcontrib-serializinghtml = [
1393 | {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
1394 | {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
1395 | ]
1396 | starlette = [
1397 | {file = "starlette-0.20.4-py3-none-any.whl", hash = "sha256:c0414d5a56297d37f3db96a84034d61ce29889b9eaccf65eb98a0b39441fcaa3"},
1398 | {file = "starlette-0.20.4.tar.gz", hash = "sha256:42fcf3122f998fefce3e2c5ad7e5edbf0f02cf685d646a83a08d404726af5084"},
1399 | ]
1400 | toml = [
1401 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
1402 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
1403 | ]
1404 | tomli = [
1405 | {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
1406 | {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
1407 | ]
1408 | tomlkit = [
1409 | {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"},
1410 | {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"},
1411 | ]
1412 | types-cachetools = [
1413 | {file = "types-cachetools-0.1.10.tar.gz", hash = "sha256:f2727cf379989ba4d081f9ed11ec28c6730adbd120dc0deb2036c5ef69e515e0"},
1414 | {file = "types_cachetools-0.1.10-py3-none-any.whl", hash = "sha256:76d312d6e55fe19f7a2ea9a3271945e6f5c56f4fd76cc3cb2366109785f3462b"},
1415 | ]
1416 | types-requests = [
1417 | {file = "types-requests-2.28.11.1.tar.gz", hash = "sha256:02b1806c5b9904edcd87fa29236164aea0e6cdc4d93ea020cd615ef65cb43d65"},
1418 | {file = "types_requests-2.28.11.1-py3-none-any.whl", hash = "sha256:1ff2c1301f6fe58b5d1c66cdf631ca19734cb3b1a4bbadc878d75557d183291a"},
1419 | ]
1420 | types-urllib3 = [
1421 | {file = "types-urllib3-1.26.25.tar.gz", hash = "sha256:5aef0e663724eef924afa8b320b62ffef2c1736c1fa6caecfc9bc6c8ae2c3def"},
1422 | {file = "types_urllib3-1.26.25-py3-none-any.whl", hash = "sha256:c1d78cef7bd581e162e46c20a57b2e1aa6ebecdcf01fd0713bb90978ff3e3427"},
1423 | ]
1424 | typing-extensions = [
1425 | {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
1426 | {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
1427 | ]
1428 | urllib3 = [
1429 | {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
1430 | {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
1431 | ]
1432 | uvicorn = [
1433 | {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"},
1434 | {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"},
1435 | ]
1436 | wrapt = [
1437 | {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
1438 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
1439 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
1440 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
1441 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
1442 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
1443 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
1444 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
1445 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
1446 | {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
1447 | {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
1448 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
1449 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
1450 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
1451 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
1452 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
1453 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
1454 | {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
1455 | {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
1456 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
1457 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
1458 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
1459 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
1460 | {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
1461 | {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
1462 | {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
1463 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
1464 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
1465 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
1466 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
1467 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
1468 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
1469 | {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
1470 | {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
1471 | {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
1472 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
1473 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
1474 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
1475 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
1476 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
1477 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
1478 | {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
1479 | {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
1480 | {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
1481 | {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
1482 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
1483 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
1484 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
1485 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
1486 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
1487 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
1488 | {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
1489 | {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
1490 | {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
1491 | {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
1492 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
1493 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
1494 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
1495 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
1496 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
1497 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
1498 | {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
1499 | {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
1500 | {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
1501 | ]
1502 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "fastapi-third-party-auth"
3 | version = "0.0.0"
4 | description = "Simple library for using a third party authentication service like Keycloak or Auth0 with FastAPI"
5 | authors = ["HarryMWinters ", "Richard Löwenström "]
6 | license = "MIT"
7 | readme = "README.md"
8 | homepage = "https://github.com/aiwizo/fastapi-third-party-auth"
9 | repository = "https://github.com/aiwizo/fastapi-third-party-auth"
10 | documentation = "https://fastapi-third-party-auth.readthedocs.io/en/latest/"
11 |
12 | [tool.poetry.dependencies]
13 | python = "^3.8"
14 | fastapi = ">= 0.61.0"
15 | pydantic = ">= 1.6.1"
16 | cachetools = ">= 4.1.1"
17 | requests = ">= 2.24.0"
18 | python-jose = {extras = ["cryptography"], version = ">= 3.2.0"}
19 |
20 | [tool.poetry.dev-dependencies]
21 | pytest = "^6.0.1"
22 | black = "21.7b0"
23 | pylint = "^2.6.0"
24 | pyjwt = "^1.7.1"
25 | sphinx = "^3.3.1"
26 | mypy = "^0.910"
27 | types-cachetools = "^0.1.9"
28 | types-requests = "^2.25.0"
29 | uvicorn = "^0.15.0"
30 | sphinx-rtd-theme = "^1.0.0"
31 |
32 | [tool.poetry.extras]
33 | docs = ["sphinx"]
34 |
35 | [build-system]
36 | requires = ["poetry>=0.12"]
37 | build-backend = "poetry.masonry.api"
38 |
39 | [tool.isort]
40 | profile = "black"
41 | force_single_line = "True"
42 | known_first_party = []
43 | known_third_party = ["app", "cachetools", "cryptography", "fastapi", "jose", "jwt", "pydantic", "pytest", "requests", "starlette", "uvicorn"]
44 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextml-code/fastapi-third-party-auth/039ee19eef78889f4a09342f18680ecccd7e7879/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import time
4 | import uuid
5 |
6 | import jwt
7 | import pytest
8 | from cryptography.hazmat.backends import default_backend
9 | from cryptography.hazmat.primitives import serialization
10 | from cryptography.hazmat.primitives.asymmetric import rsa
11 |
12 | FIXTURES_DIRECTORY = os.path.join(os.path.dirname(__file__), "fixtures")
13 |
14 |
15 | KEY = rsa.generate_private_key(
16 | backend=default_backend(), public_exponent=65537, key_size=2048
17 | )
18 |
19 |
20 | @pytest.fixture
21 | def oidc_discovery():
22 | with open(FIXTURES_DIRECTORY + "/AuthServerDiscovery.json") as f:
23 | OIDC_DISCOVERY_RESPONSE = json.load(f)
24 |
25 | return OIDC_DISCOVERY_RESPONSE
26 |
27 |
28 | @pytest.fixture
29 | def test_email():
30 | return "AnticipationOfANewLoversArrivalThe@VeryLittleGravitasIndeed"
31 |
32 |
33 | @pytest.fixture
34 | def key():
35 | # keeping the key global so it isn't regenerated with each fixture use.
36 | return KEY
37 |
38 |
39 | @pytest.fixture
40 | def private_key(key):
41 | return key.private_bytes(
42 | serialization.Encoding.PEM,
43 | serialization.PrivateFormat.PKCS8,
44 | serialization.NoEncryption(),
45 | ).decode("UTF-8")
46 |
47 |
48 | @pytest.fixture
49 | def public_key(key):
50 | return (
51 | key.public_key()
52 | .public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.PKCS1)
53 | .decode("UTF-8")
54 | )
55 |
56 |
57 | @pytest.fixture
58 | def config_w_aud():
59 | return {
60 | "client_id": "NeverAgain",
61 | "openid_connect_url": "WhatAreTheCivilianApplications?",
62 | "issuer": "PokeItWithAStick",
63 | "signature_cache_ttl": 6e3,
64 | }
65 |
66 |
67 | @pytest.fixture
68 | def no_audience_config():
69 | return {
70 | "openid_connect_url": "WhatAreTheCivilianApplications?",
71 | "issuer": "PokeItWithAStick",
72 | "signature_cache_ttl": 6e3,
73 | }
74 |
75 |
76 | @pytest.fixture
77 | def token_with_audience(private_key, config_w_aud, test_email) -> str:
78 | audience: str = str(config_w_aud["client_id"])
79 | issuer: str = str(config_w_aud["issuer"])
80 | now = int(time.time())
81 |
82 | return jwt.encode(
83 | {
84 | "aud": audience,
85 | "iss": issuer,
86 | "email": test_email,
87 | "name": "SweetAndFullOfGrace",
88 | "preferred_username": "Sweet",
89 | "exp": now + 30,
90 | "auth_time": now,
91 | "sub": "foo",
92 | "ver": "1",
93 | "iat": now,
94 | "jti": str(uuid.uuid4()),
95 | "amr": [],
96 | "idp": "",
97 | "nonce": "",
98 | "at_hash": "",
99 | },
100 | private_key,
101 | algorithm="RS256",
102 | ).decode("UTF-8")
103 |
104 |
105 | @pytest.fixture
106 | def token_without_audience(private_key, no_audience_config, test_email) -> str:
107 | # Make a token where audience is client_id
108 | issuer: str = str(no_audience_config["issuer"])
109 | now = int(time.time())
110 |
111 | return jwt.encode(
112 | {
113 | "aud": "NoAudience",
114 | "iss": issuer,
115 | "email": test_email,
116 | "name": "SweetAndFullOfGrace",
117 | "preferred_username": "Sweet",
118 | "exp": now + 30,
119 | "auth_time": now,
120 | "sub": "foo",
121 | "ver": "1",
122 | "iat": now,
123 | "jti": str(uuid.uuid4()),
124 | "amr": [],
125 | "idp": "",
126 | "nonce": "",
127 | "at_hash": "",
128 | },
129 | private_key,
130 | algorithm="RS256",
131 | ).decode("UTF-8")
132 |
133 |
134 | @pytest.fixture
135 | def mock_discovery(oidc_discovery, public_key):
136 | class functions:
137 | auth_server = lambda **_: oidc_discovery
138 | public_keys = lambda _: public_key
139 | signing_algos = lambda x: x["id_token_signing_alg_values_supported"]
140 | authorization_url = lambda x: x["authorization_endpoint"]
141 | token_url = lambda x: x["token_endpoint"]
142 | supported_scopes = lambda x: x["scopes_supported"]
143 |
144 | return lambda *args, **kwargs: functions
145 |
--------------------------------------------------------------------------------
/tests/fixtures/AuthServerDiscovery.json:
--------------------------------------------------------------------------------
1 | {
2 | "issuer": "https://WisdomLikeSilence",
3 | "authorization_endpoint": "https://EthicsGradient/authorize",
4 | "token_endpoint": "https://EthicsGradient/token",
5 | "userinfo_endpoint": "https://EthicsGradient/userinfo",
6 | "registration_endpoint": "https://EthicsGradient/clients",
7 | "jwks_uri": "https://EthicsGradient/keys",
8 | "response_types_supported": [
9 | "code",
10 | "code id_token",
11 | "code token",
12 | "code id_token token",
13 | "id_token",
14 | "id_token token"
15 | ],
16 | "response_modes_supported": [
17 | "query",
18 | "fragment",
19 | "form_post",
20 | "okta_post_message"
21 | ],
22 | "grant_types_supported": [
23 | "authorization_code",
24 | "implicit",
25 | "refresh_token",
26 | "password"
27 | ],
28 | "subject_types_supported": ["public"],
29 | "id_token_signing_alg_values_supported": ["RS256"],
30 | "scopes_supported": [
31 | "openid",
32 | "email",
33 | "profile",
34 | "address",
35 | "phone",
36 | "offline_access",
37 | "groups"
38 | ],
39 | "token_endpoint_auth_methods_supported": [
40 | "client_secret_basic",
41 | "client_secret_post",
42 | "client_secret_jwt",
43 | "none"
44 | ],
45 | "claims_supported": [
46 | "iss",
47 | "ver",
48 | "sub",
49 | "aud",
50 | "iat",
51 | "exp",
52 | "jti",
53 | "auth_time",
54 | "amr",
55 | "idp",
56 | "nonce",
57 | "name",
58 | "nickname",
59 | "preferred_username",
60 | "given_name",
61 | "middle_name",
62 | "family_name",
63 | "email",
64 | "email_verified",
65 | "profile",
66 | "zoneinfo",
67 | "locale",
68 | "address",
69 | "phone_number",
70 | "picture",
71 | "website",
72 | "gender",
73 | "birthdate",
74 | "updated_at",
75 | "at_hash",
76 | "c_hash"
77 | ],
78 | "introspection_endpoint": "https://EthicsGradient/introspect",
79 | "introspection_endpoint_auth_methods_supported": [
80 | "client_secret_basic",
81 | "client_secret_post",
82 | "client_secret_jwt",
83 | "none"
84 | ],
85 | "revocation_endpoint": "https://EthicsGradient/revoke",
86 | "revocation_endpoint_auth_methods_supported": [
87 | "client_secret_basic",
88 | "client_secret_post",
89 | "client_secret_jwt",
90 | "none"
91 | ],
92 | "end_session_endpoint": "https://EthicsGradient/logout",
93 | "request_parameter_supported": true,
94 | "request_object_signing_alg_values_supported": ["HS256", "HS384", "HS512"]
95 | }
96 |
--------------------------------------------------------------------------------
/tests/test_auth.py:
--------------------------------------------------------------------------------
1 | from fastapi.security import HTTPAuthorizationCredentials
2 | from fastapi.security import SecurityScopes
3 |
4 | import fastapi_third_party_auth
5 | from fastapi_third_party_auth import Auth
6 | from fastapi_third_party_auth.idtoken_types import IDToken
7 |
8 |
9 | def test__authenticate_user(
10 | monkeypatch,
11 | mock_discovery,
12 | token_with_audience,
13 | config_w_aud,
14 | test_email,
15 | ):
16 | monkeypatch.setattr(
17 | fastapi_third_party_auth.auth.discovery, "configure", mock_discovery
18 | )
19 |
20 | token = token_with_audience
21 |
22 | auth = Auth(**config_w_aud)
23 | id_token = auth.required(
24 | security_scopes=SecurityScopes(scopes=[]),
25 | authorization_credentials=HTTPAuthorizationCredentials(
26 | scheme="Bearer", credentials=token
27 | ),
28 | )
29 |
30 | assert id_token.email == test_email # nosec
31 | assert id_token.aud == config_w_aud["client_id"]
32 |
33 |
34 | def test__authenticate_user_no_aud(
35 | monkeypatch,
36 | mock_discovery,
37 | token_without_audience,
38 | no_audience_config,
39 | test_email,
40 | ):
41 |
42 | monkeypatch.setattr(
43 | fastapi_third_party_auth.auth.discovery, "configure", mock_discovery
44 | )
45 |
46 | token = token_without_audience
47 |
48 | auth = Auth(**no_audience_config)
49 |
50 | id_token = auth.required(
51 | security_scopes=SecurityScopes(scopes=[]),
52 | authorization_credentials=HTTPAuthorizationCredentials(
53 | scheme="Bearer", credentials=token
54 | ),
55 | )
56 |
57 | assert id_token.email == test_email # nosec
58 |
59 |
60 | def test__authenticate_user_returns_custom_tokens(
61 | monkeypatch, mock_discovery, token_without_audience, no_audience_config
62 | ):
63 | class CustomToken(IDToken):
64 | custom_field: str = "OnlySlightlyBent"
65 |
66 | monkeypatch.setattr(
67 | fastapi_third_party_auth.auth.discovery, "configure", mock_discovery
68 | )
69 |
70 | token = token_without_audience
71 |
72 | auth = Auth(
73 | **no_audience_config,
74 | idtoken_model=CustomToken,
75 | )
76 |
77 | custom_token = auth.required(
78 | security_scopes=SecurityScopes(scopes=[]),
79 | authorization_credentials=HTTPAuthorizationCredentials(
80 | scheme="Bearer", credentials=token
81 | ),
82 | )
83 |
84 | assert custom_token.custom_field == "OnlySlightlyBent"
85 |
--------------------------------------------------------------------------------
/tests/test_types.py:
--------------------------------------------------------------------------------
1 | import pydantic
2 | import pytest
3 |
4 | from fastapi_third_party_auth import idtoken_types
5 |
6 |
7 | def test_IDToken_raises_with_bad_field_types():
8 | with pytest.raises(pydantic.ValidationError):
9 | idtoken_types.IDToken(
10 | iss="ClearAirTurbulence",
11 | sub="ValueJudgement",
12 | aud="SoberCounsel",
13 | exp="NowTurningToReason&ItsJustSweetness",
14 | iat="GermaneRiposte",
15 | )
16 |
17 |
18 | def test_IDToken_only_requires_fields_in_OIDC_spec():
19 | # Call IDToken with minimal types defined in spec
20 | assert idtoken_types.IDToken(
21 | iss="ClearAirTurbulence",
22 | sub="ValueJudgement",
23 | aud="SoberCounsel",
24 | exp=3.12,
25 | iat=42,
26 | )
27 |
28 |
29 | def test_IDToken_takes_arbitrary_extra_fields():
30 | assert idtoken_types.IDToken(
31 | iss="ClearAirTurbulence",
32 | sub="ValueJudgement",
33 | aud="SoberCounsel",
34 | exp=3.12,
35 | iat=42,
36 | arbitrary_extra_field="Laskuil-Hliz",
37 | )
38 |
--------------------------------------------------------------------------------