Browse Source

Merge pull request #373 from marcospereirampj/style/more_checks

Style/more checks
pull/374/head
Richard Nemeth 2 years ago
committed by GitHub
parent
commit
a34054d089
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      docs/source/changelog.rst
  2. 1
      docs/source/conf.py
  3. 595
      poetry.lock
  4. 5
      pyproject.toml
  5. 8
      src/keycloak/authorization/__init__.py
  6. 69
      src/keycloak/authorization/permission.py
  7. 78
      src/keycloak/authorization/policy.py
  8. 27
      src/keycloak/authorization/role.py
  9. 115
      src/keycloak/connection.py
  10. 34
      src/keycloak/exceptions.py
  11. 767
      src/keycloak/keycloak_admin.py
  12. 220
      src/keycloak/keycloak_openid.py
  13. 96
      src/keycloak/uma_permissions.py
  14. 180
      tests/conftest.py
  15. 297
      tests/test_keycloak_admin.py
  16. 110
      tests/test_keycloak_openid.py
  17. 7
      tox.ini

1
docs/source/changelog.rst

@ -1,2 +1 @@
.. mdinclude:: ../../CHANGELOG.md

1
docs/source/conf.py

@ -86,6 +86,7 @@ language = "en"
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
suppress_warnings = ["ref.python"]
add_function_parentheses = False
add_module_names = True

595
poetry.lock

@ -117,17 +117,9 @@ category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "chardet"
version = "5.0.0"
description = "Universal encoding detector for Python 3"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.1.0"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@ -148,6 +140,18 @@ python-versions = ">=3.7"
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "codespell"
version = "2.2.1"
description = "Codespell"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"]
hard-encoding-detection = ["chardet"]
[[package]]
name = "colorama"
version = "0.4.5"
@ -158,7 +162,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "commitizen"
version = "2.29.2"
version = "2.32.2"
description = "Python commitizen client tool"
category = "dev"
optional = false
@ -166,7 +170,7 @@ python-versions = ">=3.6.2,<4.0.0"
[package.dependencies]
argcomplete = ">=1.12.1,<2.0.0"
chardet = ">=5.0.0,<6.0.0"
charset-normalizer = ">=2.1.0,<3.0.0"
colorama = ">=0.4.1,<0.5.0"
decli = ">=0.5.2,<0.6.0"
jinja2 = ">=2.10.3"
@ -186,11 +190,11 @@ optional = true
python-versions = "*"
[package.extras]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
test = ["hypothesis (==3.55.3)", "flake8 (==3.7.8)"]
[[package]]
name = "coverage"
version = "6.4.2"
version = "6.4.4"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@ -221,6 +225,14 @@ sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "darglint"
version = "1.8.1"
description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[[package]]
name = "decli"
version = "0.5.2"
@ -231,7 +243,7 @@ python-versions = ">=3.6"
[[package]]
name = "distlib"
version = "0.3.5"
version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
@ -262,15 +274,15 @@ gmpy2 = ["gmpy2"]
[[package]]
name = "filelock"
version = "3.7.1"
version = "3.8.0"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "flake8"
@ -300,7 +312,7 @@ pydocstyle = ">=2.1"
[[package]]
name = "identify"
version = "2.5.2"
version = "2.5.3"
description = "File identification library for Python"
category = "dev"
optional = false
@ -494,8 +506,8 @@ python-versions = ">=3.6"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
testing = ["pytest-benchmark", "pytest"]
dev = ["tox", "pre-commit"]
[[package]]
name = "pre-commit"
@ -581,12 +593,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pygments"
version = "2.12.0"
version = "2.13.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = true
python-versions = ">=3.6"
[package.extras]
plugins = ["importlib-metadata"]
[[package]]
name = "pyparsing"
version = "3.0.9"
@ -633,7 +648,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"]
[[package]]
name = "python-jose"
@ -655,7 +670,7 @@ pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"]
[[package]]
name = "pytz"
version = "2022.1"
version = "2022.2.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = true
@ -830,7 +845,7 @@ docutils = "<0.18"
sphinx = ">=1.6"
[package.extras]
dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"]
[[package]]
name = "sphinxcontrib-applehelp"
@ -841,8 +856,8 @@ optional = true
python-versions = ">=3.5"
[package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]]
name = "sphinxcontrib-devhelp"
@ -853,8 +868,8 @@ optional = true
python-versions = ">=3.5"
[package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]]
name = "sphinxcontrib-htmlhelp"
@ -865,8 +880,8 @@ optional = true
python-versions = ">=3.6"
[package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest", "html5lib"]
test = ["html5lib", "pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]]
name = "sphinxcontrib-jsmath"
@ -877,7 +892,7 @@ optional = true
python-versions = ">=3.5"
[package.extras]
test = ["pytest", "flake8", "mypy"]
test = ["mypy", "flake8", "pytest"]
[[package]]
name = "sphinxcontrib-qthelp"
@ -888,8 +903,8 @@ optional = true
python-versions = ">=3.5"
[package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]]
name = "sphinxcontrib-serializinghtml"
@ -900,8 +915,8 @@ optional = true
python-versions = ">=3.5"
[package.extras]
lint = ["flake8", "mypy", "docutils-stubs"]
test = ["pytest"]
lint = ["docutils-stubs", "mypy", "flake8"]
[[package]]
name = "termcolor"
@ -929,7 +944,7 @@ python-versions = ">=3.7"
[[package]]
name = "tomlkit"
version = "0.11.1"
version = "0.11.4"
description = "Style preserving TOML library"
category = "dev"
optional = false
@ -984,7 +999,7 @@ python-versions = ">=3.5"
[[package]]
name = "urllib3"
version = "1.26.11"
version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
@ -992,26 +1007,26 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*,
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
version = "20.16.2"
version = "20.16.3"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
distlib = ">=0.3.1,<1"
filelock = ">=3.2,<4"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
platformdirs = ">=2,<3"
distlib = ">=0.3.5,<1"
filelock = ">=3.4.1,<4"
importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""}
platformdirs = ">=2.4,<3"
[package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"]
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "wcwidth"
@ -1047,470 +1062,96 @@ docs = ["mock", "alabaster", "commonmark", "recommonmark", "Sphinx", "sphinx-rtd
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "b6851001fb6b3e331a39e6bab1fa2ed99fdc555e9683137c20c9c593c0e1c040"
content-hash = "5d740e81a3604cb20ca85945c2fc134f6373f599315992afb50284f1f993c1d0"
[metadata.files]
alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
]
argcomplete = [
{file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
{file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
]
alabaster = []
argcomplete = []
astroid = []
atomicwrites = []
attrs = []
babel = [
{file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
{file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
]
babel = []
black = []
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
certifi = []
cffi = []
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
chardet = []
cfgv = []
charset-normalizer = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
click = []
codespell = []
colorama = []
commitizen = []
commonmark = [
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
commonmark = []
coverage = []
cryptography = []
decli = [
{file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"},
{file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"},
]
darglint = []
decli = []
distlib = []
docutils = [
{file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
]
docutils = []
ecdsa = []
filelock = [
{file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
{file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
filelock = []
flake8 = []
flake8-docstrings = []
identify = []
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
idna = []
imagesize = []
importlib-metadata = [
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
lazy-object-proxy = [
{file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
{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"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
{file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
{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"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
{file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
{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"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
{file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
{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"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
{file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
{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"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
{file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
]
m2r2 = [
{file = "m2r2-0.3.2-py3-none-any.whl", hash = "sha256:d3684086b61b4bebe2307f15189495360f05a123c9bda2a66462649b7ca236aa"},
{file = "m2r2-0.3.2.tar.gz", hash = "sha256:ccd95b052dcd1ac7442ecb3111262b2001c10e4119b459c34c93ac7a5c2c7868"},
]
markupsafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
mistune = [
{file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
]
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
{file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
nodeenv = [
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
importlib-metadata = []
iniconfig = []
isort = []
jinja2 = []
lazy-object-proxy = []
m2r2 = []
markupsafe = []
mccabe = []
mistune = []
mock = []
mypy-extensions = []
nodeenv = []
packaging = []
pathspec = []
platformdirs = []
pluggy = []
pre-commit = []
prompt-toolkit = []
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pyasn1 = [
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
]
pycodestyle = [
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
]
py = []
pyasn1 = []
pycodestyle = []
pycparser = []
pydocstyle = []
pyflakes = [
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
pygments = [
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
python-jose = [
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
]
pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
questionary = [
{file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"},
{file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"},
]
readthedocs-sphinx-ext = [
{file = "readthedocs-sphinx-ext-2.1.8.tar.gz", hash = "sha256:a57e3713daf77bf91d1ba19e4b9888a47c0abfeb63ecf02e3ac77fcfd99bfe69"},
{file = "readthedocs_sphinx_ext-2.1.8-py2.py3-none-any.whl", hash = "sha256:5ab5875993191e5e526ca196a1082b73116b0cefd79073ab25367ba0458fffe9"},
]
recommonmark = [
{file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"},
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
]
pyflakes = []
pygments = []
pyparsing = []
pytest = []
pytest-cov = []
python-jose = []
pytz = []
pyyaml = []
questionary = []
readthedocs-sphinx-ext = []
recommonmark = []
requests = []
requests-toolbelt = []
rsa = []
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
snowballstemmer = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
six = []
snowballstemmer = []
sphinx = []
sphinx-autoapi = []
sphinx-rtd-theme = [
{file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
{file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
]
sphinxcontrib-applehelp = [
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
{file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
]
sphinxcontrib-devhelp = [
{file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
{file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
]
sphinxcontrib-htmlhelp = [
{file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
{file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
]
sphinxcontrib-jsmath = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
]
sphinxcontrib-qthelp = [
{file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
{file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
]
sphinxcontrib-serializinghtml = [
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
]
termcolor = [
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
sphinx-rtd-theme = []
sphinxcontrib-applehelp = []
sphinxcontrib-devhelp = []
sphinxcontrib-htmlhelp = []
sphinxcontrib-jsmath = []
sphinxcontrib-qthelp = []
sphinxcontrib-serializinghtml = []
termcolor = []
toml = []
tomli = []
tomlkit = []
tox = []
typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
typed-ast = []
typing-extensions = []
unidecode = [
{file = "Unidecode-1.3.4-py3-none-any.whl", hash = "sha256:afa04efcdd818a93237574791be9b2817d7077c25a068b00f8cff7baa4e59257"},
{file = "Unidecode-1.3.4.tar.gz", hash = "sha256:8e4352fb93d5a735c788110d2e7ac8e8031eb06ccbfe8d324ab71735015f9342"},
]
unidecode = []
urllib3 = []
virtualenv = []
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
wrapt = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
{file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
{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"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
{file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
{file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
{file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
{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"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
{file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
{file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
{file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
{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"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
{file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
{file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
{file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
{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"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
{file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
{file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
{file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
{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"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
{file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
wcwidth = []
wrapt = []
zipp = []

5
pyproject.toml

@ -56,6 +56,8 @@ flake8 = "^3.5.0"
flake8-docstrings = "^1.6.0"
commitizen = "^2.28.0"
cryptography = "^37.0.4"
codespell = "^2.1.0"
darglint = "^1.8.1"
[tool.poetry.extras]
docs = [
@ -80,3 +82,6 @@ line-length = 99
[tool.isort]
line_length = 99
profile = "black"
[tool.darglint]
enable = "DAR104"

8
src/keycloak/authorization/__init__.py

@ -44,7 +44,11 @@ class Authorization:
@property
def policies(self):
"""Get policies."""
"""Get policies.
:returns: Policies
:rtype: dict
"""
return self._policies
@policies.setter
@ -55,7 +59,7 @@ class Authorization:
"""Load policies, roles and permissions (scope/resources).
:param data: keycloak authorization data (dict)
:returns: None
:type data: dict
"""
for pol in data["policies"]:
if pol["type"] == "role":

69
src/keycloak/authorization/permission.py

@ -45,10 +45,29 @@ class Permission:
https://keycloak.gitbooks.io/documentation/authorization_services/topics/permission/overview.html
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
def __init__(self, name, type, logic, decision_strategy):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
self.name = name
self.type = type
self.logic = logic
@ -57,16 +76,28 @@ class Permission:
self.scopes = []
def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: Class representation
:rtype: str
"""
return "<Permission: %s (%s)>" % (self.name, self.type)
def __str__(self):
"""Str method."""
"""Str method.
:returns: Class string representation
:rtype: str
"""
return "Permission: %s (%s)" % (self.name, self.type)
@property
def name(self):
"""Get name."""
"""Get name.
:returns: name
:rtype: str
"""
return self._name
@name.setter
@ -75,7 +106,11 @@ class Permission:
@property
def type(self):
"""Get type."""
"""Get type.
:returns: type
:rtype: str
"""
return self._type
@type.setter
@ -84,7 +119,11 @@ class Permission:
@property
def logic(self):
"""Get logic."""
"""Get logic.
:returns: Logic
:rtype: str
"""
return self._logic
@logic.setter
@ -93,7 +132,11 @@ class Permission:
@property
def decision_strategy(self):
"""Get decision strategy."""
"""Get decision strategy.
:returns: Decision strategy
:rtype: str
"""
return self._decision_strategy
@decision_strategy.setter
@ -102,7 +145,11 @@ class Permission:
@property
def resources(self):
"""Get resources."""
"""Get resources.
:returns: Resources
:rtype: list
"""
return self._resources
@resources.setter
@ -111,7 +158,11 @@ class Permission:
@property
def scopes(self):
"""Get scopes."""
"""Get scopes.
:returns: Scopes
:rtype: list
"""
return self._scopes
@scopes.setter

78
src/keycloak/authorization/policy.py

@ -39,10 +39,29 @@ class Policy:
https://keycloak.gitbooks.io/documentation/authorization_services/topics/policy/overview.html
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
def __init__(self, name, type, logic, decision_strategy):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param type: Type
:type type: str
:param logic: Logic
:type logic: str
:param decision_strategy: Decision strategy
:type decision_strategy: str
"""
self.name = name
self.type = type
self.logic = logic
@ -51,16 +70,28 @@ class Policy:
self.permissions = []
def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: Class representation
:rtype: str
"""
return "<Policy: %s (%s)>" % (self.name, self.type)
def __str__(self):
"""Str method."""
"""Str method.
:returns: Class string representation
:rtype: str
"""
return "Policy: %s (%s)" % (self.name, self.type)
@property
def name(self):
"""Get name."""
"""Get name.
:returns: Name
:rtype: str
"""
return self._name
@name.setter
@ -69,7 +100,11 @@ class Policy:
@property
def type(self):
"""Get type."""
"""Get type.
:returns: Type
:rtype: str
"""
return self._type
@type.setter
@ -78,7 +113,11 @@ class Policy:
@property
def logic(self):
"""Get logic."""
"""Get logic.
:returns: Logic
:rtype: str
"""
return self._logic
@logic.setter
@ -87,7 +126,11 @@ class Policy:
@property
def decision_strategy(self):
"""Get decision strategy."""
"""Get decision strategy.
:returns: Decision strategy
:rtype: str
"""
return self._decision_strategy
@decision_strategy.setter
@ -96,7 +139,11 @@ class Policy:
@property
def roles(self):
"""Get roles."""
"""Get roles.
:returns: Roles
:rtype: list
"""
return self._roles
@roles.setter
@ -105,7 +152,11 @@ class Policy:
@property
def permissions(self):
"""Get permissions."""
"""Get permissions.
:returns: Permissions
:rtype: list
"""
return self._permissions
@permissions.setter
@ -115,8 +166,9 @@ class Policy:
def add_role(self, role):
"""Add keycloak role in policy.
:param role: keycloak role.
:return:
:param role: Keycloak role
:type role: keycloak.authorization.Role
:raises KeycloakAuthorizationConfigError: In case of misconfigured policy type
"""
if self.type != "role":
raise KeycloakAuthorizationConfigError(
@ -127,7 +179,7 @@ class Policy:
def add_permission(self, permission):
"""Add keycloak permission in policy.
:param permission: keycloak permission.
:return:
:param permission: Keycloak permission
:type permission: keycloak.authorization.Permission
"""
self._permissions.append(permission)

27
src/keycloak/authorization/role.py

@ -31,19 +31,40 @@ class Role:
manager, and employee are all typical roles that may exist in an organization.
https://keycloak.gitbooks.io/documentation/server_admin/topics/roles.html
:param name: Name
:type name: str
:param required: Required role indicator
:type required: bool
"""
def __init__(self, name, required=False):
"""Init method."""
"""Init method.
:param name: Name
:type name: str
:param required: Required role indicator
:type required: bool
"""
self.name = name
self.required = required
def get_name(self):
"""Get name."""
"""Get name.
:returns: Name
:rtype: str
"""
return self.name
def __eq__(self, other):
"""Eq method."""
"""Eq method.
:param other: The other object
:type other: str
:returns: Equality bool
:rtype: bool | NotImplemented
"""
if isinstance(other, str):
return self.name == other
return NotImplemented

115
src/keycloak/connection.py

@ -37,15 +37,32 @@ from .exceptions import KeycloakConnectionError
class ConnectionManager(object):
"""Represents a simple server connection.
:param base_url: (str) The server URL.
:param headers: (dict) The header parameters of the requests to the server.
:param timeout: (int) Timeout to use for requests to the server.
:param verify: (bool) Verify server SSL.
:param proxies: (dict) The proxies servers requests is sent by.
:param base_url: The server URL.
:type base_url: str
:param headers: The header parameters of the requests to the server.
:type headers: dict
:param timeout: Timeout to use for requests to the server.
:type timeout: int
:param verify: Verify server SSL.
:type verify: bool
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
"""
def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
"""Init method."""
"""Init method.
:param base_url: The server URL.
:type base_url: str
:param headers: The header parameters of the requests to the server.
:type headers: dict
:param timeout: Timeout to use for requests to the server.
:type timeout: int
:param verify: Verify server SSL.
:type verify: bool
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
"""
self.base_url = base_url
self.headers = headers
self.timeout = timeout
@ -73,7 +90,11 @@ class ConnectionManager(object):
@property
def base_url(self):
"""Return base url in use for requests to the server."""
"""Return base url in use for requests to the server.
:returns: Base URL
:rtype: str
"""
return self._base_url
@base_url.setter
@ -82,7 +103,11 @@ class ConnectionManager(object):
@property
def timeout(self):
"""Return timeout in use for request to the server."""
"""Return timeout in use for request to the server.
:returns: Timeout
:rtype: int
"""
return self._timeout
@timeout.setter
@ -91,7 +116,11 @@ class ConnectionManager(object):
@property
def verify(self):
"""Return verify in use for request to the server."""
"""Return verify in use for request to the server.
:returns: Verify indicator
:rtype: bool
"""
return self._verify
@verify.setter
@ -100,7 +129,11 @@ class ConnectionManager(object):
@property
def headers(self):
"""Return header request to the server."""
"""Return header request to the server.
:returns: Request headers
:rtype: dict
"""
return self._headers
@headers.setter
@ -110,8 +143,10 @@ class ConnectionManager(object):
def param_headers(self, key):
"""Return a specific header parameter.
:param key: (str) Header parameters key.
:param key: Header parameters key.
:type key: str
:returns: If the header parameters exist, return its value.
:rtype: str
"""
return self.headers.get(key)
@ -122,32 +157,41 @@ class ConnectionManager(object):
def exist_param_headers(self, key):
"""Check if the parameter exists in the header.
:param key: (str) Header parameters key.
:param key: Header parameters key.
:type key: str
:returns: If the header parameters exist, return True.
:rtype: bool
"""
return self.param_headers(key) is not None
def add_param_headers(self, key, value):
"""Add a single parameter inside the header.
:param key: (str) Header parameters key.
:param value: (str) Value to be added.
:param key: Header parameters key.
:type key: str
:param value: Value to be added.
:type value: str
"""
self.headers[key] = value
def del_param_headers(self, key):
"""Remove a specific parameter.
:param key: (str) Key of the header parameters.
:param key: Key of the header parameters.
:type key: str
"""
self.headers.pop(key, None)
def raw_get(self, path, **kwargs):
"""Submit get request to the path.
:param path: (str) Path for request.
:param path: Path for request.
:type path: str
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
"""
try:
return self._s.get(
@ -163,10 +207,15 @@ class ConnectionManager(object):
def raw_post(self, path, data, **kwargs):
"""Submit post request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
"""
try:
return self._s.post(
@ -183,10 +232,15 @@ class ConnectionManager(object):
def raw_put(self, path, data, **kwargs):
"""Submit put request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
"""
try:
return self._s.put(
@ -200,19 +254,24 @@ class ConnectionManager(object):
except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
def raw_delete(self, path, data={}, **kwargs):
def raw_delete(self, path, data=None, **kwargs):
"""Submit delete request to the path.
:param path: (str) Path for request.
:param data: (dict) Payload for request.
:param path: Path for request.
:type path: str
:param data: Payload for request.
:type data: dict | None
:param kwargs: Additional arguments
:type kwargs: dict
:returns: Response the request.
:raises: HttpError Can't connect to server.
:rtype: Response
:raises KeycloakConnectionError: HttpError Can't connect to server.
"""
try:
return self._s.delete(
urljoin(self.base_url, path),
params=kwargs,
data=data,
data=data or dict(),
headers=self.headers,
timeout=self.timeout,
verify=self.verify,

34
src/keycloak/exceptions.py

@ -21,7 +21,7 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Keycloak custom exeptions module."""
"""Keycloak custom exceptions module."""
import requests
@ -36,7 +36,15 @@ class KeycloakError(Exception):
"""
def __init__(self, error_message="", response_code=None, response_body=None):
"""Init method."""
"""Init method.
:param error_message: The error message
:type error_message: str
:param response_code: The code of the response
:type response_code: int
:param response_body: Body of the response
:type response_body: bytes
"""
Exception.__init__(self, error_message)
self.response_code = response_code
@ -44,7 +52,11 @@ class KeycloakError(Exception):
self.error_message = error_message
def __str__(self):
"""Str method."""
"""Str method.
:returns: String representation of the object
:rtype: str
"""
if self.response_code is not None:
return "{0}: {1}".format(self.response_code, self.error_message)
else:
@ -136,7 +148,21 @@ class PermissionDefinitionError(Exception):
def raise_error_from_response(response, error, expected_codes=None, skip_exists=False):
"""Raise an exception for the response."""
"""Raise an exception for the response.
:param response: The response object
:type response: Response
:param error: Error object to raise
:type error: dict or Exception
:param expected_codes: Set of expected codes, which should not raise the exception
:type expected_codes: Sequence[int]
:param skip_exists: Indicates whether the response on already existing object should be ignored
:type skip_exists: bool
:returns: Content of the response message
:type: bytes or dict
:raises KeycloakError: In case of unexpected status codes
""" # noqa: DAR401,DAR402
if expected_codes is None:
expected_codes = [200, 201, 204]

767
src/keycloak/keycloak_admin.py
File diff suppressed because it is too large
View File

220
src/keycloak/keycloak_openid.py

@ -81,7 +81,25 @@ class KeycloakOpenID:
proxies=None,
timeout=60,
):
"""Init method."""
"""Init method.
:param server_url: Keycloak server url
:type server_url: str
:param client_id: client id
:type client_id: str
:param realm_name: realm name
:type realm_name: str
:param client_secret_key: client secret key
:type client_secret_key: str
:param verify: True if want check connection SSL
:type verify: bool
:param custom_headers: dict of custom header to pass to each HTML request
:type custom_headers: dict
:param proxies: dict of proxies to sent the request by.
:type proxies: dict
:param timeout: connection timeout in seconds
:type timeout: int
"""
self.client_id = client_id
self.client_secret_key = client_secret_key
self.realm_name = realm_name
@ -94,7 +112,11 @@ class KeycloakOpenID:
@property
def client_id(self):
"""Get client id."""
"""Get client id.
:returns: Client id
:rtype: str
"""
return self._client_id
@client_id.setter
@ -103,7 +125,11 @@ class KeycloakOpenID:
@property
def client_secret_key(self):
"""Get the client secret key."""
"""Get the client secret key.
:returns: Client secret key
:rtype: str
"""
return self._client_secret_key
@client_secret_key.setter
@ -112,7 +138,11 @@ class KeycloakOpenID:
@property
def realm_name(self):
"""Get the realm name."""
"""Get the realm name.
:returns: Realm name
:rtype: str
"""
return self._realm_name
@realm_name.setter
@ -121,7 +151,11 @@ class KeycloakOpenID:
@property
def connection(self):
"""Get connection."""
"""Get connection.
:returns: Connection manager object
:rtype: ConnectionManager
"""
return self._connection
@connection.setter
@ -130,7 +164,11 @@ class KeycloakOpenID:
@property
def authorization(self):
"""Get authorization."""
"""Get authorization.
:returns: The authorization manager
:rtype: Authorization
"""
return self._authorization
@authorization.setter
@ -140,8 +178,10 @@ class KeycloakOpenID:
def _add_secret_key(self, payload):
"""Add secret key if exists.
:param payload:
:return:
:param payload: Payload
:type payload: dict
:returns: Payload with the secret key
:rtype: dict
"""
if self.client_secret_key:
payload.update({"client_secret": self.client_secret_key})
@ -151,18 +191,24 @@ class KeycloakOpenID:
def _build_name_role(self, role):
"""Build name of a role.
:param role:
:return:
:param role: Role name
:type role: str
:returns: Role path
:rtype: str
"""
return self.client_id + "/" + role
def _token_info(self, token, method_token_info, **kwargs):
"""Getter for the token data.
:param token:
:param method_token_info:
:param kwargs:
:return:
:param token: Token
:type token: str
:param method_token_info: Token info method to use
:type method_token_info: str
:param kwargs: Additional keyword arguments
:type kwargs: dict
:returns: Token info
:rtype: dict
"""
if method_token_info == "introspect":
token_info = self.introspect(token)
@ -178,7 +224,8 @@ class KeycloakOpenID:
endpoint. It lists endpoints and other configuration options relevant to
the OpenID Connect implementation in Keycloak.
:return It lists endpoints and other configuration options relevant.
:returns: It lists endpoints and other configuration options relevant
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path))
@ -190,9 +237,9 @@ class KeycloakOpenID:
:param redirect_uri: Redirect url to receive oauth code
:type redirect_uri: str
:param scope: Scope of authorization request, split with the blank space
:type: scope: str
:type scope: str
:param state: State will be returned to the redirect_uri
:type: str
:type state: str
:returns: Authorization URL Full Build
:rtype: str
"""
@ -224,13 +271,22 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param username:
:param password:
:param grant_type:
:param code:
:param redirect_uri:
:param totp:
:return:
:param username: Username
:type username: str
:param password: Password
:type password: str
:param grant_type: Grant type
:type grant_type: str
:param code: Code
:type code: str
:param redirect_uri: Redirect URI
:type redirect_uri: str
:param totp: Time-based one-time password
:type totp: int
:param extra: Additional extra arguments
:type extra: dict
:returns: Keycloak token
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
payload = {
@ -261,9 +317,12 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param refresh_token:
:param grant_type:
:return:
:param refresh_token: Refresh token from Keycloak
:type refresh_token: str
:param grant_type: Grant type
:type grant_type: str
:returns: New token
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
payload = {
@ -289,13 +348,20 @@ class KeycloakOpenID:
Use a token to obtain an entirely different token. See
https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange
:param token:
:param client_id:
:param audience:
:param subject:
:param requested_token_type:
:param scope:
:return:
:param token: Access token
:type token: str
:param client_id: Client id
:type client_id: str
:param audience: Audience
:type audience: str
:param subject: Subject
:type subject: str
:param requested_token_type: Token type specification
:type requested_token_type: str
:param scope: Scope
:type scope: str
:returns: Exchanged token
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
payload = {
@ -319,8 +385,10 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
:param token:
:return:
:param token: Access token
:type token: str
:returns: Userinfo object
:rtype: dict
"""
self.connection.add_param_headers("Authorization", "Bearer " + token)
params_path = {"realm-name": self.realm_name}
@ -330,8 +398,10 @@ class KeycloakOpenID:
def logout(self, refresh_token):
"""Log out the authenticated user.
:param refresh_token:
:return:
:param refresh_token: Refresh token from Keycloak
:type refresh_token: str
:returns: Keycloak server response
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
payload = {"client_id": self.client_id, "refresh_token": refresh_token}
@ -348,7 +418,8 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7517
:return:
:returns: Certificates
:rtype: dict
"""
params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_CERTS.format(**params_path))
@ -359,7 +430,8 @@ class KeycloakOpenID:
The public key is exposed by the realm page directly.
:return:
:returns: The public key
:rtype: str
"""
params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_REALM.format(**params_path))
@ -374,7 +446,12 @@ class KeycloakOpenID:
authorization policies associated with the resources being requested. With an RPT,
client applications can gain access to protected resources at the resource server.
:return:
:param token: Access token
:type token: str
:param resource_server_id: Resource server ID
:type resource_server_id: str
:returns: Entitlements
:rtype: dict
"""
self.connection.add_param_headers("Authorization", "Bearer " + token)
params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id}
@ -393,11 +470,16 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7662
:param token:
:param rpt:
:param token_type_hint:
:param token: Access token
:type token: str
:param rpt: Requesting party token
:type rpt: str
:param token_type_hint: Token type hint
:type token_type_hint: str
:return:
:returns: Token info
:rtype: dict
:raises KeycloakRPTNotFound: In case of RPT not specified
"""
params_path = {"realm-name": self.realm_name}
payload = {"client_id": self.client_id, "token": token}
@ -426,10 +508,16 @@ class KeycloakOpenID:
https://tools.ietf.org/html/rfc7517
:param token:
:param key:
:param algorithms:
:return:
:param token: Keycloak token
:type token: str
:param key: Decode key
:type key: str
:param algorithms: Algorithms to use for decoding
:type algorithms: list[str]
:param kwargs: Keyword arguments
:type kwargs: dict
:returns: Decoded token
:rtype: dict
"""
return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs)
@ -437,7 +525,7 @@ class KeycloakOpenID:
"""Load Keycloak settings (authorization).
:param path: settings file (json)
:return:
:type path: str
"""
with open(path, "r") as fp:
authorization_json = json.load(fp)
@ -447,8 +535,16 @@ class KeycloakOpenID:
def get_policies(self, token, method_token_info="introspect", **kwargs):
"""Get policies by user token.
:param token: user token
:return: policies list
:param token: User token
:type token: str
:param method_token_info: Method for token info decoding
:type method_token_info: str
:param kwargs: Additional keyword arguments
:type kwargs: dict
:return: Policies
:rtype: dict
:raises KeycloakAuthorizationConfigError: In case of bad authorization configuration
:raises KeycloakInvalidTokenError: In case of bad token
"""
if not self.authorization.policies:
raise KeycloakAuthorizationConfigError(
@ -478,9 +574,15 @@ class KeycloakOpenID:
"""Get permission by user token.
:param token: user token
:type token: str
:param method_token_info: Decode token method
:type method_token_info: str
:param kwargs: parameters for decode
:return: permissions list
:type kwargs: dict
:returns: permissions list
:rtype: list
:raises KeycloakAuthorizationConfigError: In case of bad authorization configuration
:raises KeycloakInvalidTokenError: In case of bad token
"""
if not self.authorization.policies:
raise KeycloakAuthorizationConfigError(
@ -515,8 +617,11 @@ class KeycloakOpenID:
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
:param token: user token
:type token: str
:param permissions: list of uma permissions list(resource:scope) requested by the user
:return: permissions list
:type permissions: str
:returns: Keycloak server response
:rtype: dict
"""
permission = build_permission_param(permissions)
@ -536,8 +641,13 @@ class KeycloakOpenID:
"""Determine whether user has uma permissions with specified user token.
:param token: user token
:type token: str
:param permissions: list of uma permissions (resource:scope)
:return: auth status
:type permissions: str
:return: Authentication status
:rtype: AuthStatus
:raises KeycloakAuthenticationError: In case of failed authentication
:raises KeycloakPostError: In case of failed request to Keycloak
"""
needed = build_permission_param(permissions)
try:

96
src/keycloak/uma_permissions.py

@ -48,7 +48,16 @@ class UMAPermission:
"""
def __init__(self, permission=None, resource="", scope=""):
"""Init method."""
"""Init method.
:param permission: Permission
:type permission: UMAPermission
:param resource: Resource
:type resource: str
:param scope: Scope
:type scope: str
:raises PermissionDefinitionError: In case bad permission definition
"""
self.resource = resource
self.scope = scope
@ -63,26 +72,55 @@ class UMAPermission:
self.scope = str(permission.scope)
def __str__(self):
"""Str method."""
"""Str method.
:returns: String representation
:rtype: str
"""
scope = self.scope
if scope:
scope = "#" + scope
return "{}{}".format(self.resource, scope)
def __eq__(self, __o: object) -> bool:
"""Eq method."""
"""Eq method.
:param __o: The other object
:type __o: object
:returns: Equality boolean
:rtype: bool
"""
return str(self) == str(__o)
def __repr__(self) -> str:
"""Repr method."""
"""Repr method.
:returns: The object representation
:rtype: str
"""
return self.__str__()
def __hash__(self) -> int:
"""Hash method."""
"""Hash method.
:returns: Hash of the object
:rtype: int
"""
return hash(str(self))
def __call__(self, permission=None, resource="", scope="") -> object:
"""Call method."""
def __call__(self, permission=None, resource="", scope="") -> "UMAPermission":
"""Call method.
:param permission: Permission
:type permission: UMAPermission
:param resource: Resource
:type resource: str
:param scope: Scope
:type scope: str
:returns: The combined UMA permission
:rtype: UMAPermission
:raises PermissionDefinitionError: In case bad permission definition
"""
result_resource = self.resource
result_scope = self.scope
@ -114,7 +152,11 @@ class Resource(UMAPermission):
"""
def __init__(self, resource):
"""Init method."""
"""Init method.
:param resource: Resource
:type resource: str
"""
super().__init__(resource=resource)
@ -128,7 +170,11 @@ class Scope(UMAPermission):
"""
def __init__(self, scope):
"""Init method."""
"""Init method.
:param scope: Scope
:type scope: str
"""
super().__init__(scope=scope)
@ -147,17 +193,33 @@ class AuthStatus:
"""
def __init__(self, is_logged_in, is_authorized, missing_permissions):
"""Init method."""
"""Init method.
:param is_logged_in: Is logged in indicator
:type is_logged_in: bool
:param is_authorized: Is authorized indicator
:type is_authorized: bool
:param missing_permissions: Missing permissions
:type missing_permissions: set
"""
self.is_logged_in = is_logged_in
self.is_authorized = is_authorized
self.missing_permissions = missing_permissions
def __bool__(self):
"""Bool method."""
"""Bool method.
:returns: Boolean representation
:rtype: bool
"""
return self.is_authorized
def __repr__(self):
"""Repr method."""
"""Repr method.
:returns: The object representation
:rtype: str
"""
return (
f"AuthStatus("
f"is_authorized={self.is_authorized}, "
@ -169,11 +231,11 @@ class AuthStatus:
def build_permission_param(permissions):
"""Transform permissions to a set, so they are usable for requests.
:param permissions: either str (resource#scope),
iterable[str] (resource#scope),
dict[str,str] (resource: scope),
dict[str,iterable[str]] (resource: scopes)
:return: result bool
:param permissions: Permissions
:type permissions: str | Iterable[str] | dict[str, str] | dict[str, Iterabble[str]]
:returns: Permission parameters
:rtype: set
:raises KeycloakPermissionFormatError: In case of bad permission format
"""
if permissions is None or permissions == "":
return set()

180
tests/conftest.py

@ -35,7 +35,17 @@ class KeycloakTestEnv(object):
username: str = os.environ["KEYCLOAK_ADMIN"],
password: str = os.environ["KEYCLOAK_ADMIN_PASSWORD"],
):
"""Init method."""
"""Init method.
:param host: Hostname
:type host: str
:param port: Port
:type port: str
:param username: Admin username
:type username: str
:param password: Admin password
:type password: str
"""
self.KEYCLOAK_HOST = host
self.KEYCLOAK_PORT = port
self.KEYCLOAK_ADMIN = username
@ -43,54 +53,96 @@ class KeycloakTestEnv(object):
@property
def KEYCLOAK_HOST(self):
"""Hostname getter."""
"""Hostname getter.
:returns: Keycloak host
:rtype: str
"""
return self._KEYCLOAK_HOST
@KEYCLOAK_HOST.setter
def KEYCLOAK_HOST(self, value: str):
"""Hostname setter."""
"""Hostname setter.
:param value: Keycloak host
:type value: str
"""
self._KEYCLOAK_HOST = value
@property
def KEYCLOAK_PORT(self):
"""Port getter."""
"""Port getter.
:returns: Keycloak port
:rtype: str
"""
return self._KEYCLOAK_PORT
@KEYCLOAK_PORT.setter
def KEYCLOAK_PORT(self, value: str):
"""Port setter."""
"""Port setter.
:param value: Keycloak port
:type value: str
"""
self._KEYCLOAK_PORT = value
@property
def KEYCLOAK_ADMIN(self):
"""Admin username getter."""
"""Admin username getter.
:returns: Admin username
:rtype: str
"""
return self._KEYCLOAK_ADMIN
@KEYCLOAK_ADMIN.setter
def KEYCLOAK_ADMIN(self, value: str):
"""Admin username setter."""
"""Admin username setter.
:param value: Admin username
:type value: str
"""
self._KEYCLOAK_ADMIN = value
@property
def KEYCLOAK_ADMIN_PASSWORD(self):
"""Admin password getter."""
"""Admin password getter.
:returns: Admin password
:rtype: str
"""
return self._KEYCLOAK_ADMIN_PASSWORD
@KEYCLOAK_ADMIN_PASSWORD.setter
def KEYCLOAK_ADMIN_PASSWORD(self, value: str):
"""Admin password setter."""
"""Admin password setter.
:param value: Admin password
:type value: str
"""
self._KEYCLOAK_ADMIN_PASSWORD = value
@pytest.fixture
def env():
"""Fixture for getting the test environment configuration object."""
"""Fixture for getting the test environment configuration object.
:returns: Keycloak test environment object
:rtype: KeycloakTestEnv
"""
return KeycloakTestEnv()
@pytest.fixture
def admin(env: KeycloakTestEnv):
"""Fixture for initialized KeycloakAdmin class."""
"""Fixture for initialized KeycloakAdmin class.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:returns: Keycloak admin
:rtype: KeycloakAdmin
"""
return KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
username=env.KEYCLOAK_ADMIN,
@ -100,7 +152,17 @@ def admin(env: KeycloakTestEnv):
@pytest.fixture
def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for initialized KeycloakOpenID class."""
"""Fixture for initialized KeycloakOpenID class.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client
:rtype: KeycloakOpenID
"""
# Set the realm
admin.realm_name = realm
# Create client
@ -126,7 +188,17 @@ def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
@pytest.fixture
def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for an initialized KeycloakOpenID class and a random user credentials."""
"""Fixture for an initialized KeycloakOpenID class and a random user credentials.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client with user credentials
:rtype: Tuple[KeycloakOpenID, str, str]
"""
# Set the realm
admin.realm_name = realm
# Create client
@ -173,7 +245,17 @@ def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin)
@pytest.fixture
def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
"""Fixture for an initialized KeycloakOpenID class and a random user credentials."""
"""Fixture for an initialized KeycloakOpenID class and a random user credentials.
:param env: Keycloak test environment
:type env: KeycloakTestEnv
:param realm: Keycloak realm
:type realm: str
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak OpenID client configured as an authorization server with client credentials
:rtype: Tuple[KeycloakOpenID, str, str]
"""
# Set the realm
admin.realm_name = realm
# Create client
@ -229,7 +311,13 @@ def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: Keycloak
@pytest.fixture
def realm(admin: KeycloakAdmin) -> str:
"""Fixture for a new random realm."""
"""Fixture for a new random realm.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:yields: Keycloak realm
:rtype: str
"""
realm_name = str(uuid.uuid4())
admin.create_realm(payload={"realm": realm_name, "enabled": True})
yield realm_name
@ -238,7 +326,15 @@ def realm(admin: KeycloakAdmin) -> str:
@pytest.fixture
def user(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random user."""
"""Fixture for a new random user.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak user
:rtype: str
"""
admin.realm_name = realm
username = str(uuid.uuid4())
user_id = admin.create_user(payload={"username": username, "email": f"{username}@test.test"})
@ -248,7 +344,15 @@ def user(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture
def group(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random group."""
"""Fixture for a new random group.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak group
:rtype: str
"""
admin.realm_name = realm
group_name = str(uuid.uuid4())
group_id = admin.create_group(payload={"name": group_name})
@ -258,7 +362,15 @@ def group(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture
def client(admin: KeycloakAdmin, realm: str) -> str:
"""Fixture for a new random client."""
"""Fixture for a new random client.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:yields: Keycloak client id
:rtype: str
"""
admin.realm_name = realm
client = str(uuid.uuid4())
client_id = admin.create_client(payload={"name": client, "clientId": client})
@ -268,7 +380,17 @@ def client(admin: KeycloakAdmin, realm: str) -> str:
@pytest.fixture
def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str:
"""Fixture for a new random client role."""
"""Fixture for a new random client role.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:yields: Keycloak client role
:rtype: str
"""
admin.realm_name = realm
role = str(uuid.uuid4())
admin.create_client_role(client, {"name": role, "composite": False})
@ -278,7 +400,19 @@ def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str:
@pytest.fixture
def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_role: str) -> str:
"""Fixture for a new random composite client role."""
"""Fixture for a new random composite client role.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param client_role: Keycloak client role
:type client_role: str
:yields: Composite client role
:rtype: str
"""
admin.realm_name = realm
role = str(uuid.uuid4())
admin.create_client_role(client, {"name": role, "composite": True})
@ -290,7 +424,11 @@ def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_
@pytest.fixture
def selfsigned_cert():
"""Generate self signed certificate for a hostname, and optional IP addresses."""
"""Generate self signed certificate for a hostname, and optional IP addresses.
:returns: Selfsigned certificate
:rtype: Tuple[str, str]
"""
hostname = "testcert"
ip_addresses = None
key = None

297
tests/test_keycloak_admin.py

@ -22,7 +22,11 @@ def test_keycloak_version():
def test_keycloak_admin_bad_init(env):
"""Test keycloak admin bad init."""
"""Test keycloak admin bad init.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
with pytest.raises(TypeError) as err:
KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
@ -43,7 +47,11 @@ def test_keycloak_admin_bad_init(env):
def test_keycloak_admin_init(env):
"""Test keycloak admin init."""
"""Test keycloak admin init.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
admin = KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
username=env.KEYCLOAK_ADMIN,
@ -118,7 +126,11 @@ def test_keycloak_admin_init(env):
def test_realms(admin: KeycloakAdmin):
"""Test realms."""
"""Test realms.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
# Get realms
realms = admin.get_realms()
assert len(realms) == 1, realms
@ -183,7 +195,13 @@ def test_realms(admin: KeycloakAdmin):
def test_import_export_realms(admin: KeycloakAdmin, realm: str):
"""Test import and export of realms."""
"""Test import and export of realms.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
realm_export = admin.export_realm(export_clients=True, export_groups_and_role=True)
@ -201,7 +219,13 @@ def test_import_export_realms(admin: KeycloakAdmin, realm: str):
def test_users(admin: KeycloakAdmin, realm: str):
"""Test users."""
"""Test users.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Check no users present
@ -293,7 +317,13 @@ def test_users(admin: KeycloakAdmin, realm: str):
def test_users_pagination(admin: KeycloakAdmin, realm: str):
"""Test user pagination."""
"""Test user pagination.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
for ind in range(admin.PAGE_SIZE + 50):
@ -311,7 +341,13 @@ def test_users_pagination(admin: KeycloakAdmin, realm: str):
def test_idps(admin: KeycloakAdmin, realm: str):
"""Test IDPs."""
"""Test IDPs.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Create IDP
@ -383,7 +419,13 @@ def test_idps(admin: KeycloakAdmin, realm: str):
def test_user_credentials(admin: KeycloakAdmin, user: str):
"""Test user credentials."""
"""Test user credentials.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
res = admin.set_user_password(user_id=user, password="booya", temporary=True)
assert res == dict(), res
@ -411,7 +453,13 @@ def test_user_credentials(admin: KeycloakAdmin, user: str):
def test_social_logins(admin: KeycloakAdmin, user: str):
"""Test social logins."""
"""Test social logins.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
res = admin.add_user_social_login(
user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test"
)
@ -451,7 +499,11 @@ def test_social_logins(admin: KeycloakAdmin, user: str):
def test_server_info(admin: KeycloakAdmin):
"""Test server info."""
"""Test server info.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
info = admin.get_server_info()
assert set(info.keys()) == {
"systemInfo",
@ -471,7 +523,13 @@ def test_server_info(admin: KeycloakAdmin):
def test_groups(admin: KeycloakAdmin, user: str):
"""Test groups."""
"""Test groups.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
# Test get groups
groups = admin.get_groups()
assert len(groups) == 0
@ -615,7 +673,13 @@ def test_groups(admin: KeycloakAdmin, user: str):
def test_clients(admin: KeycloakAdmin, realm: str):
"""Test clients."""
"""Test clients.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get clients
@ -875,7 +939,13 @@ def test_clients(admin: KeycloakAdmin, realm: str):
def test_realm_roles(admin: KeycloakAdmin, realm: str):
"""Test realm roles."""
"""Test realm roles.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get realm roles
@ -1046,7 +1116,21 @@ def test_role_attributes(
includes_attributes: bool,
testcase: str,
):
"""Test getting role attributes for bulk calls."""
"""Test getting role attributes for bulk calls.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param arg_brief_repr: Brief representation
:type arg_brief_repr: dict
:param includes_attributes: Indicator whether to include attributes
:type includes_attributes: bool
:param testcase: Test case
:type testcase: str
"""
# setup
attribute_role = "test-realm-role-w-attr"
test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]}
@ -1088,7 +1172,13 @@ def test_role_attributes(
def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str):
"""Test client realm roles."""
"""Test client realm roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get realm roles
@ -1146,7 +1236,15 @@ def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str):
def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str):
"""Test client assignment of other client roles."""
"""Test client assignment of other client roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
"""
admin.realm_name = realm
client_id = admin.create_client(
@ -1202,7 +1300,13 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str
def test_client_roles(admin: KeycloakAdmin, client: str):
"""Test client roles."""
"""Test client roles.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param client: Keycloak client
:type client: str
"""
# Test get client roles
res = admin.get_client_roles(client_id=client)
assert len(res) == 0
@ -1365,7 +1469,14 @@ def test_client_roles(admin: KeycloakAdmin, client: str):
def test_enable_token_exchange(admin: KeycloakAdmin, realm: str):
"""Test enable token exchange."""
"""Test enable token exchange.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:raises AssertionError: In case of bad configuration
"""
# Test enabling token exchange between two confidential clients
admin.realm_name = realm
@ -1454,7 +1565,13 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str):
def test_email(admin: KeycloakAdmin, user: str):
"""Test email."""
"""Test email.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param user: Keycloak user
:type user: str
"""
# Emails will fail as we don't have SMTP test setup
with pytest.raises(KeycloakPutError) as err:
admin.send_update_account(user_id=user, payload=dict())
@ -1467,7 +1584,11 @@ def test_email(admin: KeycloakAdmin, user: str):
def test_get_sessions(admin: KeycloakAdmin):
"""Test get sessions."""
"""Test get sessions.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.username))
assert len(sessions) >= 1
with pytest.raises(KeycloakGetError) as err:
@ -1476,7 +1597,13 @@ def test_get_sessions(admin: KeycloakAdmin):
def test_get_client_installation_provider(admin: KeycloakAdmin, client: str):
"""Test get client installation provider."""
"""Test get client installation provider.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param client: Keycloak client
:type client: str
"""
with pytest.raises(KeycloakGetError) as err:
admin.get_client_installation_provider(client_id=client, provider_id="bad")
assert err.match('404: b\'{"error":"Unknown Provider"}\'')
@ -1495,7 +1622,13 @@ def test_get_client_installation_provider(admin: KeycloakAdmin, client: str):
def test_auth_flows(admin: KeycloakAdmin, realm: str):
"""Test auth flows."""
"""Test auth flows.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
res = admin.get_authentication_flows()
@ -1642,7 +1775,13 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str):
def test_authentication_configs(admin: KeycloakAdmin, realm: str):
"""Test authentication configs."""
"""Test authentication configs.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test list of auth providers
@ -1674,7 +1813,13 @@ def test_authentication_configs(admin: KeycloakAdmin, realm: str):
def test_sync_users(admin: KeycloakAdmin, realm: str):
"""Test sync users."""
"""Test sync users.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Only testing the error message
@ -1684,7 +1829,13 @@ def test_sync_users(admin: KeycloakAdmin, realm: str):
def test_client_scopes(admin: KeycloakAdmin, realm: str):
"""Test client scopes."""
"""Test client scopes.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get client scopes
@ -1822,7 +1973,13 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str):
def test_components(admin: KeycloakAdmin, realm: str):
"""Test components."""
"""Test components.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get components
@ -1873,7 +2030,13 @@ def test_components(admin: KeycloakAdmin, realm: str):
def test_keys(admin: KeycloakAdmin, realm: str):
"""Test keys."""
"""Test keys.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
assert set(admin.get_keys()["active"].keys()) == {"AES", "HS256", "RS256", "RSA-OAEP"}
assert {k["algorithm"] for k in admin.get_keys()["keys"]} == {
@ -1885,7 +2048,13 @@ def test_keys(admin: KeycloakAdmin, realm: str):
def test_events(admin: KeycloakAdmin, realm: str):
"""Test events."""
"""Test events.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
events = admin.get_events()
@ -1905,7 +2074,13 @@ def test_events(admin: KeycloakAdmin, realm: str):
def test_auto_refresh(admin: KeycloakAdmin, realm: str):
"""Test auto refresh token."""
"""Test auto refresh token.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
# Test get refresh
admin.auto_refresh_token = list()
admin.connection = ConnectionManager(
@ -1989,7 +2164,13 @@ def test_auto_refresh(admin: KeycloakAdmin, realm: str):
def test_get_required_actions(admin: KeycloakAdmin, realm: str):
"""Test requried actions."""
"""Test required actions.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
ractions = admin.get_required_actions()
assert isinstance(ractions, list)
@ -2007,7 +2188,13 @@ def test_get_required_actions(admin: KeycloakAdmin, realm: str):
def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str):
"""Test get required action by alias."""
"""Test get required action by alias.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
ractions = admin.get_required_actions()
ra = admin.get_required_action_by_alias("UPDATE_PASSWORD")
@ -2017,7 +2204,13 @@ def test_get_required_action_by_alias(admin: KeycloakAdmin, realm: str):
def test_update_required_action(admin: KeycloakAdmin, realm: str):
"""Test update required action."""
"""Test update required action.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
ra = admin.get_required_action_by_alias("UPDATE_PASSWORD")
old = copy.deepcopy(ra)
@ -2031,7 +2224,19 @@ def test_update_required_action(admin: KeycloakAdmin, realm: str):
def test_get_composite_client_roles_of_group(
admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str
):
"""Test get composite client roles of group."""
"""Test get composite client roles of group.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param group: Keycloak group
:type group: str
:param composite_client_role: Composite client role
:type composite_client_role: str
"""
admin.realm_name = realm
role = admin.get_client_role(client, composite_client_role)
admin.assign_group_client_roles(group_id=group, client_id=client, roles=[role])
@ -2042,7 +2247,19 @@ def test_get_composite_client_roles_of_group(
def test_get_role_client_level_children(
admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str
):
"""Test get children of composite client role."""
"""Test get children of composite client role.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param composite_client_role: Composite client role
:type composite_client_role: str
:param client_role: Client role
:type client_role: str
"""
admin.realm_name = realm
child = admin.get_client_role(client, client_role)
parent = admin.get_client_role(client, composite_client_role)
@ -2051,7 +2268,17 @@ def test_get_role_client_level_children(
def test_upload_certificate(admin: KeycloakAdmin, realm: str, client: str, selfsigned_cert: tuple):
"""Test upload certificate."""
"""Test upload certificate.
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param selfsigned_cert: Selfsigned certificates
:type selfsigned_cert: tuple
"""
admin.realm_name = realm
cert, _ = selfsigned_cert
cert = cert.decode("utf-8").strip()

110
tests/test_keycloak_openid.py

@ -22,7 +22,11 @@ from keycloak.keycloak_openid import KeycloakOpenID
def test_keycloak_openid_init(env):
"""Test KeycloakOpenId's init method."""
"""Test KeycloakOpenId's init method.
:param env: Environment fixture
:type env: KeycloakTestEnv
"""
oid = KeycloakOpenID(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
realm_name="master",
@ -37,7 +41,11 @@ def test_keycloak_openid_init(env):
def test_well_known(oid: KeycloakOpenID):
"""Test the well_known method."""
"""Test the well_known method.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
res = oid.well_known()
assert res is not None
assert res != dict()
@ -100,7 +108,13 @@ def test_well_known(oid: KeycloakOpenID):
def test_auth_url(env, oid: KeycloakOpenID):
"""Test the auth_url method."""
"""Test the auth_url method.
:param env: Environment fixture
:type env: KeycloakTestEnv
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
res = oid.auth_url(redirect_uri="http://test.test/*")
assert (
res
@ -111,7 +125,11 @@ def test_auth_url(env, oid: KeycloakOpenID):
def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test the token method."""
"""Test the token method.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password)
assert token == {
@ -155,7 +173,13 @@ def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_exchange_token(
oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
):
"""Test the exchange token method."""
"""Test the exchange token method.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
# Verify existing user
oid, username, password = oid_with_credentials
@ -197,7 +221,11 @@ def test_exchange_token(
def test_logout(oid_with_credentials):
"""Test logout."""
"""Test logout.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password)
@ -209,19 +237,34 @@ def test_logout(oid_with_credentials):
def test_certs(oid: KeycloakOpenID):
"""Test certificates."""
"""Test certificates.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
assert len(oid.certs()["keys"]) == 2
def test_public_key(oid: KeycloakOpenID):
"""Test public key."""
"""Test public key.
:param oid: Keycloak OpenID client
:type oid: KeycloakOpenID
"""
assert oid.public_key() is not None
def test_entitlement(
oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
):
"""Test entitlement."""
"""Test entitlement.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password)
resource_server_id = admin.get_client_authz_resources(
@ -233,7 +276,11 @@ def test_entitlement(
def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test introspect."""
"""Test introspect.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password)
@ -247,7 +294,11 @@ def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
"""Test decode token."""
"""Test decode token.
:param oid_with_credentials: Keycloak OpenID client with pre-configured user credentials
:type oid_with_credentials: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials
token = oid.token(username=username, password=password)
@ -262,7 +313,12 @@ def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]):
def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test load authorization config."""
"""Test load authorization config.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz
oid.load_authorization_config(path="tests/data/authz_settings.json")
@ -277,7 +333,12 @@ def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpe
def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test get policies."""
"""Test get policies.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password)
@ -310,7 +371,12 @@ def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str
def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test get policies."""
"""Test get policies.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password)
@ -354,7 +420,12 @@ def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str,
def test_uma_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]):
"""Test UMA permissions."""
"""Test UMA permissions.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
"""
oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password)
@ -365,7 +436,14 @@ def test_uma_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str,
def test_has_uma_access(
oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
):
"""Test has UMA access."""
"""Test has UMA access.
:param oid_with_credentials_authz: Keycloak OpenID client configured as an authorization
server with client credentials
:type oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]
:param admin: Keycloak Admin client
:type admin: KeycloakAdmin
"""
oid, username, password = oid_with_credentials_authz
token = oid.token(username=username, password=password)

7
tox.ini

@ -13,6 +13,7 @@ commands =
black --check --diff src/keycloak tests docs
isort -c --df src/keycloak tests docs
flake8 src/keycloak tests docs
codespell src tests docs
[testenv:apply-check]
commands =
@ -44,9 +45,13 @@ commands =
setenv = file|tox.env
passenv = CONTAINER_HOST
commands =
cz changelog --incremental
cz changelog
[flake8]
max-line-length = 99
docstring-convention = all
ignore = D203, D213, W503
docstring_style = sphinx
[darglint]
enable = DAR104
Loading…
Cancel
Save