diff --git a/docs/source/conf.py b/docs/source/conf.py index d3cb236..a6d4cf1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # python-keycloak documentation build configuration file, created by # sphinx-quickstart on Tue Aug 15 11:02:59 2017. @@ -169,7 +168,7 @@ latex_documents = [ "python-keycloak Documentation", "Marcos Pereira", "manual", - ) + ), ] @@ -194,5 +193,5 @@ texinfo_documents = [ "python-keycloak", "One line description of project.", "Miscellaneous", - ) + ), ] diff --git a/poetry.lock b/poetry.lock index 2b37d7f..fde33b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,16 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] [[package]] name = "alabaster" @@ -6,6 +18,7 @@ version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -13,13 +26,14 @@ files = [ [[package]] name = "anyio" -version = "4.7.0" +version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, - {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] @@ -30,18 +44,19 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] name = "argcomplete" -version = "3.5.2" +version = "3.5.3" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "argcomplete-3.5.2-py3-none-any.whl", hash = "sha256:036d020d79048a5d525bc63880d7a4b8d1668566b8a76daf1144c0bbe0f63472"}, - {file = "argcomplete-3.5.2.tar.gz", hash = "sha256:23146ed7ac4403b70bd6026402468942ceba34a6732255b9edf5b7354f68a6bb"}, + {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, + {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, ] [package.extras] @@ -49,13 +64,14 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "astroid" -version = "3.3.6" +version = "3.3.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" +groups = ["docs"] files = [ - {file = "astroid-3.3.6-py3-none-any.whl", hash = "sha256:db676dc4f3ae6bfe31cda227dc60e03438378d7a896aec57422c95634e8d722f"}, - {file = "astroid-3.3.6.tar.gz", hash = "sha256:6aaea045f938c735ead292204afdb977a36e989522b7833ef6fea94de743f442"}, + {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, + {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, ] [package.dependencies] @@ -67,6 +83,7 @@ version = "0.2.2" description = "Python decorator for async properties." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"}, {file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"}, @@ -78,6 +95,7 @@ version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, @@ -92,6 +110,8 @@ version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, @@ -101,72 +121,28 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] -[[package]] -name = "black" -version = "24.10.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -files = [ - {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, - {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, - {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, - {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, - {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, - {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, - {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, - {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, - {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, - {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, - {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, - {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, - {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, - {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, - {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, - {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, - {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, - {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, - {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, - {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, - {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, - {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "cachetools" -version = "5.5.0" +version = "5.5.1" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, - {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, + {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, + {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, ] [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev", "docs"] files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -175,6 +151,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -254,6 +232,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -265,6 +244,7 @@ version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -272,141 +252,116 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +groups = ["main", "dev", "docs"] +files = [ + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "codespell" -version = "2.3.0" -description = "Codespell" +version = "2.4.0" +description = "Fix common misspellings in text files" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, - {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, + {file = "codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d"}, + {file = "codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58"}, ] [package.extras] @@ -421,10 +376,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {docs = "sys_platform == \"win32\""} [[package]] name = "commitizen" @@ -432,6 +389,7 @@ version = "4.1.0" description = "Python commitizen client tool" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "commitizen-4.1.0-py3-none-any.whl", hash = "sha256:2e6c5fbd442cab4bcc5a04bc86ef2196ef84bcf611317d6c596e87f5bb4c09f5"}, {file = "commitizen-4.1.0.tar.gz", hash = "sha256:4f2d9400ec411aec1c738d4c63fc7fd5807cd6ddf6be970869e03e68b88ff718"}, @@ -457,6 +415,7 @@ version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -467,73 +426,74 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "7.6.9" +version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" -files = [ - {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, - {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, - {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, - {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, - {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, - {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, - {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, - {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, - {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, - {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, - {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, - {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, - {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, - {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, - {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, - {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, - {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, - {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, - {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, - {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, - {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, - {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, - {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, - {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, - {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, - {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, - {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, - {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, - {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, - {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, - {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, - {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, - {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, +groups = ["dev"] +files = [ + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, ] [package.dependencies] @@ -548,6 +508,7 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -597,6 +558,7 @@ version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." optional = false python-versions = ">=3.6,<4.0" +groups = ["dev"] files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, @@ -608,6 +570,7 @@ version = "0.6.2" description = "Minimal, easy-to-use, declarative cli tool" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed"}, {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, @@ -619,6 +582,7 @@ version = "2.1.0" description = "A library to handle automated deprecations" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, @@ -633,6 +597,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -644,6 +609,7 @@ version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, @@ -655,6 +621,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -665,57 +633,28 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.16.1" +version = "3.17.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2)"] -[[package]] -name = "flake8" -version = "7.1.1" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, - {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.12.0,<2.13.0" -pyflakes = ">=3.2.0,<3.3.0" - -[[package]] -name = "flake8-docstrings" -version = "1.7.0" -description = "Extension for flake8 which uses pydocstyle to check docstrings" -optional = false -python-versions = ">=3.7" -files = [ - {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, - {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, -] - -[package.dependencies] -flake8 = ">=3" -pydocstyle = ">=2.1" - [[package]] name = "freezegun" version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -730,6 +669,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -741,6 +681,7 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -762,6 +703,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -780,15 +722,36 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "id" +version = "1.5.0" +description = "A tool for generating OIDC identities" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"}, + {file = "id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"}, +] + +[package.dependencies] +requests = "*" + +[package.extras] +dev = ["build", "bump (>=1.3.2)", "id[lint,test]"] +lint = ["bandit", "interrogate", "mypy", "ruff (<0.8.2)", "types-requests"] +test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] + [[package]] name = "identify" -version = "2.6.3" +version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, - {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, + {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, + {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, ] [package.extras] @@ -800,6 +763,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev", "docs"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -814,6 +778,7 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["docs"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -821,14 +786,16 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] +markers = {dev = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\" or python_version < \"3.10\"", docs = "python_version < \"3.10\""} [package.dependencies] zipp = ">=3.20" @@ -839,7 +806,7 @@ cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -848,31 +815,20 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "jaraco-classes" version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -891,6 +847,8 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -909,6 +867,8 @@ version = "4.1.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, @@ -931,6 +891,8 @@ version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, @@ -942,13 +904,14 @@ trio = ["async_generator", "trio"] [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -963,6 +926,7 @@ version = "1.5.6" description = "Implementation of JOSE Web standards" optional = false python-versions = ">= 3.8" +groups = ["main"] files = [ {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, @@ -974,17 +938,19 @@ typing-extensions = ">=4.5.0" [[package]] name = "keyring" -version = "25.5.0" +version = "25.6.0" description = "Store and access your passwords safely." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ - {file = "keyring-25.5.0-py3-none-any.whl", hash = "sha256:e67f8ac32b04be4714b42fe84ce7dad9c40985b9ca827c592cc303e7c26d9741"}, - {file = "keyring-25.5.0.tar.gz", hash = "sha256:4c753b3ec91717fe713c4edd522d625889d8973a349b0e582622f49766de58e6"}, + {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, + {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, ] [package.dependencies] -importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} "jaraco.classes" = "*" "jaraco.context" = "*" "jaraco.functools" = "*" @@ -1007,6 +973,7 @@ version = "0.3.3.post2" description = "Markdown and reStructuredText in a single file." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "m2r2-0.3.3.post2-py3-none-any.whl", hash = "sha256:86157721eb6eabcd54d4eea7195890cc58fa6188b8d0abea633383cfbb5e11e3"}, {file = "m2r2-0.3.3.post2.tar.gz", hash = "sha256:e62bcb0e74b3ce19cda0737a0556b04cf4a43b785072fcef474558f2c1482ca8"}, @@ -1022,6 +989,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1046,6 +1014,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1110,23 +1079,13 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1138,6 +1097,7 @@ version = "0.8.4" description = "The fastest markdown parser in pure Python" optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, @@ -1145,57 +1105,49 @@ files = [ [[package]] name = "more-itertools" -version = "10.5.0" +version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.8" -files = [ - {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, - {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, + {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, ] [[package]] name = "nh3" -version = "0.2.19" -description = "Python bindings to the ammonia HTML sanitization library." +version = "0.2.20" +description = "Python binding to Ammonia HTML sanitizer Rust crate" optional = false -python-versions = "*" -files = [ - {file = "nh3-0.2.19-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ec9c8bf86e397cb88c560361f60fdce478b5edb8b93f04ead419b72fbe937ea6"}, - {file = "nh3-0.2.19-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0adf00e2b2026fa10a42537b60d161e516f206781c7515e4e97e09f72a8c5d0"}, - {file = "nh3-0.2.19-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3805161c4e12088bd74752ba69630e915bc30fe666034f47217a2f16b16efc37"}, - {file = "nh3-0.2.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3dedd7858a21312f7675841529941035a2ac91057db13402c8fe907aa19205a"}, - {file = "nh3-0.2.19-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:0b6820fc64f2ff7ef3e7253a093c946a87865c877b3889149a6d21d322ed8dbd"}, - {file = "nh3-0.2.19-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:833b3b5f1783ce95834a13030300cea00cbdfd64ea29260d01af9c4821da0aa9"}, - {file = "nh3-0.2.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5d4f5e2189861b352b73acb803b5f4bb409c2f36275d22717e27d4e0c217ae55"}, - {file = "nh3-0.2.19-cp313-cp313t-win32.whl", hash = "sha256:2b926f179eb4bce72b651bfdf76f8aa05d167b2b72bc2f3657fd319f40232adc"}, - {file = "nh3-0.2.19-cp313-cp313t-win_amd64.whl", hash = "sha256:ac536a4b5c073fdadd8f5f4889adabe1cbdae55305366fb870723c96ca7f49c3"}, - {file = "nh3-0.2.19-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c2e3f0d18cc101132fe10ab7ef5c4f41411297e639e23b64b5e888ccaad63f41"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11270b16c1b012677e3e2dd166c1aa273388776bf99a3e3677179db5097ee16a"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc483dd8d20f8f8c010783a25a84db3bebeadced92d24d34b40d687f8043ac69"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d53a4577b6123ca1d7e8483fad3e13cb7eda28913d516bd0a648c1a473aa21a9"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdb20740d24ab9f2a1341458a00a11205294e97e905de060eeab1ceca020c09c"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8325d51e47cb5b11f649d55e626d56c76041ba508cd59e0cb1cf687cc7612f1"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8eb7affc590e542fa7981ef508cd1644f62176bcd10d4429890fc629b47f0bc"}, - {file = "nh3-0.2.19-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2eb021804e9df1761abeb844bb86648d77aa118a663c82f50ea04110d87ed707"}, - {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a7b928862daddb29805a1010a0282f77f4b8b238a37b5f76bc6c0d16d930fd22"}, - {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed06ed78f6b69d57463b46a04f68f270605301e69d80756a8adf7519002de57d"}, - {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:df8eac98fec80bd6f5fd0ae27a65de14f1e1a65a76d8e2237eb695f9cd1121d9"}, - {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00810cd5275f5c3f44b9eb0e521d1a841ee2f8023622de39ffc7d88bd533d8e0"}, - {file = "nh3-0.2.19-cp38-abi3-win32.whl", hash = "sha256:7e98621856b0a911c21faa5eef8f8ea3e691526c2433f9afc2be713cb6fbdb48"}, - {file = "nh3-0.2.19-cp38-abi3-win_amd64.whl", hash = "sha256:75c7cafb840f24430b009f7368945cb5ca88b2b54bb384ebfba495f16bc9c121"}, - {file = "nh3-0.2.19.tar.gz", hash = "sha256:790056b54c068ff8dceb443eaefb696b84beff58cca6c07afd754d17692a4804"}, +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"}, + {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"}, + {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ae9cbd713524cdb81e64663d0d6aae26f678db9f2cd9db0bf162606f1f9f20c"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1f7370b4e14cc03f5ae141ef30a1caf81fa5787711f80be9081418dd9eb79d2"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:ac4d27dc836a476efffc6eb661994426b8b805c951b29c9cf2ff36bc9ad58bc5"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4fd2e9248725ebcedac3997a8d3da0d90a12a28c9179c6ba51f1658938ac30d0"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f7d564871833ddbe54df3aa59053b1110729d3a800cb7628ae8f42adb3d75208"}, + {file = "nh3-0.2.20-cp313-cp313t-win32.whl", hash = "sha256:d2a176fd4306b6f0f178a3f67fac91bd97a3a8d8fafb771c9b9ef675ba5c8886"}, + {file = "nh3-0.2.20-cp313-cp313t-win_amd64.whl", hash = "sha256:6ed834c68452a600f517dd3e1534dbfaff1f67f98899fecf139a055a25d99150"}, + {file = "nh3-0.2.20-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:76e2f603b30c02ff6456b233a83fc377dedab6a50947b04e960a6b905637b776"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:181063c581defe683bd4bb78188ac9936d208aebbc74c7f7c16b6a32ae2ebb38"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:231addb7643c952cd6d71f1c8702d703f8fe34afcb20becb3efb319a501a12d7"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1b9a8340a0aab991c68a5ca938d35ef4a8a3f4bf1b455da8855a40bee1fa0ace"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10317cd96fe4bbd4eb6b95f3920b71c902157ad44fed103fdcde43e3b8ee8be6"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8698db4c04b140800d1a1cd3067fda399e36e1e2b8fc1fe04292a907350a3e9b"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eb04b9c3deb13c3a375ea39fd4a3c00d1f92e8fb2349f25f1e3e4506751774b"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92f3f1c4f47a2c6f3ca7317b1d5ced05bd29556a75d3a4e2715652ae9d15c05d"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddefa9fd6794a87e37d05827d299d4b53a3ec6f23258101907b96029bfef138a"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ce3731c8f217685d33d9268362e5b4f770914e922bba94d368ab244a59a6c397"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:09f037c02fc2c43b211ff1523de32801dcfb0918648d8e651c36ef890f1731ec"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:813f1c8012dd64c990514b795508abb90789334f76a561fa0fd4ca32d2275330"}, + {file = "nh3-0.2.20-cp38-abi3-win32.whl", hash = "sha256:47b2946c0e13057855209daeffb45dc910bd0c55daf10190bb0b4b60e2999784"}, + {file = "nh3-0.2.20-cp38-abi3-win_amd64.whl", hash = "sha256:da87573f03084edae8eb87cfe811ec338606288f81d333c07d2a9a0b9b976c0b"}, + {file = "nh3-0.2.20.tar.gz", hash = "sha256:9705c42d7ff88a0bea546c82d7fe5e59135e3d3f057e485394f491248a1f8ed5"}, ] [[package]] @@ -1204,6 +1156,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1215,42 +1168,19 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "docs"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pkginfo" -version = "1.12.0" -description = "Query metadata from sdists / bdists / installed packages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pkginfo-1.12.0-py3-none-any.whl", hash = "sha256:dcd589c9be4da8973eceffa247733c144812759aa67eaf4bbf97016a02f39088"}, - {file = "pkginfo-1.12.0.tar.gz", hash = "sha256:8ad91a0445a036782b9366ef8b8c2c50291f83a553478ba8580c73d3215700cf"}, -] - -[package.extras] -testing = ["pytest", "pytest-cov", "wheel"] - [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1267,6 +1197,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1278,13 +1209,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "4.0.1" +version = "4.1.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, - {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, + {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, + {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, ] [package.dependencies] @@ -1296,77 +1228,42 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.36" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.8.0" +groups = ["dev"] files = [ - {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, - {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] wcwidth = "*" -[[package]] -name = "pycodestyle" -version = "2.12.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, - {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, -] - [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -[[package]] -name = "pydocstyle" -version = "6.3.0" -description = "Python docstring style checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, - {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, -] - -[package.dependencies] -snowballstemmer = ">=2.2.0" - -[package.extras] -toml = ["tomli (>=1.2.3)"] - -[[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, -] - [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -1374,22 +1271,23 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyproject-api" -version = "1.8.0" +version = "1.9.0" description = "API to interact with the python pyproject.toml based projects" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, - {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, + {file = "pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766"}, + {file = "pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e"}, ] [package.dependencies] -packaging = ">=24.1" -tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +packaging = ">=24.2" +tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""} [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] +docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "setuptools (>=75.8)"] [[package]] name = "pytest" @@ -1397,6 +1295,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1415,13 +1314,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.25.0" +version = "0.25.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, - {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, + {file = "pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075"}, + {file = "pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f"}, ] [package.dependencies] @@ -1437,6 +1337,7 @@ version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, @@ -1455,6 +1356,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1469,6 +1371,8 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -1480,6 +1384,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1538,17 +1443,18 @@ files = [ [[package]] name = "questionary" -version = "2.0.1" +version = "2.1.0" description = "Python library to build pretty command line user prompts ⭐️" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"}, - {file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"}, + {file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"}, + {file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"}, ] [package.dependencies] -prompt_toolkit = ">=2.0,<=3.0.36" +prompt_toolkit = ">=2.0,<4.0" [[package]] name = "readme-renderer" @@ -1556,6 +1462,7 @@ version = "43.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9"}, {file = "readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311"}, @@ -1575,6 +1482,7 @@ version = "2.2.5" description = "Sphinx extension for Read the Docs overrides" optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "readthedocs-sphinx-ext-2.2.5.tar.gz", hash = "sha256:ee5fd5b99db9f0c180b2396cbce528aa36671951b9526bb0272dbfce5517bd27"}, {file = "readthedocs_sphinx_ext-2.2.5-py2.py3-none-any.whl", hash = "sha256:f8c56184ea011c972dd45a90122568587cc85b0127bc9cf064d17c68bc809daa"}, @@ -1591,6 +1499,7 @@ version = "0.7.1" description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, @@ -1607,6 +1516,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "docs"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1628,6 +1538,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -1642,6 +1553,7 @@ version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, @@ -1656,6 +1568,7 @@ version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -1669,12 +1582,42 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.9.3" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, + {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, + {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, + {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, + {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, + {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, + {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, +] + [[package]] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, @@ -1686,23 +1629,24 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "75.6.0" +version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, - {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, + {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, + {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1710,6 +1654,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1721,6 +1666,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1732,6 +1678,7 @@ version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, @@ -1743,6 +1690,7 @@ version = "7.4.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, @@ -1779,6 +1727,7 @@ version = "3.4.0" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "sphinx_autoapi-3.4.0-py3-none-any.whl", hash = "sha256:4027fef2875a22c5f2a57107c71641d82f6166bf55beb407a47aaf3ef14e7b92"}, {file = "sphinx_autoapi-3.4.0.tar.gz", hash = "sha256:e6d5371f9411bbb9fca358c00a9e57aef3ac94cbfc5df4bab285946462f69e0c"}, @@ -1800,6 +1749,7 @@ version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, @@ -1819,6 +1769,7 @@ version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -1835,6 +1786,7 @@ version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -1851,6 +1803,7 @@ version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -1867,6 +1820,7 @@ version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" +groups = ["docs"] files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, @@ -1881,6 +1835,7 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["docs"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1895,6 +1850,7 @@ version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -1911,6 +1867,7 @@ version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" +groups = ["docs"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -1927,6 +1884,8 @@ version = "0.11.0" description = "A list of Python Standard Libraries (2.7 through 3.12)." optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version < \"3.10\"" files = [ {file = "stdlib_list-0.11.0-py3-none-any.whl", hash = "sha256:8bf8decfffaaf273d4cfeb5bd852b910a00dec1037dcf163576803622bccf597"}, {file = "stdlib_list-0.11.0.tar.gz", hash = "sha256:b74a7b643a77a12637e907f3f62f0ab9f67300bce4014f6b2d3c8b4c8fd63c66"}, @@ -1945,6 +1904,7 @@ version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, @@ -1959,6 +1919,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1993,6 +1954,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {dev = "python_full_version <= \"3.11.0a6\"", docs = "python_version < \"3.11\""} [[package]] name = "tomlkit" @@ -2000,6 +1962,7 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2007,13 +1970,14 @@ files = [ [[package]] name = "tox" -version = "4.23.2" +version = "4.24.1" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"}, - {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"}, + {file = "tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75"}, + {file = "tox-4.24.1.tar.gz", hash = "sha256:083a720adbc6166fff0b7d1df9d154f9d00bfccb9403b8abf6bc0ee435d6a62e"}, ] [package.dependencies] @@ -2021,33 +1985,34 @@ cachetools = ">=5.5" chardet = ">=5.2" colorama = ">=0.4.6" filelock = ">=3.16.1" -packaging = ">=24.1" +packaging = ">=24.2" platformdirs = ">=4.3.6" pluggy = ">=1.5" pyproject-api = ">=1.8" -tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +tomli = {version = ">=2.1", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} -virtualenv = ">=20.26.6" +virtualenv = ">=20.27.1" [package.extras] test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"] [[package]] name = "twine" -version = "6.0.1" +version = "6.1.0" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "twine-6.0.1-py3-none-any.whl", hash = "sha256:9c6025b203b51521d53e200f4a08b116dee7500a38591668c6a6033117bdc218"}, - {file = "twine-6.0.1.tar.gz", hash = "sha256:36158b09df5406e1c9c1fb8edb24fc2be387709443e7376689b938531582ee27"}, + {file = "twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384"}, + {file = "twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd"}, ] [package.dependencies] +id = "*" importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} keyring = {version = ">=15.1", markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\""} -packaging = "*" -pkginfo = ">=1.8.1" +packaging = ">=24.0" readme-renderer = ">=35.0" requests = ">=2.20" requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" @@ -2064,20 +2029,23 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "docs"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +markers = {dev = "python_version < \"3.11\"", docs = "python_version < \"3.11\""} [[package]] name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev", "docs"] files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -2088,13 +2056,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.28.0" +version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, - {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, + {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, + {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, ] [package.dependencies] @@ -2112,6 +2081,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -2123,6 +2093,7 @@ version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, @@ -2137,10 +2108,12 @@ version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["dev", "docs"] files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] +markers = {dev = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\" or python_version < \"3.10\"", docs = "python_version < \"3.10\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] @@ -2151,6 +2124,6 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", type = ["pytest-mypy"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "b4e07d4e56d1bc47fe5d3ac0312deaebbef6a5a358688c32a7b3f8466afd5d18" +content-hash = "a7ce032e9af765a0a21c077b6b6ddf26684d6476b69bd58e1d71fe0e1a58e12b" diff --git a/pyproject.toml b/pyproject.toml index b998745..13eb304 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ deprecation = ">=2.1.0" jwcrypto = ">=1.5.4" httpx = ">=0.23.2" async-property = ">=0.2.2" +aiofiles = ">=24.1.0" [tool.poetry.group.docs.dependencies] alabaster = ">=0.7.0" @@ -56,10 +57,6 @@ pytest-cov = ">=3.0.0" pytest-asyncio = ">=0.23.7" wheel = ">=0.38.4" pre-commit = ">=3.5.0" -isort = ">=5.10.1" -black = ">=22.3.0" -flake8 = ">=7.0.0" -flake8-docstrings = ">=1.6.0" commitizen = ">=2.28.0" cryptography = ">=42.0.0" codespell = ">=2.1.0" @@ -67,6 +64,7 @@ darglint = ">=1.8.1" twine = ">=4.0.2" freezegun = ">=1.2.2" docutils = "<0.21" +ruff = ">=0.9.3" [[tool.poetry.source]] name = "PyPI" @@ -76,15 +74,23 @@ priority = "primary" requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" -[tool.black] +[tool.ruff] line-length = 99 -[tool.isort] -line_length = 99 -profile = "black" - -[tool.darglint] -enable = "DAR104" +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "BLE001", + "C901", + "D203", + "D212", + "FBT001", + "FBT002", + "N818", + "PLR0912", + "PLR0913", + "TRY003", +] [tool.pytest.ini_options] asyncio_default_fixture_loop_scope = "function" diff --git a/src/keycloak/__init__.py b/src/keycloak/__init__.py index 9a6961d..8f6e339 100644 --- a/src/keycloak/__init__.py +++ b/src/keycloak/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -46,8 +45,8 @@ from .keycloak_uma import KeycloakUMA from .openid_connection import KeycloakOpenIDConnection __all__ = [ - "__version__", "ConnectionManager", + "KeycloakAdmin", "KeycloakAuthenticationError", "KeycloakAuthorizationConfigError", "KeycloakConnectionError", @@ -56,13 +55,13 @@ __all__ = [ "KeycloakError", "KeycloakGetError", "KeycloakInvalidTokenError", + "KeycloakOpenID", + "KeycloakOpenIDConnection", "KeycloakOperationError", "KeycloakPostError", "KeycloakPutError", "KeycloakRPTNotFound", "KeycloakSecretNotFound", - "KeycloakAdmin", - "KeycloakOpenID", - "KeycloakOpenIDConnection", "KeycloakUMA", + "__version__", ] diff --git a/src/keycloak/_version.py b/src/keycloak/_version.py index 8ebd529..3679695 100644 --- a/src/keycloak/_version.py +++ b/src/keycloak/_version.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # diff --git a/src/keycloak/authorization/__init__.py b/src/keycloak/authorization/__init__.py index ddb885c..42efdbb 100644 --- a/src/keycloak/authorization/__init__.py +++ b/src/keycloak/authorization/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -32,19 +31,21 @@ from .role import Role class Authorization: - """Keycloak Authorization (policies, roles, scopes and resources). + """ + Keycloak Authorization (policies, roles, scopes and resources). https://keycloak.gitbooks.io/documentation/authorization_services/index.html """ - def __init__(self): + def __init__(self) -> None: """Init method.""" self.policies = {} @property - def policies(self): - """Get policies. + def policies(self) -> dict: + """ + Get policies. :returns: Policies :rtype: dict @@ -52,11 +53,12 @@ class Authorization: return self._policies @policies.setter - def policies(self, value): + def policies(self, value: dict) -> None: self._policies = value - def load_config(self, data): - """Load policies, roles and permissions (scope/resources). + def load_config(self, data: dict) -> None: + """ + Load policies, roles and permissions (scope/resources). :param data: keycloak authorization data (dict) :type data: dict diff --git a/src/keycloak/authorization/permission.py b/src/keycloak/authorization/permission.py index d1b606f..c1e7e3e 100644 --- a/src/keycloak/authorization/permission.py +++ b/src/keycloak/authorization/permission.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -25,7 +24,8 @@ class Permission: - """Base permission class. + """ + Base permission class. Consider this simple and very common permission: @@ -56,8 +56,9 @@ class Permission: """ - def __init__(self, name, type, logic, decision_strategy): - """Init method. + def __init__(self, name: str, type: str, logic: str, decision_strategy: str) -> None: # noqa: A002 + """ + Init method. :param name: Name :type name: str @@ -75,25 +76,28 @@ class Permission: self.resources = [] self.scopes = [] - def __repr__(self): - """Repr method. + def __repr__(self) -> str: + """ + Repr method. :returns: Class representation :rtype: str """ - return "" % (self.name, self.type) + return f"" - def __str__(self): - """Str method. + def __str__(self) -> str: + """ + Str method. :returns: Class string representation :rtype: str """ - return "Permission: %s (%s)" % (self.name, self.type) + return f"Permission: {self.name} ({self.type})" @property - def name(self): - """Get name. + def name(self) -> str: + """ + Get name. :returns: name :rtype: str @@ -101,12 +105,13 @@ class Permission: return self._name @name.setter - def name(self, value): + def name(self, value: str) -> None: self._name = value @property - def type(self): - """Get type. + def type(self) -> str: + """ + Get type. :returns: type :rtype: str @@ -114,12 +119,13 @@ class Permission: return self._type @type.setter - def type(self, value): + def type(self, value: str) -> None: self._type = value @property - def logic(self): - """Get logic. + def logic(self) -> str: + """ + Get logic. :returns: Logic :rtype: str @@ -127,12 +133,13 @@ class Permission: return self._logic @logic.setter - def logic(self, value): + def logic(self, value: str) -> str: self._logic = value @property - def decision_strategy(self): - """Get decision strategy. + def decision_strategy(self) -> str: + """ + Get decision strategy. :returns: Decision strategy :rtype: str @@ -140,12 +147,13 @@ class Permission: return self._decision_strategy @decision_strategy.setter - def decision_strategy(self, value): + def decision_strategy(self, value: str) -> None: self._decision_strategy = value @property - def resources(self): - """Get resources. + def resources(self) -> list: + """ + Get resources. :returns: Resources :rtype: list @@ -153,12 +161,13 @@ class Permission: return self._resources @resources.setter - def resources(self, value): + def resources(self, value: list) -> None: self._resources = value @property - def scopes(self): - """Get scopes. + def scopes(self) -> list: + """ + Get scopes. :returns: Scopes :rtype: list @@ -166,5 +175,5 @@ class Permission: return self._scopes @scopes.setter - def scopes(self, value): + def scopes(self, value: list) -> None: self._scopes = value diff --git a/src/keycloak/authorization/policy.py b/src/keycloak/authorization/policy.py index fdf482d..87cdbbf 100644 --- a/src/keycloak/authorization/policy.py +++ b/src/keycloak/authorization/policy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -23,11 +22,12 @@ """Keycloak authorization Policy module.""" -from ..exceptions import KeycloakAuthorizationConfigError +from keycloak.exceptions import KeycloakAuthorizationConfigError class Policy: - """Base policy class. + """ + Base policy class. A policy defines the conditions that must be satisfied to grant access to an object. Unlike permissions, you do not specify the object being protected but rather the conditions @@ -50,8 +50,9 @@ class Policy: """ - def __init__(self, name, type, logic, decision_strategy): - """Init method. + def __init__(self, name: str, type: str, logic: str, decision_strategy: str) -> None: # noqa: A002 + """ + Init method. :param name: Name :type name: str @@ -69,25 +70,28 @@ class Policy: self.roles = [] self.permissions = [] - def __repr__(self): - """Repr method. + def __repr__(self) -> str: + """ + Repr method. :returns: Class representation :rtype: str """ - return "" % (self.name, self.type) + return f"" - def __str__(self): - """Str method. + def __str__(self) -> str: + """ + Str method. :returns: Class string representation :rtype: str """ - return "Policy: %s (%s)" % (self.name, self.type) + return f"Policy: {self.name} ({self.type})" @property - def name(self): - """Get name. + def name(self) -> str: + """ + Get name. :returns: Name :rtype: str @@ -95,12 +99,13 @@ class Policy: return self._name @name.setter - def name(self, value): + def name(self, value: str) -> None: self._name = value @property - def type(self): - """Get type. + def type(self) -> str: + """ + Get type. :returns: Type :rtype: str @@ -108,12 +113,13 @@ class Policy: return self._type @type.setter - def type(self, value): + def type(self, value: str) -> None: self._type = value @property - def logic(self): - """Get logic. + def logic(self) -> str: + """ + Get logic. :returns: Logic :rtype: str @@ -121,12 +127,13 @@ class Policy: return self._logic @logic.setter - def logic(self, value): + def logic(self, value: str) -> None: self._logic = value @property - def decision_strategy(self): - """Get decision strategy. + def decision_strategy(self) -> str: + """ + Get decision strategy. :returns: Decision strategy :rtype: str @@ -134,12 +141,13 @@ class Policy: return self._decision_strategy @decision_strategy.setter - def decision_strategy(self, value): + def decision_strategy(self, value: str) -> None: self._decision_strategy = value @property - def roles(self): - """Get roles. + def roles(self) -> list: + """ + Get roles. :returns: Roles :rtype: list @@ -147,12 +155,13 @@ class Policy: return self._roles @roles.setter - def roles(self, value): + def roles(self, value: list) -> None: self._roles = value @property - def permissions(self): - """Get permissions. + def permissions(self) -> list: + """ + Get permissions. :returns: Permissions :rtype: list @@ -160,24 +169,25 @@ class Policy: return self._permissions @permissions.setter - def permissions(self, value): + def permissions(self, value: list) -> None: self._permissions = value - def add_role(self, role): - """Add keycloak role in policy. + def add_role(self, role: dict) -> None: + """ + Add keycloak role in policy. :param role: Keycloak role :type role: keycloak.authorization.Role :raises KeycloakAuthorizationConfigError: In case of misconfigured policy type """ if self.type != "role": - raise KeycloakAuthorizationConfigError( - "Can't add role. Policy type is different of role" - ) + error_msg = "Can't add role. Policy type is different of role" + raise KeycloakAuthorizationConfigError(error_msg) self._roles.append(role) - def add_permission(self, permission): - """Add keycloak permission in policy. + def add_permission(self, permission: dict) -> None: + """ + Add keycloak permission in policy. :param permission: Keycloak permission :type permission: keycloak.authorization.Permission diff --git a/src/keycloak/authorization/role.py b/src/keycloak/authorization/role.py index 3d4c000..2c15182 100644 --- a/src/keycloak/authorization/role.py +++ b/src/keycloak/authorization/role.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -25,7 +24,8 @@ class Role: - """Authorization Role base class. + """ + Authorization Role base class. Roles identify a type or category of user. Admin, user, manager, and employee are all typical roles that may exist in an organization. @@ -38,8 +38,9 @@ class Role: :type required: bool """ - def __init__(self, name, required=False): - """Init method. + def __init__(self, name: str, required: bool = False) -> None: + """ + Init method. :param name: Name :type name: str @@ -49,22 +50,25 @@ class Role: self.name = name self.required = required - def get_name(self): - """Get name. + def get_name(self) -> str: + """ + Get name. :returns: Name :rtype: str """ return self.name - def __eq__(self, other): - """Eq method. + def __eq__(self, other: str) -> bool: + """ + Eq method. :param other: The other object :type other: str :returns: Equality bool - :rtype: bool | NotImplemented + :rtype: bool """ if isinstance(other, str): return self.name == other - return NotImplemented + + raise NotImplementedError diff --git a/src/keycloak/connection.py b/src/keycloak/connection.py index a709713..400ee23 100644 --- a/src/keycloak/connection.py +++ b/src/keycloak/connection.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -23,6 +22,8 @@ """Connection manager module.""" +from __future__ import annotations + try: from urllib.parse import urljoin except ImportError: # pragma: no cover @@ -30,13 +31,16 @@ except ImportError: # pragma: no cover import httpx import requests +from httpx import Response as AsyncResponse +from requests import Response from requests.adapters import HTTPAdapter from .exceptions import KeycloakConnectionError -class ConnectionManager(object): - """Represents a simple server connection. +class ConnectionManager: + """ + Represents a simple server connection. :param base_url: The server URL. :type base_url: str @@ -58,9 +62,17 @@ class ConnectionManager(object): """ def __init__( - self, base_url, headers={}, timeout=60, verify=True, proxies=None, cert=None, max_retries=1 - ): - """Init method. + self, + base_url: str, + headers: dict | None = None, + timeout: int = 60, + verify: bool = True, + proxies: dict | None = None, + cert: str | tuple | None = None, + max_retries: int = 1, + ) -> None: + """ + Init method. :param base_url: The server URL. :type base_url: str @@ -106,19 +118,20 @@ class ConnectionManager(object): self.async_s.auth = None # don't let requests add auth headers self.async_s.transport = httpx.AsyncHTTPTransport(retries=1) - async def aclose(self): + async def aclose(self) -> None: """Close the async connection on delete.""" if hasattr(self, "_s"): await self.async_s.aclose() - def __del__(self): + def __del__(self) -> None: """Del method.""" if hasattr(self, "_s"): self._s.close() @property - def base_url(self): - """Return base url in use for requests to the server. + def base_url(self) -> str: + """ + Return base url in use for requests to the server. :returns: Base URL :rtype: str @@ -126,12 +139,13 @@ class ConnectionManager(object): return self._base_url @base_url.setter - def base_url(self, value): + def base_url(self, value: str) -> None: self._base_url = value @property - def timeout(self): - """Return timeout in use for request to the server. + def timeout(self) -> int: + """ + Return timeout in use for request to the server. :returns: Timeout :rtype: int @@ -139,12 +153,13 @@ class ConnectionManager(object): return self._timeout @timeout.setter - def timeout(self, value): + def timeout(self, value: int) -> None: self._timeout = value @property - def verify(self): - """Return verify in use for request to the server. + def verify(self) -> bool: + """ + Return verify in use for request to the server. :returns: Verify indicator :rtype: bool @@ -152,12 +167,13 @@ class ConnectionManager(object): return self._verify @verify.setter - def verify(self, value): + def verify(self, value: bool) -> None: self._verify = value @property - def cert(self): - """Return client certificates in use for request to the server. + def cert(self) -> str | tuple: + """ + Return client certificates in use for request to the server. :returns: Client certificate :rtype: Union[str,Tuple[str,str]] @@ -165,12 +181,13 @@ class ConnectionManager(object): return self._cert @cert.setter - def cert(self, value): + def cert(self, value: str | tuple) -> None: self._cert = value @property - def headers(self): - """Return header request to the server. + def headers(self) -> dict: + """ + Return header request to the server. :returns: Request headers :rtype: dict @@ -178,11 +195,12 @@ class ConnectionManager(object): return self._headers @headers.setter - def headers(self, value): - self._headers = value + def headers(self, value: dict) -> None: + self._headers = value or {} - def param_headers(self, key): - """Return a specific header parameter. + def param_headers(self, key: str) -> str | None: + """ + Return a specific header parameter. :param key: Header parameters key. :type key: str @@ -191,12 +209,13 @@ class ConnectionManager(object): """ return self.headers.get(key) - def clean_headers(self): + def clean_headers(self) -> None: """Clear header parameters.""" self.headers = {} - def exist_param_headers(self, key): - """Check if the parameter exists in the header. + def exist_param_headers(self, key: str) -> bool: + """ + Check if the parameter exists in the header. :param key: Header parameters key. :type key: str @@ -205,8 +224,9 @@ class ConnectionManager(object): """ return self.param_headers(key) is not None - def add_param_headers(self, key, value): - """Add a single parameter inside the header. + def add_param_headers(self, key: str, value: str) -> None: + """ + Add a single parameter inside the header. :param key: Header parameters key. :type key: str @@ -215,16 +235,18 @@ class ConnectionManager(object): """ self.headers[key] = value - def del_param_headers(self, key): - """Remove a specific parameter. + def del_param_headers(self, key: str) -> None: + """ + Remove a specific parameter. :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. + def raw_get(self, path: str, **kwargs: dict) -> Response: + """ + Submit get request to the path. :param path: Path for request. :type path: str @@ -244,10 +266,12 @@ class ConnectionManager(object): cert=self.cert, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - def raw_post(self, path, data, **kwargs): - """Submit post request to the path. + def raw_post(self, path: str, data: dict, **kwargs: dict) -> Response: + """ + Submit post request to the path. :param path: Path for request. :type path: str @@ -270,10 +294,12 @@ class ConnectionManager(object): cert=self.cert, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - def raw_put(self, path, data, **kwargs): - """Submit put request to the path. + def raw_put(self, path: str, data: dict, **kwargs: dict) -> Response: + """ + Submit put request to the path. :param path: Path for request. :type path: str @@ -296,10 +322,12 @@ class ConnectionManager(object): cert=self.cert, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - def raw_delete(self, path, data=None, **kwargs): - """Submit delete request to the path. + def raw_delete(self, path: str, data: dict | None = None, **kwargs: dict) -> Response: + """ + Submit delete request to the path. :param path: Path for request. :type path: str @@ -312,21 +340,22 @@ class ConnectionManager(object): :raises KeycloakConnectionError: HttpError Can't connect to server. """ try: - r = self._s.delete( + return self._s.delete( urljoin(self.base_url, path), params=kwargs, - data=data or dict(), + data=data or {}, headers=self.headers, timeout=self.timeout, verify=self.verify, cert=self.cert, ) - return r except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - async def a_raw_get(self, path, **kwargs): - """Submit get request to the path. + async def a_raw_get(self, path: str, **kwargs: dict) -> AsyncResponse: + """ + Submit get request to the path. :param path: Path for request. :type path: str @@ -344,10 +373,12 @@ class ConnectionManager(object): timeout=self.timeout, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - async def a_raw_post(self, path, data, **kwargs): - """Submit post request to the path. + async def a_raw_post(self, path: str, data: dict, **kwargs: dict) -> AsyncResponse: + """ + Submit post request to the path. :param path: Path for request. :type path: str @@ -369,10 +400,12 @@ class ConnectionManager(object): timeout=self.timeout, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e - async def a_raw_put(self, path, data, **kwargs): - """Submit put request to the path. + async def a_raw_put(self, path: str, data: dict, **kwargs: dict) -> AsyncResponse: + """ + Submit put request to the path. :param path: Path for request. :type path: str @@ -393,10 +426,17 @@ class ConnectionManager(object): timeout=self.timeout, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) - - async def a_raw_delete(self, path, data=None, **kwargs): - """Submit delete request to the path. + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e + + async def a_raw_delete( + self, + path: str, + data: dict | None = None, + **kwargs: dict, + ) -> AsyncResponse: + """ + Submit delete request to the path. :param path: Path for request. :type path: str @@ -412,17 +452,19 @@ class ConnectionManager(object): return await self.async_s.request( method="DELETE", url=urljoin(self.base_url, path), - data=data or dict(), + data=data or {}, params=self._filter_query_params(kwargs), headers=self.headers, timeout=self.timeout, ) except Exception as e: - raise KeycloakConnectionError("Can't connect to server (%s)" % e) + msg = "Can't connect to server" + raise KeycloakConnectionError(msg) from e @staticmethod - def _filter_query_params(query_params): - """Explicitly filter query params with None values for compatibility. + def _filter_query_params(query_params: dict) -> dict: + """ + Explicitly filter query params with None values for compatibility. Httpx and requests differ in the way they handle query params with the value None, requests does not include params with the value None while httpx includes them as-is. diff --git a/src/keycloak/exceptions.py b/src/keycloak/exceptions.py index fe46bf4..48f36a4 100644 --- a/src/keycloak/exceptions.py +++ b/src/keycloak/exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -23,11 +22,32 @@ """Keycloak custom exceptions module.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import requests +if TYPE_CHECKING: + from httpx import Response as AsyncResponse + +from requests import Response + +HTTP_OK = 200 +HTTP_CREATED = 201 +HTTP_ACCEPTED = 202 +HTTP_NO_CONTENT = 204 +HTTP_BAD_REQUEST = 400 +HTTP_UNAUTHORIZED = 401 +HTTP_FORBIDDEN = 403 +HTTP_NOT_FOUND = 404 +HTTP_NOT_ALLOWED = 405 +HTTP_CONFLICT = 409 + class KeycloakError(Exception): - """Base class for custom Keycloak errors. + """ + Base class for custom Keycloak errors. :param error_message: The error message :type error_message: str @@ -35,8 +55,14 @@ class KeycloakError(Exception): :type response_code: int """ - def __init__(self, error_message="", response_code=None, response_body=None): - """Init method. + def __init__( + self, + error_message: str = "", + response_code: int | None = None, + response_body: bytes | None = None, + ) -> None: + """ + Init method. :param error_message: The error message :type error_message: str @@ -51,104 +77,82 @@ class KeycloakError(Exception): self.response_body = response_body self.error_message = error_message - def __str__(self): - """Str method. + def __str__(self) -> str: + """ + 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: - return "{0}".format(self.error_message) + return f"{self.response_code}: {self.error_message}" + return f"{self.error_message}" class KeycloakAuthenticationError(KeycloakError): """Keycloak authentication error exception.""" - pass - class KeycloakConnectionError(KeycloakError): """Keycloak connection error exception.""" - pass - class KeycloakOperationError(KeycloakError): """Keycloak operation error exception.""" - pass - class KeycloakDeprecationError(KeycloakError): """Keycloak deprecation error exception.""" - pass - class KeycloakGetError(KeycloakOperationError): """Keycloak request get error exception.""" - pass - class KeycloakPostError(KeycloakOperationError): """Keycloak request post error exception.""" - pass - class KeycloakPutError(KeycloakOperationError): """Keycloak request put error exception.""" - pass - class KeycloakDeleteError(KeycloakOperationError): """Keycloak request delete error exception.""" - pass - class KeycloakSecretNotFound(KeycloakOperationError): """Keycloak secret not found exception.""" - pass - class KeycloakRPTNotFound(KeycloakOperationError): """Keycloak RPT not found exception.""" - pass - class KeycloakAuthorizationConfigError(KeycloakOperationError): """Keycloak authorization config exception.""" - pass - class KeycloakInvalidTokenError(KeycloakOperationError): """Keycloak invalid token exception.""" - pass - class KeycloakPermissionFormatError(KeycloakOperationError): """Keycloak permission format exception.""" - pass - class PermissionDefinitionError(Exception): """Keycloak permission definition exception.""" - pass - -def raise_error_from_response(response, error, expected_codes=None, skip_exists=False): - """Raise an exception for the response. +def raise_error_from_response( + response: Response | AsyncResponse, + error: dict | Exception, + expected_codes: list[int] | None = None, + skip_exists: bool = False, +) -> bytes | dict | list: + """ + Raise an exception for the response. :param response: The response object :type response: Response @@ -162,9 +166,9 @@ def raise_error_from_response(response, error, expected_codes=None, skip_exists= :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] + expected_codes = [HTTP_OK, HTTP_CREATED, HTTP_NO_CONTENT] if response.status_code in expected_codes: if response.status_code == requests.codes.no_content: @@ -175,7 +179,7 @@ def raise_error_from_response(response, error, expected_codes=None, skip_exists= except ValueError: return response.content - if skip_exists and response.status_code == 409: + if skip_exists and response.status_code == HTTP_CONFLICT: return {"msg": "Already exists"} try: @@ -185,10 +189,11 @@ def raise_error_from_response(response, error, expected_codes=None, skip_exists= if isinstance(error, dict): error = error.get(response.status_code, KeycloakOperationError) - else: - if response.status_code == 401: - error = KeycloakAuthenticationError + elif response.status_code == HTTP_UNAUTHORIZED: + error = KeycloakAuthenticationError raise error( - error_message=message, response_code=response.status_code, response_body=response.content + error_message=message, + response_code=response.status_code, + response_body=response.content, ) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index b69f8df..26a3e14 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -26,15 +25,21 @@ """The keycloak admin module.""" +from __future__ import annotations + import copy import json -from builtins import isinstance -from typing import Optional from requests_toolbelt import MultipartEncoder from . import urls_patterns from .exceptions import ( + HTTP_ACCEPTED, + HTTP_BAD_REQUEST, + HTTP_CREATED, + HTTP_NO_CONTENT, + HTTP_NOT_FOUND, + HTTP_OK, KeycloakDeleteError, KeycloakGetError, KeycloakPostError, @@ -45,7 +50,8 @@ from .openid_connection import KeycloakOpenIDConnection class KeycloakAdmin: - """Keycloak Admin client. + """ + Keycloak Admin client. :param server_url: Keycloak server url :type server_url: str @@ -87,24 +93,25 @@ class KeycloakAdmin: def __init__( self, - server_url=None, - grant_type=None, - username=None, - password=None, - token=None, - totp=None, - realm_name="master", - client_id="admin-cli", - verify=True, - client_secret_key=None, - custom_headers=None, - user_realm_name=None, - timeout=60, - cert=None, - max_retries=1, - connection: Optional[KeycloakOpenIDConnection] = None, - ): - """Init method. + server_url: str | None = None, + grant_type: str | None = None, + username: str | None = None, + password: str | None = None, + token: dict | None = None, + totp: str | None = None, + realm_name: str = "master", + client_id: str = "admin-cli", + verify: bool | str = True, + client_secret_key: str | None = None, + custom_headers: dict | None = None, + user_realm_name: str | None = None, + timeout: int = 60, + cert: str | tuple | None = None, + max_retries: int = 1, + connection: KeycloakOpenIDConnection | None = None, + ) -> None: + """ + Init method. :param server_url: Keycloak server url :type server_url: str @@ -162,7 +169,8 @@ class KeycloakAdmin: @property def connection(self) -> KeycloakOpenIDConnection: - """Get connection. + """ + Get connection. :returns: Connection manager :rtype: KeycloakOpenIDConnection @@ -173,8 +181,9 @@ class KeycloakAdmin: def connection(self, value: KeycloakOpenIDConnection) -> None: self._connection = value - def __fetch_all(self, url, query=None): - """Paginate over get requests. + def __fetch_all(self, url: str, query: dict | None = None) -> list: + """ + Paginate over get requests. Wrapper function to paginate GET requests. @@ -191,6 +200,7 @@ class KeycloakAdmin: # initialize query if it was called with None if not query: query = {} + page = 0 query["max"] = self.PAGE_SIZE @@ -198,7 +208,8 @@ class KeycloakAdmin: while True: query["first"] = page * self.PAGE_SIZE partial_results = raise_error_from_response( - self.connection.raw_get(url, **query), KeycloakGetError + self.connection.raw_get(url, **query), + KeycloakGetError, ) if not partial_results: break @@ -206,10 +217,12 @@ class KeycloakAdmin: if len(partial_results) < query["max"]: break page += 1 + return results - def __fetch_paginated(self, url, query=None): - """Make a specific paginated request. + def __fetch_paginated(self, url: str, query: dict | None = None) -> dict | list: + """ + Make a specific paginated request. :param url: The url on which the query is executed :type url: str @@ -222,7 +235,8 @@ class KeycloakAdmin: return raise_error_from_response(self.connection.raw_get(url, **query), KeycloakGetError) def get_current_realm(self) -> str: - """Return the currently configured realm. + """ + Return the currently configured realm. :returns: Currently configured realm name :rtype: str @@ -230,15 +244,17 @@ class KeycloakAdmin: return self.connection.realm_name def change_current_realm(self, realm_name: str) -> None: - """Change the current realm. + """ + Change the current realm. :param realm_name: The name of the realm to be configured as current :type realm_name: str """ self.connection.realm_name = realm_name - def import_realm(self, payload): - """Import a new realm from a RealmRepresentation. + def import_realm(self, payload: dict) -> dict | bytes: + """ + Import a new realm from a RealmRepresentation. Realm name must be unique. @@ -251,12 +267,18 @@ class KeycloakAdmin: :rtype: dict """ data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALMS, + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def partial_import_realm(self, realm_name, payload): - """Partial import realm configuration from PartialImportRepresentation. + def partial_import_realm(self, realm_name: str, payload: dict) -> dict | bytes: + """ + Partial import realm configuration from PartialImportRepresentation. Realm partialImport is used for modifying configuration of existing realm. @@ -276,10 +298,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - def export_realm(self, export_clients=False, export_groups_and_role=False): - """Export the realm configurations in the json format. + def export_realm( + self, + export_clients: bool = False, + export_groups_and_role: bool = False, + ) -> dict: + """ + Export the realm configurations in the json format. RealmRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_partialexport @@ -305,8 +332,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - def get_realms(self): - """List all realms in Keycloak deployment. + def get_realms(self) -> list: + """ + List all realms in Keycloak deployment. :return: realms list :rtype: list @@ -314,8 +342,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALMS) return raise_error_from_response(data_raw, KeycloakGetError) - def get_realm(self, realm_name): - """Get a specific realm. + def get_realm(self, realm_name: str) -> dict: + """ + Get a specific realm. RealmRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmrepresentation @@ -327,10 +356,11 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def create_realm(self, payload, skip_exists=False): - """Create a realm. + def create_realm(self, payload: dict, skip_exists: bool = False) -> dict | bytes: + """ + Create a realm. RealmRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmrepresentation @@ -343,14 +373,19 @@ class KeycloakAdmin: :rtype: dict """ data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALMS, + data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def update_realm(self, realm_name, payload): - """Update a realm. + def update_realm(self, realm_name: str, payload: dict) -> dict | bytes: + """ + Update a realm. This will only update top level attributes and will ignore any user, role, or client information in the payload. @@ -367,12 +402,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALM.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def delete_realm(self, realm_name): - """Delete a realm. + def delete_realm(self, realm_name: str) -> dict | bytes: + """ + Delete a realm. :param realm_name: Realm name (not the realm id) :type realm_name: str @@ -381,10 +422,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_users(self, query=None): - """Get all users. + def get_users(self, query: dict | None = None) -> list: + """ + Get all users. Return a list of users, filtered according to query parameters @@ -405,8 +451,9 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def create_idp(self, payload): - """Create an ID Provider. + def create_idp(self, payload: dict) -> dict | bytes: + """ + Create an ID Provider. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identityproviderrepresentation @@ -418,12 +465,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDPS.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def update_idp(self, idp_alias, payload): - """Update an ID Provider. + def update_idp(self, idp_alias: str, payload: dict) -> dict | bytes: + """ + Update an ID Provider. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identity_providers_resource @@ -437,12 +490,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDP.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def add_mapper_to_idp(self, idp_alias, payload): - """Create an ID Provider. + def add_mapper_to_idp(self, idp_alias: str, payload: dict) -> dict | bytes: + """ + Create an ID Provider. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identityprovidermapperrepresentation @@ -456,12 +515,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def update_mapper_in_idp(self, idp_alias, mapper_id, payload): - """Update an IdP mapper. + def update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> dict | bytes: + """ + Update an IdP mapper. IdentityProviderMapperRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_update @@ -486,10 +551,15 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_idp_mappers(self, idp_alias): - """Get IDP mappers. + def get_idp_mappers(self, idp_alias: str) -> list: + """ + Get IDP mappers. Returns a list of ID Providers mappers @@ -503,12 +573,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_idps(self): - """Get IDPs. + def get_idps(self) -> list: + """ + Get IDPs. Returns a list of ID Providers, @@ -522,8 +593,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def get_idp(self, idp_alias): - """Get IDP provider. + def get_idp(self, idp_alias: str) -> dict: + """ + Get IDP provider. Get the representation of a specific IDP Provider. @@ -539,8 +611,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDP.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_idp(self, idp_alias): - """Delete an ID Provider. + def delete_idp(self, idp_alias: str) -> dict | bytes: + """ + Delete an ID Provider. :param: idp_alias: idp alias name :type idp_alias: str @@ -549,10 +622,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def create_user(self, payload, exist_ok=False): - """Create a new user. + def create_user(self, payload: str, exist_ok: bool = False) -> str: + """ + Create a new user. Username must be unique @@ -577,14 +655,16 @@ class KeycloakAdmin: return str(exists) data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_USERS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_USERS.format(**params_path), + data=json.dumps(payload), ) - raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def users_count(self, query=None): - """Count users. + def users_count(self, query: dict | None = None) -> int: + """ + Count users. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_users_resource @@ -594,15 +674,17 @@ class KeycloakAdmin: :return: counter :rtype: int """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query + urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_user_id(self, username): - """Get internal keycloak user id from username. + def get_user_id(self, username: str) -> str | None: + """ + Get internal keycloak user id from username. This is required for further actions against this user. @@ -619,8 +701,9 @@ class KeycloakAdmin: users = self.get_users(query={"username": lower_user_name, "max": 1, "exact": True}) return users[0]["id"] if len(users) == 1 else None - def get_user(self, user_id, user_profile_metadata=False): - """Get representation of the user. + def get_user(self, user_id: str, user_profile_metadata: bool = False) -> dict: + """ + Get representation of the user. UserRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation @@ -638,8 +721,14 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_user_groups(self, user_id, query=None, brief_representation=True): - """Get user groups. + def get_user_groups( + self, + user_id: str, + query: dict | None = None, + brief_representation: bool = True, + ) -> list: + """ + Get user groups. Returns a list of groups of which the user is a member @@ -667,8 +756,9 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def update_user(self, user_id, payload): - """Update the user. + def update_user(self, user_id: str, payload: dict) -> dict | bytes: + """ + Update the user. :param user_id: User id :type user_id: str @@ -680,12 +770,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_USER.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def disable_user(self, user_id): - """Disable the user from the realm. Disabled users can not log in. + def disable_user(self, user_id: str) -> dict | bytes: + """ + Disable the user from the realm. Disabled users can not log in. :param user_id: User id :type user_id: str @@ -695,8 +791,9 @@ class KeycloakAdmin: """ return self.update_user(user_id=user_id, payload={"enabled": False}) - def enable_user(self, user_id): - """Enable the user from the realm. + def enable_user(self, user_id: str) -> dict | bytes: + """ + Enable the user from the realm. :param user_id: User id :type user_id: str @@ -706,22 +803,23 @@ class KeycloakAdmin: """ return self.update_user(user_id=user_id, payload={"enabled": True}) - def disable_all_users(self): + def disable_all_users(self) -> None: """Disable all existing users.""" users = self.get_users() for user in users: user_id = user["id"] self.disable_user(user_id=user_id) - def enable_all_users(self): + def enable_all_users(self) -> None: """Disable all existing users.""" users = self.get_users() for user in users: user_id = user["id"] self.enable_user(user_id=user_id) - def delete_user(self, user_id): - """Delete the user. + def delete_user(self, user_id: str) -> dict | bytes: + """ + Delete the user. :param user_id: User id :type user_id: str @@ -730,10 +828,20 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_USER.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def set_user_password(self, user_id, password, temporary=True): - """Set up a password for the user. + def set_user_password( + self, + user_id: str, + password: str, + temporary: bool = True, + ) -> dict | bytes: + """ + Set up a password for the user. If temporary is True, the user will have to reset the temporary password next time they log in. @@ -753,12 +861,18 @@ class KeycloakAdmin: payload = {"type": "password", "temporary": temporary, "value": password} params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def get_credentials(self, user_id): - """Get user credentials. + def get_credentials(self, user_id: str) -> dict: + """ + Get user credentials. Returns a list of credential belonging to the user. @@ -772,12 +886,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path) + urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_credential(self, user_id, credential_id): - """Delete credential of the user. + def delete_credential(self, user_id: str, credential_id: str) -> dict | bytes: + """ + Delete credential of the user. CredentialRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_credentialrepresentation @@ -795,12 +910,13 @@ class KeycloakAdmin: "credential_id": credential_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path) + urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def user_logout(self, user_id): - """Log out the user. + def user_logout(self, user_id: str) -> dict | bytes: + """ + Log out the user. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_logout @@ -811,12 +927,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data="" + urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - def user_consents(self, user_id): - """Get consents granted by the user. + def user_consents(self, user_id: str) -> list: + """ + Get consents granted by the user. UserConsentRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userconsentrepresentation @@ -828,12 +950,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path) + urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_user_social_logins(self, user_id): - """Get user social logins. + def get_user_social_logins(self, user_id: str) -> list: + """ + Get user social logins. Returns a list of federated identities/social logins of which the user has been associated with @@ -844,12 +967,19 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path) + urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def add_user_social_login(self, user_id, provider_id, provider_userid, provider_username): - """Add a federated identity / social login provider to the user. + def add_user_social_login( + self, + user_id: str, + provider_id: str, + provider_userid: str, + provider_username: str, + ) -> dict | bytes: + """ + Add a federated identity / social login provider to the user. :param user_id: User id :type user_id: str @@ -876,10 +1006,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201, 204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED, HTTP_NO_CONTENT], + ) - def delete_user_social_login(self, user_id, provider_id): - """Delete a federated identity / social login provider from the user. + def delete_user_social_login(self, user_id: str, provider_id: str) -> dict | bytes: + """ + Delete a federated identity / social login provider from the user. :param user_id: User id :type user_id: str @@ -894,14 +1029,24 @@ class KeycloakAdmin: "provider": provider_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path) + urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) def send_update_account( - self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None - ): - """Send an update account email to the user. + self, + user_id: str, + payload: dict, + client_id: str | None = None, + lifespan: int | None = None, + redirect_uri: str | None = None, + ) -> dict | bytes: + """ + Send an update account email to the user. An email contains a link the user can click to perform a set of required actions. @@ -928,8 +1073,14 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - def send_verify_email(self, user_id, client_id=None, redirect_uri=None): - """Send a update account email to the user. + def send_verify_email( + self, + user_id: str, + client_id: str | None = None, + redirect_uri: str | None = None, + ) -> dict | bytes: + """ + Send a update account email to the user. An email contains a link the user can click to perform a set of required actions. @@ -952,8 +1103,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - def get_sessions(self, user_id): - """Get sessions associated with the user. + def get_sessions(self, user_id: str) -> list: + """ + Get sessions associated with the user. UserSessionRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation @@ -961,16 +1113,17 @@ class KeycloakAdmin: :param user_id: Id of user :type user_id: str :return: UserSessionRepresentation - :rtype: dict + :rtype: list """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path) + urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_server_info(self): - """Get themes, social providers, etc. on this server. + def get_server_info(self) -> dict: + """ + Get themes, social providers, etc. on this server. ServerInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_serverinforepresentation @@ -981,8 +1134,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) return raise_error_from_response(data_raw, KeycloakGetError) - def get_groups(self, query=None, full_hierarchy=False): - """Get groups. + def get_groups(self, query: dict | None = None, full_hierarchy: bool = False) -> list: + """ + Get groups. Returns a list of groups belonging to the realm @@ -1014,13 +1168,15 @@ class KeycloakAdmin: for group in groups: if group.get("subGroupCount"): group["subGroups"] = self.get_group_children( - group_id=group.get("id"), full_hierarchy=full_hierarchy + group_id=group.get("id"), + full_hierarchy=full_hierarchy, ) return groups - def get_group(self, group_id, full_hierarchy=False): - """Get group by id. + def get_group(self, group_id: str, full_hierarchy: bool = False) -> dict: + """ + Get group by id. Returns full group details @@ -1038,20 +1194,22 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": group_id} response = self.connection.raw_get(urls_patterns.URL_ADMIN_GROUP.format(**params_path)) - if response.status_code >= 400: + if response.status_code >= HTTP_BAD_REQUEST: return raise_error_from_response(response, KeycloakGetError) # For version +23.0.0 group = response.json() if group.get("subGroupCount"): group["subGroups"] = self.get_group_children( - group.get("id"), full_hierarchy=full_hierarchy + group.get("id"), + full_hierarchy=full_hierarchy, ) return group - def get_subgroups(self, group, path): - """Get subgroups. + def get_subgroups(self, group: str, path: str) -> dict | None: + """ + Get subgroups. Utility function to iterate through nested group structures @@ -1068,16 +1226,23 @@ class KeycloakAdmin: for subgroup in group["subGroups"]: if subgroup["path"] == path: return subgroup - elif subgroup["subGroups"]: - for subgroup in group["subGroups"]: - result = self.get_subgroups(subgroup, path) + if subgroup["subGroups"]: + for _subgroup in group["subGroups"]: + result = self.get_subgroups(_subgroup, path) if result: return result + # went through the tree without hits return None - def get_group_children(self, group_id, query=None, full_hierarchy=False): - """Get group children by parent id. + def get_group_children( + self, + group_id: str, + query: dict | None = None, + full_hierarchy: bool = False, + ) -> dict: + """ + Get group children by parent id. Returns full group children details @@ -1093,7 +1258,8 @@ class KeycloakAdmin: """ query = query or {} if query and full_hierarchy: - raise ValueError("Cannot use both query and full_hierarchy parameters") + msg = "Cannot use both query and full_hierarchy parameters" + raise ValueError(msg) params_path = {"realm-name": self.connection.realm_name, "id": group_id} url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path) @@ -1107,13 +1273,15 @@ class KeycloakAdmin: for group in res: if group.get("subGroupCount"): group["subGroups"] = self.get_group_children( - group_id=group.get("id"), full_hierarchy=full_hierarchy + group_id=group.get("id"), + full_hierarchy=full_hierarchy, ) return res - def get_group_members(self, group_id, query=None): - """Get members by group id. + def get_group_members(self, group_id: str, query: dict | None = None) -> list: + """ + Get members by group id. Returns group members @@ -1137,8 +1305,9 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def get_group_by_path(self, path): - """Get group id based on name or path. + def get_group_by_path(self, path: str) -> dict: + """ + Get group id based on name or path. Returns full group details for a group defined by path @@ -1152,12 +1321,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "path": path} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path) + urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, [200, 404]) + return raise_error_from_response(data_raw, KeycloakGetError, [HTTP_OK, HTTP_NOT_FOUND]) - def create_group(self, payload, parent=None, skip_exists=False): - """Create a group in the Realm. + def create_group( + self, + payload: dict, + parent: str | None = None, + skip_exists: bool = False, + ) -> str | None: + """ + Create a group in the Realm. GroupRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation @@ -1175,25 +1350,31 @@ class KeycloakAdmin: if parent is None: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_GROUPS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUPS.format(**params_path), + data=json.dumps(payload), ) else: params_path = {"realm-name": self.connection.realm_name, "id": parent} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) try: _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] except KeyError: - return + return None - def update_group(self, group_id, payload): - """Update group, ignores subgroups. + def update_group(self, group_id: str, payload: dict) -> dict | bytes: + """ + Update group, ignores subgroups. GroupRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation @@ -1208,12 +1389,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUP.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def groups_count(self, query=None): - """Count groups. + def groups_count(self, query: dict | None = None) -> dict: + """ + Count groups. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_groups @@ -1223,15 +1410,17 @@ class KeycloakAdmin: :return: Keycloak Server Response :rtype: dict """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), **query + urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - def group_set_permissions(self, group_id, enabled=True): - """Enable/Disable permissions for a group. + def group_set_permissions(self, group_id: str, enabled: bool = True) -> bytes: + """ + Enable/Disable permissions for a group. Cannot delete group if disabled @@ -1249,8 +1438,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - def group_user_add(self, user_id, group_id): - """Add user to group (user_id and group_id). + def group_user_add(self, user_id: str, group_id: str) -> bytes: + """ + Add user to group (user_id and group_id). :param user_id: id of user :type user_id: str @@ -1265,12 +1455,18 @@ class KeycloakAdmin: "group-id": group_id, } data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None + urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), + data=None, + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def group_user_remove(self, user_id, group_id): - """Remove user from group (user_id and group_id). + def group_user_remove(self, user_id: str, group_id: str) -> bytes: + """ + Remove user from group (user_id and group_id). :param user_id: id of user :type user_id: str @@ -1285,12 +1481,17 @@ class KeycloakAdmin: "group-id": group_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path) + urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def delete_group(self, group_id): - """Delete a group in the Realm. + def delete_group(self, group_id: str) -> dict | bytes: + """ + Delete a group in the Realm. :param group_id: id of group to delete :type group_id: str @@ -1299,10 +1500,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_GROUP.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_clients(self): - """Get clients. + def get_clients(self) -> list: + """ + Get clients. Returns a list of clients belonging to the realm @@ -1316,8 +1522,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENTS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client(self, client_id): - """Get representation of the client. + def get_client(self, client_id: str) -> dict: + """ + Get representation of the client. ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1331,8 +1538,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENT.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_id(self, client_id): - """Get internal keycloak client id from client-id. + def get_client_id(self, client_id: str) -> str | None: + """ + Get internal keycloak client id from client-id. This is required for further actions against this client. @@ -1344,7 +1552,8 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), clientId=client_id + urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), + clientId=client_id, ) data_response = raise_error_from_response(data_raw, KeycloakGetError) @@ -1354,8 +1563,9 @@ class KeycloakAdmin: return None - def get_client_authz_settings(self, client_id): - """Get authorization json from client. + def get_client_authz_settings(self, client_id: str) -> dict: + """ + Get authorization json from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1365,12 +1575,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_client_authz_resource(self, client_id, payload, skip_exists=False): - """Create resources of client. + def create_client_authz_resource( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> dict | bytes: + """ + Create resources of client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1392,11 +1608,20 @@ class KeycloakAdmin: max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def update_client_authz_resource(self, client_id, resource_id, payload): - """Update resource of client. + def update_client_authz_resource( + self, + client_id: str, + resource_id: str, + payload: dict, + ) -> dict | bytes: + """ + Update resource of client. Any parameter missing from the ResourceRepresentation in the payload WILL be set to default by the Keycloak server. @@ -1426,10 +1651,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_client_authz_resource(self, client_id: str, resource_id: str): - """Delete a client resource. + def delete_client_authz_resource(self, client_id: str, resource_id: str) -> bytes: + """ + Delete a client resource. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1447,12 +1677,17 @@ class KeycloakAdmin: "resource-id": resource_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def get_client_authz_resources(self, client_id): - """Get resources from client. + def get_client_authz_resources(self, client_id: str) -> list: + """ + Get resources from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1462,12 +1697,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_authz_resource(self, client_id: str, resource_id: str): - """Get a client resource. + def get_client_authz_resource(self, client_id: str, resource_id: str) -> dict | bytes: + """ + Get a client resource. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1485,12 +1722,18 @@ class KeycloakAdmin: "resource-id": resource_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def create_client_authz_role_based_policy(self, client_id, payload, skip_exists=False): - """Create role-based policy of client. + def create_client_authz_role_based_policy( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> dict | bytes: + """ + Create role-based policy of client. Payload example:: @@ -1525,11 +1768,20 @@ class KeycloakAdmin: max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def create_client_authz_policy(self, client_id, payload, skip_exists=False): - """Create an authz policy of client. + def create_client_authz_policy( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> dict | bytes: + """ + Create an authz policy of client. Payload example:: @@ -1564,11 +1816,20 @@ class KeycloakAdmin: permission=False, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def create_client_authz_resource_based_permission(self, client_id, payload, skip_exists=False): - """Create resource-based permission of client. + def create_client_authz_resource_based_permission( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> bytes: + """ + Create resource-based permission of client. Payload example:: @@ -1604,11 +1865,15 @@ class KeycloakAdmin: max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def get_client_authz_scopes(self, client_id): - """Get scopes from client. + def get_client_authz_scopes(self, client_id: str) -> list: + """ + Get scopes from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1618,12 +1883,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_client_authz_scopes(self, client_id, payload): - """Create scopes for client. + def create_client_authz_scopes(self, client_id: str, payload: dict) -> bytes: + """ + Create scopes for client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1640,10 +1907,15 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - def get_client_authz_permissions(self, client_id): - """Get permissions from client. + def get_client_authz_permissions(self, client_id: str) -> list: + """ + Get permissions from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1653,12 +1925,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_authz_policies(self, client_id): - """Get policies from client. + def get_client_authz_policies(self, client_id: str) -> list: + """ + Get policies from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1674,8 +1948,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_client_authz_policy(self, client_id, policy_id): - """Delete a policy from client. + def delete_client_authz_policy(self, client_id: str, policy_id: str) -> dict | bytes: + """ + Delete a policy from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1692,12 +1967,17 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def get_client_authz_policy(self, client_id, policy_id): - """Get a policy from client. + def get_client_authz_policy(self, client_id: str, policy_id: str) -> dict: + """ + Get a policy from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1714,12 +1994,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_service_account_user(self, client_id): - """Get service account user from client. + def get_client_service_account_user(self, client_id: str) -> dict: + """ + Get service account user from client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1729,12 +2010,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_default_client_scopes(self, client_id): - """Get all default client scopes from client. + def get_client_default_client_scopes(self, client_id: str) -> list: + """ + Get all default client scopes from client. :param client_id: id of the client in which the new default client scope should be added :type client_id: str @@ -1744,12 +2026,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def add_client_default_client_scope(self, client_id, client_scope_id, payload): - """Add a client scope to the default client scopes from client. + def add_client_default_client_scope( + self, + client_id: str, + client_scope_id: str, + payload: dict, + ) -> bytes: + """ + Add a client scope to the default client scopes from client. Payload example:: @@ -1780,8 +2068,13 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - def delete_client_default_client_scope(self, client_id, client_scope_id): - """Delete a client scope from the default client scopes of the client. + def delete_client_default_client_scope( + self, + client_id: str, + client_scope_id: str, + ) -> dict | bytes: + """ + Delete a client scope from the default client scopes of the client. :param client_id: id of the client in which the default client scope should be deleted :type client_id: str @@ -1789,7 +2082,7 @@ class KeycloakAdmin: :type client_scope_id: str :return: list of client scopes with id and name - :rtype: list + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -1797,12 +2090,13 @@ class KeycloakAdmin: "client_scope_id": client_scope_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def get_client_optional_client_scopes(self, client_id): - """Get all optional client scopes from client. + def get_client_optional_client_scopes(self, client_id: str) -> list: + """ + Get all optional client scopes from client. :param client_id: id of the client in which the new optional client scope should be added :type client_id: str @@ -1812,12 +2106,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def add_client_optional_client_scope(self, client_id, client_scope_id, payload): - """Add a client scope to the optional client scopes from client. + def add_client_optional_client_scope( + self, + client_id: str, + client_scope_id: str, + payload: dict, + ) -> bytes: + """ + Add a client scope to the optional client scopes from client. Payload example:: @@ -1848,8 +2148,13 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - def delete_client_optional_client_scope(self, client_id, client_scope_id): - """Delete a client scope from the optional client scopes of the client. + def delete_client_optional_client_scope( + self, + client_id: str, + client_scope_id: str, + ) -> dict | bytes: + """ + Delete a client scope from the optional client scopes of the client. :param client_id: id of the client in which the optional client scope should be deleted :type client_id: str @@ -1865,19 +2170,20 @@ class KeycloakAdmin: "client_scope_id": client_scope_id, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def create_initial_access_token(self, count: int = 1, expiration: int = 1): - """Create an initial access token. + def create_initial_access_token(self, count: int = 1, expiration: int = 1) -> dict | bytes: + """ + Create an initial access token. :param count: Number of clients that can be registered :type count: int :param expiration: Days until expireation :type expiration: int :return: initial access token - :rtype: str + :rtype: dict """ payload = {"count": count, "expiration": expiration} params_path = {"realm-name": self.connection.realm_name} @@ -1885,10 +2191,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_INITIAL_ACCESS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - def create_client(self, payload, skip_exists=False): - """Create a client. + def create_client(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a client. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1908,16 +2215,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def update_client(self, client_id, payload): - """Update a client. + def update_client(self, client_id: str, payload: dict) -> bytes: + """ + Update a client. :param client_id: Client id :type client_id: str @@ -1929,12 +2241,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def delete_client(self, client_id): - """Get representation of the client. + def delete_client(self, client_id: str) -> bytes: + """ + Get representation of the client. ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1946,10 +2264,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_CLIENT.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_client_installation_provider(self, client_id, provider_id): - """Get content for given installation provider. + def get_client_installation_provider(self, client_id: str, provider_id: str) -> list: + """ + Get content for given installation provider. Related documentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clients_resource @@ -1970,12 +2293,13 @@ class KeycloakAdmin: "provider-id": provider_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def get_realm_roles(self, brief_representation=True, search_text=""): - """Get all roles for the realm or client. + def get_realm_roles(self, brief_representation: bool = True, search_text: str = "") -> list: + """ + Get all roles for the realm or client. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -1994,12 +2318,19 @@ class KeycloakAdmin: params["search"] = search_text data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_realm_role_groups(self, role_name, query=None, brief_representation=True): - """Get role groups of realm by role name. + def get_realm_role_groups( + self, + role_name: str, + query: dict | None = None, + brief_representation: bool = True, + ) -> list: + """ + Get role groups of realm by role name. :param role_name: Name of the role. :type role_name: str @@ -2026,8 +2357,9 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def get_realm_role_members(self, role_name, query=None): - """Get role members of realm by role name. + def get_realm_role_members(self, role_name: str, query: dict | None = None) -> list: + """ + Get role members of realm by role name. :param role_name: Name of the role. :type role_name: str @@ -2037,14 +2369,16 @@ class KeycloakAdmin: :return: Keycloak Server Response (UserRepresentation) :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} return self.__fetch_all( - urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query + urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), + query, ) - def get_default_realm_role_id(self): - """Get the ID of the default realm role. + def get_default_realm_role_id(self) -> str: + """ + Get the ID of the default realm role. :return: Realm role ID :rtype: str @@ -2057,8 +2391,9 @@ class KeycloakAdmin: ] return default_realm_roles[0]["id"] - def get_realm_default_roles(self): - """Get all the default realm roles. + def get_realm_default_roles(self) -> list: + """ + Get all the default realm roles. :return: Keycloak Server Response (UserRepresentation) :rtype: list @@ -2068,12 +2403,13 @@ class KeycloakAdmin: "role-id": self.get_default_realm_role_id(), } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def remove_realm_default_roles(self, payload): - """Remove a set of default realm roles. + def remove_realm_default_roles(self, payload: dict) -> dict | bytes: + """ + Remove a set of default realm roles. :param payload: List of RoleRepresentations :type payload: list @@ -2090,8 +2426,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def add_realm_default_roles(self, payload): - """Add a set of default realm roles. + def add_realm_default_roles(self, payload: dict) -> dict | bytes: + """ + Add a set of default realm roles. :param payload: List of RoleRepresentations :type payload: list @@ -2108,8 +2445,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - def get_client_roles(self, client_id, brief_representation=True): - """Get all roles for the client. + def get_client_roles(self, client_id: str, brief_representation: bool = True) -> list: + """ + Get all roles for the client. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2124,12 +2462,14 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": client_id} params = {"briefRepresentation": brief_representation} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_role(self, client_id, role_name): - """Get client role by name. + def get_client_role(self, client_id: str, role_name: str) -> dict: + """ + Get client role by name. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2147,12 +2487,13 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_role_id(self, client_id, role_name): - """Get client role id by name. + def get_client_role_id(self, client_id: str, role_name: str) -> str | None: + """ + Get client role id by name. This is required for further actions with this role. @@ -2169,8 +2510,14 @@ class KeycloakAdmin: role = self.get_client_role(client_id, role_name) return role.get("id") - def create_client_role(self, client_role_id, payload, skip_exists=False): - """Create a client role. + def create_client_role( + self, + client_role_id: str, + payload: dict, + skip_exists: bool = False, + ) -> str: + """ + Create a client role. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2193,16 +2540,26 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": client_role_id} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def add_composite_client_roles_to_role(self, client_role_id, role_name, roles): - """Add composite roles to client role. + def add_composite_client_roles_to_role( + self, + client_role_id: str, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Add composite roles to client role. :param client_role_id: id of client (not client-id) :type client_role_id: str @@ -2223,10 +2580,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def remove_composite_client_roles_from_role(self, client_role_id, role_name, roles): - """Remove composite roles from a client role. + def remove_composite_client_roles_from_role( + self, + client_role_id: str, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Remove composite roles from a client role. :param client_role_id: id of client (not client-id) :type client_role_id: str @@ -2247,10 +2614,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def update_client_role(self, client_id, role_name, payload): - """Update a client role. + def update_client_role(self, client_id: str, role_name: str, payload: dict) -> bytes: + """ + Update a client role. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2270,12 +2642,18 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def delete_client_role(self, client_role_id, role_name): - """Delete a client role. + def delete_client_role(self, client_role_id: str, role_name: str) -> bytes: + """ + Delete a client role. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2293,12 +2671,17 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def assign_client_role(self, user_id, client_id, roles): - """Assign a client role to a user. + def assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> bytes: + """ + Assign a client role to a user. :param user_id: id of user :type user_id: str @@ -2319,10 +2702,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_client_role_members(self, client_id, role_name, **query): - """Get members by client role. + def get_client_role_members(self, client_id: str, role_name: str, **query: dict) -> list: + """ + Get members by client role. :param client_id: The client id :type client_id: str @@ -2340,11 +2728,13 @@ class KeycloakAdmin: "role-name": role_name, } return self.__fetch_all( - urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), query + urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), + query, ) - def get_client_role_groups(self, client_id, role_name, **query): - """Get group members by client role. + def get_client_role_groups(self, client_id: str, role_name: str, **query: dict) -> list: + """ + Get group members by client role. :param client_id: The client id :type client_id: str @@ -2362,11 +2752,13 @@ class KeycloakAdmin: "role-name": role_name, } return self.__fetch_all( - urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), query + urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), + query, ) - def get_role_by_id(self, role_id): - """Get a specific role’s representation. + def get_role_by_id(self, role_id: str) -> dict: + """ + Get a specific role's representation. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2374,16 +2766,17 @@ class KeycloakAdmin: :param role_id: id of role :type role_id: str :return: Keycloak server response (RoleRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def update_role_by_id(self, role_id, payload): - """Update the role. + def update_role_by_id(self, role_id: str, payload: dict) -> bytes: + """ + Update the role. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2400,10 +2793,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_role_by_id(self, role_id): - """Delete a role by its id. + def delete_role_by_id(self, role_id: str) -> bytes: + """ + Delete a role by its id. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2415,12 +2813,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def create_realm_role(self, payload, skip_exists=False): - """Create a new role for the realm or client. + def create_realm_role(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a new role for the realm or client. :param payload: The role (use RoleRepresentation) :type payload: dict @@ -2438,16 +2841,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def get_realm_role(self, role_name): - """Get realm role by role name. + def get_realm_role(self, role_name: str) -> dict: + """ + Get realm role by role name. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2459,12 +2867,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_realm_role_by_id(self, role_id: str): - """Get realm role by role id. + def get_realm_role_by_id(self, role_id: str) -> dict: + """ + Get realm role by role id. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -2476,12 +2885,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def update_realm_role(self, role_name, payload): - """Update a role for the realm by name. + def update_realm_role(self, role_name: str, payload: dict) -> bytes: + """ + Update a role for the realm by name. :param role_name: The name of the role to be updated :type role_name: str @@ -2495,10 +2905,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_realm_role(self, role_name): - """Delete a role for the realm by name. + def delete_realm_role(self, role_name: str) -> bytes: + """ + Delete a role for the realm by name. :param role_name: The role name :type role_name: str @@ -2507,12 +2922,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def add_composite_realm_roles_to_role(self, role_name, roles): - """Add composite roles to the role. + def add_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> bytes: + """ + Add composite roles to the role. :param role_name: The name of the role :type role_name: str @@ -2527,10 +2947,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def remove_composite_realm_roles_to_role(self, role_name, roles): - """Remove composite roles from the role. + def remove_composite_realm_roles_to_role(self, role_name: str, roles: str | list) -> bytes: + """ + Remove composite roles from the role. :param role_name: The name of the role :type role_name: str @@ -2545,10 +2970,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_composite_realm_roles_of_role(self, role_name): - """Get composite roles of the role. + def get_composite_realm_roles_of_role(self, role_name: str) -> list: + """ + Get composite roles of the role. :param role_name: The name of the role :type role_name: str @@ -2557,19 +2987,20 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def assign_realm_roles_to_client_scope(self, client_id, roles): - """Assign realm roles to a client's scope. + def assign_realm_roles_to_client_scope(self, client_id: str, roles: str | list) -> bytes: + """ + Assign realm roles to a client's scope. :param client_id: id of client (not client-id) :type client_id: str :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = {"realm-name": self.connection.realm_name, "id": client_id} @@ -2577,10 +3008,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_realm_roles_of_client_scope(self, client_id, roles): - """Delete realm roles of a client's scope. + def delete_realm_roles_of_client_scope(self, client_id: str, roles: str | list) -> bytes: + """ + Delete realm roles of a client's scope. :param client_id: id of client (not client-id) :type client_id: str @@ -2595,24 +3031,35 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_realm_roles_of_client_scope(self, client_id): - """Get all realm roles for a client's scope. + def get_realm_roles_of_client_scope(self, client_id: str) -> list: + """ + Get all realm roles for a client's scope. :param client_id: id of client (not client-id) :type client_id: str :return: Keycloak server response (array RoleRepresentation) - :rtype: dict + :rtype: list """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles): - """Assign client roles to a client's dedicated scope. + def assign_client_roles_to_client_scope( + self, + client_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Assign client roles to a client's dedicated scope. To assign roles to a client scope, use add_client_specific_roles_to_client_scope. @@ -2623,7 +3070,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -2635,10 +3082,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles): - """Delete client roles of a client's dedicated scope. + def delete_client_roles_of_client_scope( + self, + client_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles of a client's dedicated scope. To delete roles from a client scope, use remove_client_specific_roles_of_client_scope. @@ -2649,7 +3106,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -2661,10 +3118,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_client_roles_of_client_scope(self, client_id, client_roles_owner_id): - """Get all client roles for a client's dedicated scope. + def get_client_roles_of_client_scope(self, client_id: str, client_roles_owner_id: str) -> list: + """ + Get all client roles for a client's dedicated scope. To get roles for a client scope, use get_client_specific_roles_of_client_scope. @@ -2673,7 +3135,7 @@ class KeycloakAdmin: :param client_roles_owner_id: id of client (not client-id) who has the roles :type client_roles_owner_id: str :return: Keycloak server response (array RoleRepresentation) - :rtype: dict + :rtype: list """ params_path = { "realm-name": self.connection.realm_name, @@ -2681,12 +3143,13 @@ class KeycloakAdmin: "client": client_roles_owner_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def assign_realm_roles(self, user_id, roles): - """Assign realm roles to a user. + def assign_realm_roles(self, user_id: str, roles: str | list) -> bytes: + """ + Assign realm roles to a user. :param user_id: id of user :type user_id: str @@ -2701,10 +3164,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_realm_roles_of_user(self, user_id, roles): - """Delete realm roles of a user. + def delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> bytes: + """ + Delete realm roles of a user. :param user_id: id of user :type user_id: str @@ -2719,10 +3187,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_realm_roles_of_user(self, user_id): - """Get all realm roles for a user. + def get_realm_roles_of_user(self, user_id: str) -> list: + """ + Get all realm roles for a user. :param user_id: id of user :type user_id: str @@ -2731,12 +3204,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_available_realm_roles_of_user(self, user_id): - """Get all available (i.e. unassigned) realm roles for a user. + def get_available_realm_roles_of_user(self, user_id: str) -> list: + """ + Get all available (i.e. unassigned) realm roles for a user. :param user_id: id of user :type user_id: str @@ -2745,12 +3219,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path) + urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_composite_realm_roles_of_user(self, user_id, brief_representation=True): - """Get all composite (i.e. implicit) realm roles for a user. + def get_composite_realm_roles_of_user( + self, + user_id: str, + brief_representation: bool = True, + ) -> list: + """ + Get all composite (i.e. implicit) realm roles for a user. :param user_id: id of user :type user_id: str @@ -2762,12 +3241,14 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": user_id} params = {"briefRepresentation": brief_representation} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params + urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def assign_group_realm_roles(self, group_id, roles): - """Assign realm roles to a group. + def assign_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + """ + Assign realm roles to a group. :param group_id: id of group :type group_id: str @@ -2782,10 +3263,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_group_realm_roles(self, group_id, roles): - """Delete realm roles of a group. + def delete_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + """ + Delete realm roles of a group. :param group_id: id of group :type group_id: str @@ -2800,10 +3286,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_group_realm_roles(self, group_id, brief_representation=True): - """Get all realm roles for a group. + def get_group_realm_roles(self, group_id: str, brief_representation: bool = True) -> list: + """ + Get all realm roles for a group. :param group_id: id of the group :type group_id: str @@ -2815,12 +3306,14 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": group_id} params = {"briefRepresentation": brief_representation} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def assign_group_client_roles(self, group_id, client_id, roles): - """Assign client roles to a group. + def assign_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> bytes: + """ + Assign client roles to a group. :param group_id: id of group :type group_id: str @@ -2841,10 +3334,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_group_client_roles(self, group_id, client_id): - """Get client roles of a group. + def get_group_client_roles(self, group_id: str, client_id: str) -> list: + """ + Get client roles of a group. :param group_id: id of group :type group_id: str @@ -2859,12 +3357,13 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_group_client_roles(self, group_id, client_id, roles): - """Delete client roles of a group. + def delete_group_client_roles(self, group_id: str, client_id: str, roles: str | list) -> bytes: + """ + Delete client roles of a group. :param group_id: id of group :type group_id: str @@ -2885,10 +3384,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_all_roles_of_user(self, user_id): - """Get all level roles for a user. + def get_all_roles_of_user(self, user_id: str) -> list: + """ + Get all level roles for a user. :param user_id: id of user :type user_id: str @@ -2897,12 +3401,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_roles_of_user(self, user_id, client_id): - """Get all client roles for a user. + def get_client_roles_of_user(self, user_id: str, client_id: str) -> list: + """ + Get all client roles for a user. :param user_id: id of user :type user_id: str @@ -2912,11 +3417,14 @@ class KeycloakAdmin: :rtype: list """ return self._get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, user_id, client_id + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, + user_id, + client_id, ) - def get_available_client_roles_of_user(self, user_id, client_id): - """Get available client role-mappings for a user. + def get_available_client_roles_of_user(self, user_id: str, client_id: str) -> list: + """ + Get available client role-mappings for a user. :param user_id: id of user :type user_id: str @@ -2926,11 +3434,19 @@ class KeycloakAdmin: :rtype: list """ return self._get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, + user_id, + client_id, ) - def get_composite_client_roles_of_user(self, user_id, client_id, brief_representation=False): - """Get composite client role-mappings for a user. + def get_composite_client_roles_of_user( + self, + user_id: str, + client_id: str, + brief_representation: bool = False, + ) -> list: + """ + Get composite client role-mappings for a user. :param user_id: id of user :type user_id: str @@ -2943,13 +3459,21 @@ class KeycloakAdmin: """ params = {"briefRepresentation": brief_representation} return self._get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id, **params + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, + user_id, + client_id, + **params, ) def _get_client_roles_of_user( - self, client_level_role_mapping_url, user_id, client_id, **params - ): - """Get client roles of a single user helper. + self, + client_level_role_mapping_url: str, + user_id: str, + client_id: str, + **params: dict, + ) -> list: + """ + Get client roles of a single user helper. :param client_level_role_mapping_url: Url for the client role mapping :type client_level_role_mapping_url: str @@ -2968,12 +3492,19 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = self.connection.raw_get( - client_level_role_mapping_url.format(**params_path), **params + client_level_role_mapping_url.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_client_roles_of_user(self, user_id, client_id, roles): - """Delete client roles from a user. + def delete_client_roles_of_user( + self, + user_id: str, + client_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles from a user. :param user_id: id of user :type user_id: str @@ -2994,10 +3525,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_authentication_flows(self): - """Get authentication flows. + def get_authentication_flows(self) -> list: + """ + Get authentication flows. Returns all flow details @@ -3011,8 +3547,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_FLOWS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def get_authentication_flow_for_id(self, flow_id): - """Get one authentication flow by it's id. + def get_authentication_flow_for_id(self, flow_id: str) -> dict: + """ + Get one authentication flow by it's id. Returns all flow details @@ -3026,12 +3563,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-id": flow_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_authentication_flow(self, payload, skip_exists=False): - """Create a new authentication flow. + def create_authentication_flow(self, payload: dict, skip_exists: bool = False) -> bytes: + """ + Create a new authentication flow. AuthenticationFlowRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationflowrepresentation @@ -3045,14 +3583,19 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_FLOWS.format(**params_path), + data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def copy_authentication_flow(self, payload, flow_alias): - """Copy existing authentication flow under a new name. + def copy_authentication_flow(self, payload: dict, flow_alias: str) -> bytes: + """ + Copy existing authentication flow under a new name. The new name is given as 'newName' attribute of the passed payload. @@ -3065,12 +3608,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def delete_authentication_flow(self, flow_id): - """Delete authentication flow. + def delete_authentication_flow(self, flow_id: str) -> bytes: + """ + Delete authentication flow. AuthenticationInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationinforepresentation @@ -3082,10 +3631,15 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": flow_id} data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_FLOW.format(**params_path)) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_authentication_flow_executions(self, flow_alias): - """Get authentication flow executions. + def get_authentication_flow_executions(self, flow_alias: str) -> list: + """ + Get authentication flow executions. Returns all execution steps @@ -3096,12 +3650,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def update_authentication_flow_executions(self, payload, flow_alias): - """Update an authentication flow execution. + def update_authentication_flow_executions(self, payload: dict, flow_alias: str) -> bytes: + """ + Update an authentication flow execution. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -3118,10 +3673,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[202, 204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], + ) - def get_authentication_flow_execution(self, execution_id): - """Get authentication flow execution. + def get_authentication_flow_execution(self, execution_id: str) -> list: + """ + Get authentication flow execution. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -3133,12 +3693,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_authentication_flow_execution(self, payload, flow_alias): - """Create an authentication flow execution. + def create_authentication_flow_execution(self, payload: dict, flow_alias: str) -> bytes: + """ + Create an authentication flow execution. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -3155,10 +3716,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - def delete_authentication_flow_execution(self, execution_id): - """Delete authentication flow execution. + def delete_authentication_flow_execution(self, execution_id: str) -> bytes: + """ + Delete authentication flow execution. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -3170,12 +3736,22 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def create_authentication_flow_subflow(self, payload, flow_alias, skip_exists=False): - """Create a new sub authentication flow for a given authentication flow. + def create_authentication_flow_subflow( + self, + payload: dict, + flow_alias: str, + skip_exists: bool = False, + ) -> bytes: + """ + Create a new sub authentication flow for a given authentication flow. AuthenticationFlowRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationflowrepresentation @@ -3195,23 +3771,28 @@ class KeycloakAdmin: data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - def get_authenticator_providers(self): - """Get authenticator providers list. + def get_authenticator_providers(self) -> list: + """ + Get authenticator providers list. :return: Authenticator providers :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_authenticator_provider_config_description(self, provider_id): - """Get authenticator's provider configuration description. + def get_authenticator_provider_config_description(self, provider_id: str) -> dict: + """ + Get authenticator's provider configuration description. AuthenticatorConfigInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticatorconfiginforepresentation @@ -3223,12 +3804,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "provider-id": provider_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_authenticator_config(self, config_id): - """Get authenticator configuration. + def get_authenticator_config(self, config_id: str) -> dict: + """ + Get authenticator configuration. Returns all configuration details. @@ -3239,12 +3821,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def update_authenticator_config(self, payload, config_id): - """Update an authenticator configuration. + def update_authenticator_config(self, payload: dict, config_id: str) -> bytes: + """ + Update an authenticator configuration. AuthenticatorConfigRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticatorconfigrepresentation @@ -3261,10 +3844,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def delete_authenticator_config(self, config_id): - """Delete a authenticator configuration. + def delete_authenticator_config(self, config_id: str) -> bytes: + """ + Delete a authenticator configuration. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authentication_management_resource @@ -3275,12 +3863,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def sync_users(self, storage_id, action): - """Trigger user sync from provider. + def sync_users(self, storage_id: str, action: str) -> bytes: + """ + Trigger user sync from provider. :param storage_id: The id of the user storage provider :type storage_id: str @@ -3300,8 +3893,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - def get_client_scopes(self): - """Get client scopes. + def get_client_scopes(self) -> list: + """ + Get client scopes. Get representation of the client scopes for the realm where we are connected to https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -3311,12 +3905,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_scope(self, client_scope_id): - """Get client scope. + def get_client_scope(self, client_scope_id: str) -> dict: + """ + Get client scope. Get representation of the client scopes for the realm where we are connected to https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -3328,12 +3923,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_scope_by_name(self, client_scope_name): - """Get client scope by name. + def get_client_scope_by_name(self, client_scope_name: str) -> dict: + """ + Get client scope by name. Get representation of the client scope identified by the client scope name. @@ -3350,8 +3946,9 @@ class KeycloakAdmin: return None - def create_client_scope(self, payload, skip_exists=False): - """Create a client scope. + def create_client_scope(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a client scope. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -3371,16 +3968,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def update_client_scope(self, client_scope_id, payload): - """Update a client scope. + def update_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + """ + Update a client scope. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_client_scopes_resource @@ -3394,12 +3996,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def delete_client_scope(self, client_scope_id): - """Delete existing client scope. + def delete_client_scope(self, client_scope_id: str) -> bytes: + """ + Delete existing client scope. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_client_scopes_resource @@ -3411,12 +4019,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def get_mappers_from_client_scope(self, client_scope_id): - """Get a list of all mappers connected to the client scope. + def get_mappers_from_client_scope(self, client_scope_id: str) -> list: + """ + Get a list of all mappers connected to the client scope. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource :param client_scope_id: Client scope id @@ -3426,12 +4039,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def add_mapper_to_client_scope(self, client_scope_id, payload): - """Add a mapper to a client scope. + def add_mapper_to_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + """ + Add a mapper to a client scope. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_create_mapper @@ -3443,16 +4057,23 @@ class KeycloakAdmin: :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} - data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - - def delete_mapper_from_client_scope(self, client_scope_id, protocol_mapper_id): - """Delete a mapper from a client scope. + def delete_mapper_from_client_scope( + self, + client_scope_id: str, + protocol_mapper_id: str, + ) -> bytes: + """ + Delete a mapper from a client scope. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_delete_mapper @@ -3468,14 +4089,23 @@ class KeycloakAdmin: "scope-id": client_scope_id, "protocol-mapper-id": protocol_mapper_id, } - data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def update_mapper_in_client_scope(self, client_scope_id, protocol_mapper_id, payload): - """Update an existing protocol mapper in a client scope. + def update_mapper_in_client_scope( + self, + client_scope_id: str, + protocol_mapper_id: str, + payload: dict, + ) -> bytes: + """ + Update an existing protocol mapper in a client scope. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource @@ -3494,16 +4124,19 @@ class KeycloakAdmin: "scope-id": client_scope_id, "protocol-mapper-id": protocol_mapper_id, } - data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - - def get_default_default_client_scopes(self): - """Get default default client scopes. + def get_default_default_client_scopes(self) -> list: + """ + Get default default client scopes. Return list of default default client scopes @@ -3512,12 +4145,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_default_default_client_scope(self, scope_id): - """Delete default default client scope. + def delete_default_default_client_scope(self, scope_id: str) -> bytes: + """ + Delete default default client scope. :param scope_id: default default client scope id :type scope_id: str @@ -3526,12 +4160,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def add_default_default_client_scope(self, scope_id): - """Add default default client scope. + def add_default_default_client_scope(self, scope_id: str) -> bytes: + """ + Add default default client scope. :param scope_id: default default client scope id :type scope_id: str @@ -3544,10 +4183,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_default_optional_client_scopes(self): - """Get default optional client scopes. + def get_default_optional_client_scopes(self) -> list: + """ + Get default optional client scopes. Return list of default optional client scopes @@ -3556,12 +4200,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def delete_default_optional_client_scope(self, scope_id): - """Delete default optional client scope. + def delete_default_optional_client_scope(self, scope_id: str) -> bytes: + """ + Delete default optional client scope. :param scope_id: default optional client scope id :type scope_id: str @@ -3570,12 +4215,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def add_default_optional_client_scope(self, scope_id): - """Add default optional client scope. + def add_default_optional_client_scope(self, scope_id: str) -> bytes: + """ + Add default optional client scope. :param scope_id: default optional client scope id :type scope_id: str @@ -3588,12 +4238,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) def add_client_specific_roles_to_client_scope( - self, client_scope_id, client_roles_owner_id, roles - ): - """Assign client roles to a client scope. + self, + client_scope_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Assign client roles to a client scope. To assign roles to a client's dedicated scope, use assign_client_roles_to_client_scope. @@ -3604,7 +4262,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3616,12 +4274,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) def remove_client_specific_roles_of_client_scope( - self, client_scope_id, client_roles_owner_id, roles - ): - """Delete client roles of a client scope. + self, + client_scope_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles of a client scope. To delete roles from a client's dedicated scope, use delete_client_roles_of_client_scope. @@ -3632,7 +4298,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -3644,10 +4310,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - def get_client_specific_roles_of_client_scope(self, client_scope_id, client_roles_owner_id): - """Get client roles for a client scope, for a specific client. + def get_client_specific_roles_of_client_scope( + self, + client_scope_id: str, + client_roles_owner_id: str, + ) -> list: + """ + Get client roles for a client scope, for a specific client. To get roles for a client's dedicated scope, use get_client_roles_of_client_scope. @@ -3656,7 +4331,7 @@ class KeycloakAdmin: :param client_roles_owner_id: id of client (not client-id) who has the roles :type client_roles_owner_id: str :return: Keycloak server response (array RoleRepresentation) - :rtype: dict + :rtype: list """ params_path = { "realm-name": self.connection.realm_name, @@ -3664,12 +4339,13 @@ class KeycloakAdmin: "client-id": client_roles_owner_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_all_roles_of_client_scope(self, client_scope_id): - """Get all client roles for a client scope. + def get_all_roles_of_client_scope(self, client_scope_id: str) -> list: + """ + Get all client roles for a client scope. To get roles for a client's dedicated scope, use get_client_roles_of_client_scope. @@ -3681,12 +4357,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_mappers_from_client(self, client_id): - """List of all client mappers. + def get_mappers_from_client(self, client_id: str) -> list: + """ + List of all client mappers. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocolmapperrepresentation @@ -3696,15 +4373,14 @@ class KeycloakAdmin: :rtype: list """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), ) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) - - def add_mapper_to_client(self, client_id, payload): - """Add a mapper to a client. + def add_mapper_to_client(self, client_id: str, payload: dict) -> bytes: + """ + Add a mapper to a client. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_create_mapper @@ -3716,16 +4392,19 @@ class KeycloakAdmin: :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - - def update_client_mapper(self, client_id, mapper_id, payload): - """Update client mapper. + def update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> bytes: + """ + Update client mapper. :param client_id: The id of the client :type client_id: str @@ -3741,16 +4420,19 @@ class KeycloakAdmin: "id": client_id, "protocol-mapper-id": mapper_id, } - data_raw = self.connection.raw_put( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - - def remove_client_mapper(self, client_id, client_mapper_id): - """Remove a mapper from the client. + def remove_client_mapper(self, client_id: str, client_mapper_id: str) -> bytes: + """ + Remove a mapper from the client. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource @@ -3766,14 +4448,18 @@ class KeycloakAdmin: "id": client_id, "protocol-mapper-id": client_mapper_id, } - data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def generate_client_secrets(self, client_id): - """Generate a new secret for the client. + def generate_client_secrets(self, client_id: str) -> bytes: + """ + Generate a new secret for the client. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_regeneratesecret @@ -3784,12 +4470,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None + urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), + data=None, ) return raise_error_from_response(data_raw, KeycloakPostError) - def get_client_secrets(self, client_id): - """Get representation of the client secrets. + def get_client_secrets(self, client_id: str) -> list: + """ + Get representation of the client secrets. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsecret @@ -3800,12 +4488,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_components(self, query=None): - """Get components. + def get_components(self, query: dict | None = None) -> list: + """ + Get components. Return a list of components, filtered according to query parameters @@ -3817,15 +4506,18 @@ class KeycloakAdmin: :return: components list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_component(self, payload): - """Create a new component. + def create_component(self, payload: dict) -> str: + """ + Create a new component. ComponentRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_componentrepresentation @@ -3837,14 +4529,16 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), + data=json.dumps(payload), ) - raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - def get_component(self, component_id): - """Get representation of the component. + def get_component(self, component_id: str) -> dict: + """ + Get representation of the component. :param component_id: Component id @@ -3860,8 +4554,9 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_COMPONENT.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def update_component(self, component_id, payload): - """Update the component. + def update_component(self, component_id: str, payload: dict) -> bytes: + """ + Update the component. :param component_id: Component id :type component_id: str @@ -3873,12 +4568,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def delete_component(self, component_id): - """Delete the component. + def delete_component(self, component_id: str) -> bytes: + """ + Delete the component. :param component_id: Component id :type component_id: str @@ -3887,12 +4588,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_COMPONENT.format(**params_path) + urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def get_keys(self): - """Get keys. + def get_keys(self) -> list: + """ + Get keys. Return a list of keys, filtered according to query parameters @@ -3904,12 +4610,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None + urls_patterns.URL_ADMIN_KEYS.format(**params_path), + data=None, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_admin_events(self, query=None): - """Get Administrative events. + def get_admin_events(self, query: dict | None = None) -> list: + """ + Get Administrative events. Return a list of events, filtered according to query parameters @@ -3922,15 +4630,18 @@ class KeycloakAdmin: :return: events list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_ADMIN_EVENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_ADMIN_EVENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_events(self, query=None): - """Get events. + def get_events(self, query: dict | None = None) -> list: + """ + Get events. Return a list of events, filtered according to query parameters @@ -3942,15 +4653,18 @@ class KeycloakAdmin: :return: events list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_USER_EVENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_USER_EVENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - def set_events(self, payload): - """Set realm events configuration. + def set_events(self, payload: dict) -> bytes: + """ + Set realm events configuration. RealmEventsConfigRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmeventsconfigrepresentation @@ -3962,12 +4676,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - def get_client_all_sessions(self, client_id, query=None): - """Get sessions associated with the client. + def get_client_all_sessions(self, client_id: str, query: dict | None = None) -> list: + """ + Get sessions associated with the client. UserSessionRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation @@ -3987,8 +4707,9 @@ class KeycloakAdmin: return self.__fetch_all(url, query) - def get_client_sessions_stats(self): - """Get current session count for all clients with active sessions. + def get_client_sessions_stats(self) -> dict: + """ + Get current session count for all clients with active sessions. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsessionstats @@ -3997,12 +4718,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_management_permissions(self, client_id): - """Get management permissions for a client. + def get_client_management_permissions(self, client_id: str) -> list: + """ + Get management permissions for a client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4012,12 +4734,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def update_client_management_permissions(self, payload, client_id): - """Update management permissions for a client. + def update_client_management_permissions(self, payload: dict, client_id: str) -> bytes: + """ + Update management permissions for a client. ManagementPermissionReference https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_managementpermissionreference @@ -4041,10 +4764,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) - def get_client_authz_policy_scopes(self, client_id, policy_id): - """Get scopes for a given policy. + def get_client_authz_policy_scopes(self, client_id: str, policy_id: str) -> list: + """ + Get scopes for a given policy. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4060,12 +4784,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_authz_policy_resources(self, client_id, policy_id): - """Get resources for a given policy. + def get_client_authz_policy_resources(self, client_id: str, policy_id: str) -> list: + """ + Get resources for a given policy. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4081,12 +4806,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_client_authz_scope_permission(self, client_id, scope_id): - """Get permissions for a given scope. + def get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> list: + """ + Get permissions for a given scope. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4102,12 +4828,13 @@ class KeycloakAdmin: "scope-id": scope_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def create_client_authz_scope_permission(self, payload, client_id): - """Create permissions for a authz scope. + def create_client_authz_scope_permission(self, payload: dict, client_id: str) -> bytes: + """ + Create permissions for a authz scope. Payload example:: @@ -4135,10 +4862,20 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - def update_client_authz_scope_permission(self, payload, client_id, scope_id): - """Update permissions for a given scope. + def update_client_authz_scope_permission( + self, + payload: dict, + client_id: str, + scope_id: str, + ) -> bytes: + """ + Update permissions for a given scope. Payload example:: @@ -4172,10 +4909,16 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) - def update_client_authz_resource_permission(self, payload, client_id, resource_id): - """Update permissions for a given resource. + def update_client_authz_resource_permission( + self, + payload: dict, + client_id: str, + resource_id: str, + ) -> bytes: + """ + Update permissions for a given resource. Payload example:: @@ -4209,10 +4952,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) - def get_client_authz_client_policies(self, client_id): - """Get policies for a given client. + def get_client_authz_client_policies(self, client_id: str) -> list: + """ + Get policies for a given client. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4222,12 +4966,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def get_client_authz_permission_associated_policies(self, client_id, policy_id): - """Get associated policies for a given client permission. + def get_client_authz_permission_associated_policies( + self, + client_id: str, + policy_id: str, + ) -> list: + """ + Get associated policies for a given client permission. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -4245,13 +4994,14 @@ class KeycloakAdmin: } data_raw = self.connection.raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY_ASSOCIATED_POLICIES.format( - **params_path - ) + **params_path, + ), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def create_client_authz_client_policy(self, payload, client_id): - """Create a new policy for a given client. + def create_client_authz_client_policy(self, payload: dict, client_id: str) -> bytes: + """ + Create a new policy for a given client. Payload example:: @@ -4276,10 +5026,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - def get_composite_client_roles_of_group(self, client_id, group_id, brief_representation=True): - """Get the composite client roles of the given group for the given client. + def get_composite_client_roles_of_group( + self, + client_id: str, + group_id: str, + brief_representation: bool = True, + ) -> list: + """ + Get the composite client roles of the given group for the given client. :param client_id: id of the client. :type client_id: str @@ -4297,12 +5057,14 @@ class KeycloakAdmin: } params = {"briefRepresentation": brief_representation} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params + urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_role_client_level_children(self, client_id, role_id): - """Get the child roles of which the given composite client role is composed of. + def get_role_client_level_children(self, client_id: str, role_id: str) -> list: + """ + Get the child roles of which the given composite client role is composed of. :param client_id: id of the client. :type client_id: str @@ -4317,12 +5079,13 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def upload_certificate(self, client_id, certcont): - """Upload a new certificate for the client. + def upload_certificate(self, client_id: str, certcont: str) -> dict: + """ + Upload a new certificate for the client. :param client_id: id of the client. :type client_id: str @@ -4348,8 +5111,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - def get_required_action_by_alias(self, action_alias): - """Get a required action by its alias. + def get_required_action_by_alias(self, action_alias: str) -> dict | None: + """ + Get a required action by its alias. :param action_alias: the alias of the required action. :type action_alias: str @@ -4362,20 +5126,22 @@ class KeycloakAdmin: return a return None - def get_required_actions(self): - """Get the required actions for the realms. + def get_required_actions(self) -> list: + """ + Get the required actions for the realms. :return: the required actions (list of RequiredActionProviderRepresentation). :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path) + urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def update_required_action(self, action_alias, payload): - """Update a required action. + def update_required_action(self, action_alias: str, payload: dict) -> dict: + """ + Update a required action. :param action_alias: the action alias. :type action_alias: str @@ -4388,12 +5154,14 @@ class KeycloakAdmin: payload = json.dumps(payload) params_path = {"realm-name": self.connection.realm_name, "action-alias": action_alias} data_raw = self.connection.raw_put( - urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), data=payload + urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), + data=payload, ) return raise_error_from_response(data_raw, KeycloakPutError) - def get_bruteforce_detection_status(self, user_id): - """Get bruteforce detection status for user. + def get_bruteforce_detection_status(self, user_id: str) -> dict: + """ + Get bruteforce detection status for user. :param user_id: User id :type user_id: str @@ -4402,12 +5170,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - def clear_bruteforce_attempts_for_user(self, user_id): - """Clear bruteforce attempts for user. + def clear_bruteforce_attempts_for_user(self, user_id: str) -> dict: + """ + Clear bruteforce attempts for user. :param user_id: User id :type user_id: str @@ -4416,61 +5185,81 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def clear_all_bruteforce_attempts(self): - """Clear bruteforce attempts for all users in realm. + def clear_all_bruteforce_attempts(self) -> dict: + """ + Clear bruteforce attempts for all users in realm. :return: empty dictionary. :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_delete( - urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - def clear_keys_cache(self): - """Clear keys cache. + def clear_keys_cache(self) -> dict: + """ + Clear keys cache. :return: empty dictionary. :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - def clear_realm_cache(self): - """Clear realm cache. + def clear_realm_cache(self) -> dict: + """ + Clear realm cache. :return: empty dictionary. :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - def clear_user_cache(self): - """Clear user cache. + def clear_user_cache(self) -> dict: + """ + Clear user cache. :return: empty dictionary. :rtype: dict """ params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_post( - urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) # async functions start - async def a___fetch_all(self, url, query=None): - """Paginate asynchronously over get requests . + async def a___fetch_all(self, url: str, query: dict | None = None) -> list: + """ + Paginate asynchronously over get requests . Wrapper function to paginate GET requests. @@ -4494,7 +5283,8 @@ class KeycloakAdmin: while True: query["first"] = page * self.PAGE_SIZE partial_results = raise_error_from_response( - await self.connection.a_raw_get(url, **query), KeycloakGetError + await self.connection.a_raw_get(url, **query), + KeycloakGetError, ) if not partial_results: break @@ -4504,23 +5294,26 @@ class KeycloakAdmin: page += 1 return results - async def a___fetch_paginated(self, url, query=None): - """Make a specific paginated request asynchronously. + async def a___fetch_paginated(self, url: str, query: dict | None = None) -> list: + """ + Make a specific paginated request asynchronously. :param url: The url on which the query is executed :type url: str :param query: Pagination settings :type query: dict :returns: Response - :rtype: dict + :rtype: list """ query = query or {} return raise_error_from_response( - await self.connection.a_raw_get(url, **query), KeycloakGetError + await self.connection.a_raw_get(url, **query), + KeycloakGetError, ) async def a_get_current_realm(self) -> str: - """Return the currently configured realm asynchronously. + """ + Return the currently configured realm asynchronously. :returns: Currently configured realm name :rtype: str @@ -4528,15 +5321,17 @@ class KeycloakAdmin: return self.connection.realm_name async def a_change_current_realm(self, realm_name: str) -> None: - """Change the current realm asynchronously. + """ + Change the current realm asynchronously. :param realm_name: The name of the realm to be configured as current :type realm_name: str """ self.connection.realm_name = realm_name - async def a_import_realm(self, payload): - """Import a new realm asynchronously from a RealmRepresentation. + async def a_import_realm(self, payload: dict) -> dict: + """ + Import a new realm asynchronously from a RealmRepresentation. Realm name must be unique. @@ -4549,12 +5344,18 @@ class KeycloakAdmin: :rtype: dict """ data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALMS, + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - async def a_partial_import_realm(self, realm_name, payload): - """Partial import realm configuration asynchronously from PartialImportRepresentation. + async def a_partial_import_realm(self, realm_name: str, payload: dict) -> dict: + """ + Partial import realm configuration asynchronously from PartialImportRepresentation. Realm partialImport is used for modifying configuration of existing realm. @@ -4574,10 +5375,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - async def a_export_realm(self, export_clients=False, export_groups_and_role=False): - """Export the realm configurations asynchronously in the json format. + async def a_export_realm( + self, + export_clients: bool = False, + export_groups_and_role: bool = False, + ) -> dict: + """ + Export the realm configurations asynchronously in the json format. RealmRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_partialexport @@ -4603,8 +5409,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_get_realms(self): - """List all realms in asynchronouslyKeycloak deployment. + async def a_get_realms(self) -> list: + """ + List all realms in asynchronouslyKeycloak deployment. :return: realms list :rtype: list @@ -4612,8 +5419,9 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get(urls_patterns.URL_ADMIN_REALMS) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_realm(self, realm_name): - """Get a specific realm asynchronously. + async def a_get_realm(self, realm_name: str) -> dict: + """ + Get a specific realm asynchronously. RealmRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmrepresentation @@ -4625,12 +5433,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM.format(**params_path) + urls_patterns.URL_ADMIN_REALM.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_create_realm(self, payload, skip_exists=False): - """Create a realm asynchronously. + async def a_create_realm(self, payload: dict, skip_exists: bool = False) -> dict: + """ + Create a realm asynchronously. RealmRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmrepresentation @@ -4643,14 +5452,19 @@ class KeycloakAdmin: :rtype: dict """ data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALMS, + data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_update_realm(self, realm_name, payload): - """Update a realm asynchronously. + async def a_update_realm(self, realm_name: str, payload: dict) -> dict: + """ + Update a realm asynchronously. This will only update top level attributes and will ignore any user, role, or client information in the payload. @@ -4667,26 +5481,37 @@ class KeycloakAdmin: """ params_path = {"realm-name": realm_name} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALM.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_delete_realm(self, realm_name): - """Delete a realm asynchronously. + async def a_delete_realm(self, realm_name: str) -> bytes: + """ + Delete a realm asynchronously. :param realm_name: Realm name (not the realm id) :type realm_name: str :return: Http response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": realm_name} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_REALM.format(**params_path) + urls_patterns.URL_ADMIN_REALM.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_users(self, query=None): - """Get all users asynchronously. + async def a_get_users(self, query: dict | None = None) -> list: + """ + Get all users asynchronously. Return a list of users, filtered according to query parameters @@ -4707,8 +5532,9 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_create_idp(self, payload): - """Create an ID Provider asynchronously. + async def a_create_idp(self, payload: dict) -> dict: + """ + Create an ID Provider asynchronously. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identityproviderrepresentation @@ -4720,12 +5546,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDPS.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - async def a_update_idp(self, idp_alias, payload): - """Update an ID Provider asynchronously. + async def a_update_idp(self, idp_alias: str, payload: dict) -> bytes: + """ + Update an ID Provider asynchronously. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identity_providers_resource @@ -4735,16 +5567,22 @@ class KeycloakAdmin: :param: payload: The IdentityProviderRepresentation :type payload: dict :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDP.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_add_mapper_to_idp(self, idp_alias, payload): - """Create an ID Provider asynchronously. + async def a_add_mapper_to_idp(self, idp_alias: str, payload: dict) -> dict: + """ + Create an ID Provider asynchronously. IdentityProviderRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_identityprovidermapperrepresentation @@ -4758,12 +5596,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - async def a_update_mapper_in_idp(self, idp_alias, mapper_id, payload): - """Update an IdP mapper asynchronously. + async def a_update_mapper_in_idp(self, idp_alias: str, mapper_id: str, payload: dict) -> bytes: + """ + Update an IdP mapper asynchronously. IdentityProviderMapperRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_update @@ -4775,7 +5619,7 @@ class KeycloakAdmin: :param: payload: IdentityProviderMapperRepresentation :type payload: dict :return: Http response - :rtype: dict + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -4788,10 +5632,15 @@ class KeycloakAdmin: data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_idp_mappers(self, idp_alias): - """Get IDP mappers asynchronously. + async def a_get_idp_mappers(self, idp_alias: str) -> list: + """ + Get IDP mappers asynchronously. Returns a list of ID Providers mappers @@ -4805,12 +5654,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_idps(self): - """Get IDPs asynchronously. + async def a_get_idps(self) -> list: + """ + Get IDPs asynchronously. Returns a list of ID Providers, @@ -4822,12 +5672,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_IDPS.format(**params_path) + urls_patterns.URL_ADMIN_IDPS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_idp(self, idp_alias): - """Get IDP provider asynchronously. + async def a_get_idp(self, idp_alias: str) -> dict: + """ + Get IDP provider asynchronously. Get the representation of a specific IDP Provider. @@ -4841,12 +5692,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_IDP.format(**params_path) + urls_patterns.URL_ADMIN_IDP.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_idp(self, idp_alias): - """Delete an ID Provider asynchronously. + async def a_delete_idp(self, idp_alias: str) -> dict: + """ + Delete an ID Provider asynchronously. :param: idp_alias: idp alias name :type idp_alias: str @@ -4855,12 +5707,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_IDP.format(**params_path) + urls_patterns.URL_ADMIN_IDP.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_create_user(self, payload, exist_ok=False): - """Create a new user asynchronously. + async def a_create_user(self, payload: dict, exist_ok: bool = False) -> str: + """ + Create a new user asynchronously. Username must be unique @@ -4885,14 +5742,16 @@ class KeycloakAdmin: return str(exists) data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_USERS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_USERS.format(**params_path), + data=json.dumps(payload), ) - raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_users_count(self, query=None): - """Count users asynchronously. + async def a_users_count(self, query: dict | None = None) -> int: + """ + Count users asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_users_resource @@ -4902,15 +5761,17 @@ class KeycloakAdmin: :return: counter :rtype: int """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query + urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_user_id(self, username): - """Get internal keycloak user id from username asynchronously. + async def a_get_user_id(self, username: str) -> str: + """ + Get internal keycloak user id from username asynchronously. This is required for further actions against this user. @@ -4925,12 +5786,13 @@ class KeycloakAdmin: """ lower_user_name = username.lower() users = await self.a_get_users( - query={"username": lower_user_name, "max": 1, "exact": True} + query={"username": lower_user_name, "max": 1, "exact": True}, ) return users[0]["id"] if len(users) == 1 else None - async def a_get_user(self, user_id, user_profile_metadata=False): - """Get representation of the user asynchronously. + async def a_get_user(self, user_id: str, user_profile_metadata: bool = False) -> dict: + """ + Get representation of the user asynchronously. UserRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation @@ -4948,8 +5810,14 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_user_groups(self, user_id, query=None, brief_representation=True): - """Get user groups asynchronously. + async def a_get_user_groups( + self, + user_id: str, + query: dict | None = None, + brief_representation: bool = True, + ) -> list: + """ + Get user groups asynchronously. Returns a list of groups of which the user is a member @@ -4963,22 +5831,19 @@ class KeycloakAdmin: :rtype: list """ query = query or {} - params = {"briefRepresentation": brief_representation} - query.update(params) params_path = {"realm-name": self.connection.realm_name, "id": user_id} - url = urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path) - if "first" in query or "max" in query: return await self.a___fetch_paginated(url, query) return await self.a___fetch_all(url, query) - async def a_update_user(self, user_id, payload): - """Update the user asynchronously. + async def a_update_user(self, user_id: str, payload: dict) -> bytes: + """ + Update the user asynchronously. :param user_id: User id :type user_id: str @@ -4990,12 +5855,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_USER.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_disable_user(self, user_id): - """Disable the user asynchronously from the realm. Disabled users can not log in. + async def a_disable_user(self, user_id: str) -> bytes: + """ + Disable the user asynchronously from the realm. Disabled users can not log in. :param user_id: User id :type user_id: str @@ -5005,8 +5876,9 @@ class KeycloakAdmin: """ return await self.a_update_user(user_id=user_id, payload={"enabled": False}) - async def a_enable_user(self, user_id): - """Enable the user from the realm asynchronously. + async def a_enable_user(self, user_id: str) -> bytes: + """ + Enable the user from the realm asynchronously. :param user_id: User id :type user_id: str @@ -5016,22 +5888,23 @@ class KeycloakAdmin: """ return await self.a_update_user(user_id=user_id, payload={"enabled": True}) - async def a_disable_all_users(self): + async def a_disable_all_users(self) -> None: """Disable all existing users asynchronously.""" users = await self.a_get_users() for user in users: user_id = user["id"] await self.a_disable_user(user_id=user_id) - async def a_enable_all_users(self): + async def a_enable_all_users(self) -> None: """Disable all existing users asynchronously.""" users = await self.a_get_users() for user in users: user_id = user["id"] await self.a_enable_user(user_id=user_id) - async def a_delete_user(self, user_id): - """Delete the user asynchronously. + async def a_delete_user(self, user_id: str) -> bytes: + """ + Delete the user asynchronously. :param user_id: User id :type user_id: str @@ -5040,12 +5913,22 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_USER.format(**params_path) + urls_patterns.URL_ADMIN_USER.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_set_user_password(self, user_id, password, temporary=True): - """Set up a password for the user asynchronously. + async def a_set_user_password( + self, + user_id: str, + password: str, + temporary: bool = True, + ) -> bytes: + """ + Set up a password for the user asynchronously. If temporary is True, the user will have to reset the temporary password next time they log in. @@ -5060,17 +5943,23 @@ class KeycloakAdmin: :param temporary: True if password is temporary :type temporary: bool :returns: Response - :rtype: dict + :rtype: bytes """ payload = {"type": "password", "temporary": temporary, "value": password} params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_get_credentials(self, user_id): - """Get user credentials asynchronously. + async def a_get_credentials(self, user_id: str) -> dict: + """ + Get user credentials asynchronously. Returns a list of credential belonging to the user. @@ -5084,12 +5973,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path) + urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_credential(self, user_id, credential_id): - """Delete credential of the user asynchronously. + async def a_delete_credential(self, user_id: str, credential_id: str) -> bytes: + """ + Delete credential of the user asynchronously. CredentialRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_credentialrepresentation @@ -5107,12 +5997,13 @@ class KeycloakAdmin: "credential_id": credential_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path) + urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_user_logout(self, user_id): - """Log out the user. + async def a_user_logout(self, user_id: str) -> bytes: + """ + Log out the user. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_logout @@ -5123,12 +6014,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data="" + urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - async def a_user_consents(self, user_id): - """Get consents granted asynchronously by the user. + async def a_user_consents(self, user_id: str) -> list: + """ + Get consents granted asynchronously by the user. UserConsentRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userconsentrepresentation @@ -5140,12 +6037,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path) + urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_user_social_logins(self, user_id): - """Get user social logins asynchronously. + async def a_get_user_social_logins(self, user_id: str) -> list: + """ + Get user social logins asynchronously. Returns a list of federated identities/social logins of which the user has been associated with @@ -5156,14 +6054,19 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path) + urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) async def a_add_user_social_login( - self, user_id, provider_id, provider_userid, provider_username - ): - """Add a federated identity / social login provider asynchronously to the user. + self, + user_id: str, + provider_id: str, + provider_userid: str, + provider_username: str, + ) -> bytes: + """ + Add a federated identity / social login provider asynchronously to the user. :param user_id: User id :type user_id: str @@ -5190,10 +6093,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201, 204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED, HTTP_NO_CONTENT], + ) - async def a_delete_user_social_login(self, user_id, provider_id): - """Delete a federated identity / social login provider asynchronously from the user. + async def a_delete_user_social_login(self, user_id: str, provider_id: str) -> bytes: + """ + Delete a federated identity / social login provider asynchronously from the user. :param user_id: User id :type user_id: str @@ -5208,14 +6116,24 @@ class KeycloakAdmin: "provider": provider_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path) + urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) async def a_send_update_account( - self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None - ): - """Send an update account email to the user asynchronously. + self, + user_id: str, + payload: dict, + client_id: str | None = None, + lifespan: int | None = None, + redirect_uri: str | None = None, + ) -> bytes: + """ + Send an update account email to the user asynchronously. An email contains a link the user can click to perform a set of required actions. @@ -5242,8 +6160,14 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_send_verify_email(self, user_id, client_id=None, redirect_uri=None): - """Send a update account email to the user asynchronously. + async def a_send_verify_email( + self, + user_id: str, + client_id: str | None = None, + redirect_uri: str | None = None, + ) -> bytes: + """ + Send a update account email to the user asynchronously. An email contains a link the user can click to perform a set of required actions. @@ -5266,8 +6190,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_get_sessions(self, user_id): - """Get sessions associated with the user asynchronously. + async def a_get_sessions(self, user_id: str) -> list: + """ + Get sessions associated with the user asynchronously. UserSessionRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation @@ -5275,16 +6200,17 @@ class KeycloakAdmin: :param user_id: Id of user :type user_id: str :return: UserSessionRepresentation - :rtype: dict + :rtype: list """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path) + urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_server_info(self): - """Get themes, social providers, etc. on this server asynchronously. + async def a_get_server_info(self) -> dict: + """ + Get themes, social providers, etc. on this server asynchronously. ServerInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_serverinforepresentation @@ -5295,8 +6221,9 @@ class KeycloakAdmin: data_raw = await self.connection.a_raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_groups(self, query=None, full_hierarchy=False): - """Get groups asynchronously. + async def a_get_groups(self, query: dict | None = None, full_hierarchy: bool = False) -> list: + """ + Get groups asynchronously. Returns a list of groups belonging to the realm @@ -5328,13 +6255,15 @@ class KeycloakAdmin: for group in groups: if group.get("subGroupCount"): group["subGroups"] = await self.a_get_group_children( - group_id=group.get("id"), full_hierarchy=full_hierarchy + group_id=group.get("id"), + full_hierarchy=full_hierarchy, ) return groups - async def a_get_group(self, group_id, full_hierarchy=False): - """Get group by id asynchronously. + async def a_get_group(self, group_id: str, full_hierarchy: bool = False) -> dict: + """ + Get group by id asynchronously. Returns full group details @@ -5351,23 +6280,25 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} response = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUP.format(**params_path) + urls_patterns.URL_ADMIN_GROUP.format(**params_path), ) - if response.status_code >= 400: + if response.status_code >= HTTP_BAD_REQUEST: return raise_error_from_response(response, KeycloakGetError) # For version +23.0.0 group = response.json() if group.get("subGroupCount"): group["subGroups"] = await self.a_get_group_children( - group.get("id"), full_hierarchy=full_hierarchy + group.get("id"), + full_hierarchy=full_hierarchy, ) return group - async def a_get_subgroups(self, group, path): - """Get subgroups asynchronously. + async def a_get_subgroups(self, group: str, path: str) -> dict | None: + """ + Get subgroups asynchronously. Utility function to iterate through nested group structures @@ -5384,16 +6315,22 @@ class KeycloakAdmin: for subgroup in group["subGroups"]: if subgroup["path"] == path: return subgroup - elif subgroup["subGroups"]: - for subgroup in group["subGroups"]: - result = await self.a_get_subgroups(subgroup, path) + if subgroup["subGroups"]: + for _subgroup in group["subGroups"]: + result = await self.a_get_subgroups(_subgroup, path) if result: return result # went through the tree without hits return None - async def a_get_group_children(self, group_id, query=None, full_hierarchy=False): - """Get group children by parent id asynchronously. + async def a_get_group_children( + self, + group_id: str, + query: dict | None = None, + full_hierarchy: bool = False, + ) -> list: + """ + Get group children by parent id asynchronously. Returns full group children details @@ -5404,12 +6341,13 @@ class KeycloakAdmin: :param full_hierarchy: If True, return all of the nested children groups :type full_hierarchy: bool :return: Keycloak server response (GroupRepresentation) - :rtype: dict + :rtype: list :raises ValueError: If both query and full_hierarchy parameters are used """ query = query or {} if query and full_hierarchy: - raise ValueError("Cannot use both query and full_hierarchy parameters") + msg = "Cannot use both query and full_hierarchy parameters" + raise ValueError(msg) params_path = {"realm-name": self.connection.realm_name, "id": group_id} url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path) @@ -5423,13 +6361,15 @@ class KeycloakAdmin: for group in res: if group.get("subGroupCount"): group["subGroups"] = await self.a_get_group_children( - group_id=group.get("id"), full_hierarchy=full_hierarchy + group_id=group.get("id"), + full_hierarchy=full_hierarchy, ) return res - async def a_get_group_members(self, group_id, query=None): - """Get members by group id asynchronously. + async def a_get_group_members(self, group_id: str, query: dict | None = None) -> list: + """ + Get members by group id asynchronously. Returns group members @@ -5453,8 +6393,9 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_get_group_by_path(self, path): - """Get group id based on name or path asynchronously . + async def a_get_group_by_path(self, path: str) -> dict: + """ + Get group id based on name or path asynchronously . Returns full group details for a group defined by path @@ -5468,12 +6409,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "path": path} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path) + urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, [200, 404]) + return raise_error_from_response(data_raw, KeycloakGetError, [HTTP_OK, 404]) - async def a_create_group(self, payload, parent=None, skip_exists=False): - """Create a group in the Realm asynchronously. + async def a_create_group( + self, + payload: dict | None, + parent: str | None = None, + skip_exists: bool | None = False, + ) -> str | None: + """ + Create a group in the Realm asynchronously. GroupRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation @@ -5491,25 +6438,31 @@ class KeycloakAdmin: if parent is None: params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_GROUPS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUPS.format(**params_path), + data=json.dumps(payload), ) else: params_path = {"realm-name": self.connection.realm_name, "id": parent} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) try: _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] except KeyError: - return + return None - async def a_update_group(self, group_id, payload): - """Update group, ignores subgroups asynchronously. + async def a_update_group(self, group_id: str, payload: dict) -> bytes: + """ + Update group, ignores subgroups asynchronously. GroupRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation @@ -5524,12 +6477,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_GROUP.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_groups_count(self, query=None): - """Count groups asynchronously. + async def a_groups_count(self, query: dict | None = None) -> dict: + """ + Count groups asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_groups @@ -5539,15 +6498,17 @@ class KeycloakAdmin: :return: Keycloak Server Response :rtype: dict """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), **query + urls_patterns.URL_ADMIN_GROUPS_COUNT.format(**params_path), + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_group_set_permissions(self, group_id, enabled=True): - """Enable/Disable permissions for a group asynchronously. + async def a_group_set_permissions(self, group_id: str, enabled: bool = True) -> bytes: + """ + Enable/Disable permissions for a group asynchronously. Cannot delete group if disabled @@ -5565,8 +6526,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_group_user_add(self, user_id, group_id): - """Add user to group (user_id and group_id) asynchronously. + async def a_group_user_add(self, user_id: str, group_id: str) -> bytes: + """ + Add user to group (user_id and group_id) asynchronously. :param user_id: id of user :type user_id: str @@ -5581,12 +6543,18 @@ class KeycloakAdmin: "group-id": group_id, } data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None + urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), + data=None, + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_group_user_remove(self, user_id, group_id): - """Remove user from group (user_id and group_id) asynchronously. + async def a_group_user_remove(self, user_id: str, group_id: str) -> bytes: + """ + Remove user from group (user_id and group_id) asynchronously. :param user_id: id of user :type user_id: str @@ -5601,12 +6569,17 @@ class KeycloakAdmin: "group-id": group_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path) + urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_delete_group(self, group_id): - """Delete a group in the Realm asynchronously. + async def a_delete_group(self, group_id: str) -> bytes: + """ + Delete a group in the Realm asynchronously. :param group_id: id of group to delete :type group_id: str @@ -5615,12 +6588,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": group_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_GROUP.format(**params_path) + urls_patterns.URL_ADMIN_GROUP.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_clients(self): - """Get clients asynchronously. + async def a_get_clients(self) -> list: + """ + Get clients asynchronously. Returns a list of clients belonging to the realm @@ -5632,12 +6610,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENTS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client(self, client_id): - """Get representation of the client asynchronously. + async def a_get_client(self, client_id: str) -> dict: + """ + Get representation of the client asynchronously. ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5649,12 +6628,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_id(self, client_id): - """Get internal keycloak client id from client-id asynchronously. + async def a_get_client_id(self, client_id: str) -> str | None: + """ + Get internal keycloak client id from client-id asynchronously. This is required for further actions against this client. @@ -5666,7 +6646,8 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), clientId=client_id + urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), + clientId=client_id, ) data_response = raise_error_from_response(data_raw, KeycloakGetError) @@ -5676,8 +6657,9 @@ class KeycloakAdmin: return None - async def a_get_client_authz_settings(self, client_id): - """Get authorization json from client asynchronously. + async def a_get_client_authz_settings(self, client_id: str) -> dict: + """ + Get authorization json from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5687,12 +6669,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_client_authz_resource(self, client_id, payload, skip_exists=False): - """Create resources of client asynchronously. + async def a_create_client_authz_resource( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> bytes | dict: + """ + Create resources of client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5714,11 +6702,20 @@ class KeycloakAdmin: max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_update_client_authz_resource(self, client_id, resource_id, payload): - """Update resource of client asynchronously. + async def a_update_client_authz_resource( + self, + client_id: str, + resource_id: str, + payload: dict, + ) -> bytes: + """ + Update resource of client asynchronously. Any parameter missing from the ResourceRepresentation in the payload WILL be set to default by the Keycloak server. @@ -5748,10 +6745,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_client_authz_resource(self, client_id: str, resource_id: str): - """Delete a client resource asynchronously. + async def a_delete_client_authz_resource(self, client_id: str, resource_id: str) -> bytes: + """ + Delete a client resource asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5769,12 +6771,17 @@ class KeycloakAdmin: "resource-id": resource_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_client_authz_resources(self, client_id): - """Get resources from client asynchronously. + async def a_get_client_authz_resources(self, client_id: str) -> list: + """ + Get resources from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5784,12 +6791,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_authz_resource(self, client_id: str, resource_id: str): - """Get a client resource asynchronously. + async def a_get_client_authz_resource(self, client_id: str, resource_id: str) -> dict: + """ + Get a client resource asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5807,12 +6816,18 @@ class KeycloakAdmin: "resource-id": resource_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_create_client_authz_role_based_policy(self, client_id, payload, skip_exists=False): - """Create role-based policy of client asynchronously. + async def a_create_client_authz_role_based_policy( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> bytes: + """ + Create role-based policy of client asynchronously. Payload example:: @@ -5837,21 +6852,28 @@ class KeycloakAdmin: :type skip_exists: bool :return: Keycloak server response :rtype: bytes - """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_ROLE_BASED_POLICY.format(**params_path), data=json.dumps(payload), max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_create_client_authz_policy(self, client_id, payload, skip_exists=False): - """Create an authz policy of client asynchronously. + async def a_create_client_authz_policy( + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> bytes: + """ + Create an authz policy of client asynchronously. Payload example:: @@ -5875,10 +6897,8 @@ class KeycloakAdmin: :type skip_exists: bool :return: Keycloak server response :rtype: bytes - """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICIES.format(**params_path), data=json.dumps(payload), @@ -5886,13 +6906,20 @@ class KeycloakAdmin: permission=False, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) async def a_create_client_authz_resource_based_permission( - self, client_id, payload, skip_exists=False - ): - """Create resource-based permission of client asynchronously. + self, + client_id: str, + payload: dict, + skip_exists: bool = False, + ) -> bytes: + """ + Create resource-based permission of client asynchronously. Payload example:: @@ -5918,21 +6945,23 @@ class KeycloakAdmin: :type skip_exists: bool :return: Keycloak server response :rtype: bytes - """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_BASED_PERMISSION.format(**params_path), data=json.dumps(payload), max=-1, ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_get_client_authz_scopes(self, client_id): - """Get scopes from client asynchronously. + async def a_get_client_authz_scopes(self, client_id: str) -> list: + """ + Get scopes from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5942,12 +6971,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_client_authz_scopes(self, client_id, payload): - """Create scopes for client asynchronously. + async def a_create_client_authz_scopes(self, client_id: str, payload: dict) -> bytes: + """ + Create scopes for client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5964,10 +6995,15 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - async def a_get_client_authz_permissions(self, client_id): - """Get permissions from client asynchronously. + async def a_get_client_authz_permissions(self, client_id: str) -> list: + """ + Get permissions from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5977,12 +7013,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), max=-1 + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path), + max=-1, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_authz_policies(self, client_id): - """Get policies from client asynchronously. + async def a_get_client_authz_policies(self, client_id: str) -> list: + """ + Get policies from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -5998,8 +7036,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_client_authz_policy(self, client_id, policy_id): - """Delete a policy from client asynchronously. + async def a_delete_client_authz_policy(self, client_id: str, policy_id: str) -> bytes: + """ + Delete a policy from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -6016,12 +7055,17 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_client_authz_policy(self, client_id, policy_id): - """Get a policy from client asynchronously. + async def a_get_client_authz_policy(self, client_id: str, policy_id: str) -> dict: + """ + Get a policy from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -6038,12 +7082,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_service_account_user(self, client_id): - """Get service account user from client asynchronously. + async def a_get_client_service_account_user(self, client_id: str) -> dict: + """ + Get service account user from client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -6053,12 +7098,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_default_client_scopes(self, client_id): - """Get all default client scopes from client asynchronously. + async def a_get_client_default_client_scopes(self, client_id: str) -> list: + """ + Get all default client scopes from client asynchronously. :param client_id: id of the client in which the new default client scope should be added :type client_id: str @@ -6068,12 +7114,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_add_client_default_client_scope(self, client_id, client_scope_id, payload): - """Add a client scope to the default client scopes from client asynchronously. + async def a_add_client_default_client_scope( + self, + client_id: str, + client_scope_id: str, + payload: dict, + ) -> bytes: + """ + Add a client scope to the default client scopes from client asynchronously. Payload example:: @@ -6104,8 +7156,13 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_delete_client_default_client_scope(self, client_id, client_scope_id): - """Delete a client scope from the default client scopes of the client asynchronously. + async def a_delete_client_default_client_scope( + self, + client_id: str, + client_scope_id: str, + ) -> bytes: + """ + Delete a client scope from the default client scopes of the client asynchronously. :param client_id: id of the client in which the default client scope should be deleted :type client_id: str @@ -6121,12 +7178,13 @@ class KeycloakAdmin: "client_scope_id": client_scope_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_get_client_optional_client_scopes(self, client_id): - """Get all optional client scopes from client asynchronously. + async def a_get_client_optional_client_scopes(self, client_id: str) -> list: + """ + Get all optional client scopes from client asynchronously. :param client_id: id of the client in which the new optional client scope should be added :type client_id: str @@ -6136,12 +7194,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_add_client_optional_client_scope(self, client_id, client_scope_id, payload): - """Add a client scope to the optional client scopes from client asynchronously. + async def a_add_client_optional_client_scope( + self, + client_id: str, + client_scope_id: str, + payload: dict, + ) -> bytes: + """ + Add a client scope to the optional client scopes from client asynchronously. Payload example:: @@ -6172,8 +7236,13 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_delete_client_optional_client_scope(self, client_id, client_scope_id): - """Delete a client scope from the optional client scopes of the client asynchronously. + async def a_delete_client_optional_client_scope( + self, + client_id: str, + client_scope_id: str, + ) -> bytes: + """ + Delete a client scope from the optional client scopes of the client asynchronously. :param client_id: id of the client in which the optional client scope should be deleted :type client_id: str @@ -6189,19 +7258,24 @@ class KeycloakAdmin: "client_scope_id": client_scope_id, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_create_initial_access_token(self, count: int = 1, expiration: int = 1): - """Create an initial access token asynchronously. + async def a_create_initial_access_token( + self, + count: int = 1, + expiration: int = 1, + ) -> dict | bytes: + """ + Create an initial access token asynchronously. :param count: Number of clients that can be registered :type count: int :param expiration: Days until expireation :type expiration: int :return: initial access token - :rtype: str + :rtype: dict """ payload = {"count": count, "expiration": expiration} params_path = {"realm-name": self.connection.realm_name} @@ -6209,10 +7283,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_INITIAL_ACCESS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - async def a_create_client(self, payload, skip_exists=False): - """Create a client asynchronously. + async def a_create_client(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a client asynchronously. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -6232,16 +7307,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_update_client(self, client_id, payload): - """Update a client asynchronously. + async def a_update_client(self, client_id: str, payload: dict) -> bytes: + """ + Update a client asynchronously. :param client_id: Client id :type client_id: str @@ -6253,12 +7333,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_delete_client(self, client_id): - """Get representation of the client asynchronously. + async def a_delete_client(self, client_id: str) -> bytes: + """ + Get representation of the client asynchronously. ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -6270,12 +7356,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_client_installation_provider(self, client_id, provider_id): - """Get content for given installation provider asynchronously. + async def a_get_client_installation_provider(self, client_id: str, provider_id: str) -> list: + """ + Get content for given installation provider asynchronously. Related documentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clients_resource @@ -6296,12 +7387,17 @@ class KeycloakAdmin: "provider-id": provider_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_get_realm_roles(self, brief_representation=True, search_text=""): - """Get all roles for the realm or client asynchronously. + async def a_get_realm_roles( + self, + brief_representation: bool = True, + search_text: str = "", + ) -> list: + """ + Get all roles for the realm or client asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6320,12 +7416,19 @@ class KeycloakAdmin: params["search"] = search_text data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_realm_role_groups(self, role_name, query=None, brief_representation=True): - """Get role groups of realm by role name asynchronously. + async def a_get_realm_role_groups( + self, + role_name: str, + query: dict | None = None, + brief_representation: bool = True, + ) -> list: + """ + Get role groups of realm by role name asynchronously. :param role_name: Name of the role. :type role_name: str @@ -6338,13 +7441,10 @@ class KeycloakAdmin: :rtype: list """ query = query or {} - params = {"briefRepresentation": brief_representation} - query.update(params) params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} - url = urls_patterns.URL_ADMIN_REALM_ROLES_GROUPS.format(**params_path) if "first" in query or "max" in query: @@ -6352,8 +7452,9 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_get_realm_role_members(self, role_name, query=None): - """Get role members of realm by role name asynchronously. + async def a_get_realm_role_members(self, role_name: str, query: dict | None = None) -> list: + """ + Get role members of realm by role name asynchronously. :param role_name: Name of the role. :type role_name: str @@ -6363,14 +7464,16 @@ class KeycloakAdmin: :return: Keycloak Server Response (UserRepresentation) :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} return await self.a___fetch_all( - urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query + urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), + query, ) - async def a_get_default_realm_role_id(self): - """Get the ID of the default realm role asynchronously. + async def a_get_default_realm_role_id(self) -> str: + """ + Get the ID of the default realm role asynchronously. :return: Realm role ID :rtype: str @@ -6383,8 +7486,9 @@ class KeycloakAdmin: ] return default_realm_roles[0]["id"] - async def a_get_realm_default_roles(self): - """Get all the default realm roles asyncho asynchronously. + async def a_get_realm_default_roles(self) -> list: + """ + Get all the default realm roles asyncho asynchronously. :return: Keycloak Server Response (UserRepresentation) :rtype: list @@ -6394,17 +7498,18 @@ class KeycloakAdmin: "role-id": await self.a_get_default_realm_role_id(), } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_remove_realm_default_roles(self, payload): - """Remove a set of default realm roles asynchronously. + async def a_remove_realm_default_roles(self, payload: dict) -> bytes: + """ + Remove a set of default realm roles asynchronously. :param payload: List of RoleRepresentations :type payload: list :return: Keycloak Server Response - :rtype: dict + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -6416,13 +7521,14 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_add_realm_default_roles(self, payload): - """Add a set of default realm roles asynchronously. + async def a_add_realm_default_roles(self, payload: dict) -> bytes: + """ + Add a set of default realm roles asynchronously. :param payload: List of RoleRepresentations :type payload: list :return: Keycloak Server Response - :rtype: dict + :rtype: bytes """ params_path = { "realm-name": self.connection.realm_name, @@ -6434,8 +7540,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_get_client_roles(self, client_id, brief_representation=True): - """Get all roles for the client asynchronously. + async def a_get_client_roles(self, client_id: str, brief_representation: bool = True) -> list: + """ + Get all roles for the client asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6450,12 +7557,14 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": client_id} params = {"briefRepresentation": brief_representation} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_role(self, client_id, role_name): - """Get client role by name asynchronously. + async def a_get_client_role(self, client_id: str, role_name: str) -> dict: + """ + Get client role by name asynchronously. :param client_id: id of client (not client-id) :type client_id: str @@ -6470,12 +7579,13 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_role_id(self, client_id, role_name): - """Get client role id by name asynchronously. + async def a_get_client_role_id(self, client_id: str, role_name: str) -> str: + """ + Get client role id by name asynchronously. This is required for further actions with this role. @@ -6492,8 +7602,14 @@ class KeycloakAdmin: role = await self.a_get_client_role(client_id, role_name) return role.get("id") - async def a_create_client_role(self, client_role_id, payload, skip_exists=False): - """Create a client role asynchronously. + async def a_create_client_role( + self, + client_role_id: str, + payload: dict, + skip_exists: bool = False, + ) -> str: + """ + Create a client role asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6510,7 +7626,8 @@ class KeycloakAdmin: if skip_exists: try: res = await self.a_get_client_role( - client_id=client_role_id, role_name=payload["name"] + client_id=client_role_id, + role_name=payload["name"], ) return res["name"] except KeycloakGetError: @@ -6518,16 +7635,26 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": client_role_id} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_add_composite_client_roles_to_role(self, client_role_id, role_name, roles): - """Add composite roles to client role asynchronously. + async def a_add_composite_client_roles_to_role( + self, + client_role_id: str, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Add composite roles to client role asynchronously. :param client_role_id: id of client (not client-id) :type client_role_id: str @@ -6548,10 +7675,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_remove_composite_client_roles_from_role(self, client_role_id, role_name, roles): - """Remove composite roles from a client role asynchronously. + async def a_remove_composite_client_roles_from_role( + self, + client_role_id: str, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Remove composite roles from a client role asynchronously. :param client_role_id: id of client (not client-id) :type client_role_id: str @@ -6572,10 +7709,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_update_client_role(self, client_id, role_name, payload): - """Update a client role asynchronously. + async def a_update_client_role(self, client_id: str, role_name: str, payload: dict) -> bytes: + """ + Update a client role asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6595,12 +7737,18 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_delete_client_role(self, client_role_id, role_name): - """Delete a client role asynchronously. + async def a_delete_client_role(self, client_role_id: str, role_name: str) -> bytes: + """ + Delete a client role asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6618,12 +7766,17 @@ class KeycloakAdmin: "role-name": role_name, } data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_assign_client_role(self, user_id, client_id, roles): - """Assign a client role to a user asynchronously. + async def a_assign_client_role(self, user_id: str, client_id: str, roles: str | list) -> bytes: + """ + Assign a client role to a user asynchronously. :param user_id: id of user :type user_id: str @@ -6644,10 +7797,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_client_role_members(self, client_id, role_name, **query): - """Get members by client role asynchronously. + async def a_get_client_role_members( + self, + client_id: str, + role_name: str, + **query: dict, + ) -> list: + """ + Get members by client role asynchronously. :param client_id: The client id :type client_id: str @@ -6665,11 +7828,18 @@ class KeycloakAdmin: "role-name": role_name, } return await self.a___fetch_all( - urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), query + urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), + query, ) - async def a_get_client_role_groups(self, client_id, role_name, **query): - """Get group members by client role asynchronously. + async def a_get_client_role_groups( + self, + client_id: str, + role_name: str, + **query: dict, + ) -> list: + """ + Get group members by client role asynchronously. :param client_id: The client id :type client_id: str @@ -6687,11 +7857,13 @@ class KeycloakAdmin: "role-name": role_name, } return await self.a___fetch_all( - urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), query + urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), + query, ) - async def a_get_role_by_id(self, role_id): - """Get a specific role’s representation asynchronously. + async def a_get_role_by_id(self, role_id: str) -> dict: + """ + Get a specific role's representation asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6699,16 +7871,17 @@ class KeycloakAdmin: :param role_id: id of role :type role_id: str :return: Keycloak server response (RoleRepresentation) - :rtype: bytes + :rtype: dict """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_update_role_by_id(self, role_id, payload): - """Update the role asynchronously. + async def a_update_role_by_id(self, role_id: str, payload: dict) -> bytes: + """ + Update the role asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6725,10 +7898,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_role_by_id(self, role_id): - """Delete a role by its id asynchronously. + async def a_delete_role_by_id(self, role_id: str) -> bytes: + """ + Delete a role by its id asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6740,12 +7918,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_create_realm_role(self, payload, skip_exists=False): - """Create a new role for the realm or client asynchronously. + async def a_create_realm_role(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a new role for the realm or client asynchronously. :param payload: The role (use RoleRepresentation) :type payload: dict @@ -6763,16 +7946,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_get_realm_role(self, role_name): - """Get realm role by role name asynchronously. + async def a_get_realm_role(self, role_name: str) -> dict: + """ + Get realm role by role name asynchronously. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6784,12 +7972,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_realm_role_by_id(self, role_id: str): - """Get realm role by role id. + async def a_get_realm_role_by_id(self, role_id: str) -> dict: + """ + Get realm role by role id. RoleRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation @@ -6801,12 +7990,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-id": role_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_realm_role(self, role_name, payload): - """Update a role for the realm by name asynchronously. + async def a_update_realm_role(self, role_name: str, payload: dict) -> bytes: + """ + Update a role for the realm by name asynchronously. :param role_name: The name of the role to be updated :type role_name: str @@ -6820,10 +8010,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_realm_role(self, role_name): - """Delete a role for the realm by name asynchronously. + async def a_delete_realm_role(self, role_name: str) -> bytes: + """ + Delete a role for the realm by name asynchronously. :param role_name: The role name :type role_name: str @@ -6832,12 +8027,21 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_add_composite_realm_roles_to_role(self, role_name, roles): - """Add composite roles to the role asynchronously. + async def a_add_composite_realm_roles_to_role( + self, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Add composite roles to the role asynchronously. :param role_name: The name of the role :type role_name: str @@ -6852,10 +8056,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_remove_composite_realm_roles_to_role(self, role_name, roles): - """Remove composite roles from the role asynchronously. + async def a_remove_composite_realm_roles_to_role( + self, + role_name: str, + roles: str | list, + ) -> bytes: + """ + Remove composite roles from the role asynchronously. :param role_name: The name of the role :type role_name: str @@ -6870,10 +8083,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_composite_realm_roles_of_role(self, role_name): - """Get composite roles of the role asynchronously. + async def a_get_composite_realm_roles_of_role(self, role_name: str) -> list: + """ + Get composite roles of the role asynchronously. :param role_name: The name of the role :type role_name: str @@ -6882,12 +8100,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "role-name": role_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path) + urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_assign_realm_roles_to_client_scope(self, client_id, roles): - """Assign realm roles to a client's scope asynchronously. + async def a_assign_realm_roles_to_client_scope( + self, + client_id: str, + roles: str | list, + ) -> dict | bytes: + """ + Assign realm roles to a client's scope asynchronously. :param client_id: id of client (not client-id) :type client_id: str @@ -6902,10 +8125,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_realm_roles_of_client_scope(self, client_id, roles): - """Delete realm roles of a client's scope asynchronously. + async def a_delete_realm_roles_of_client_scope( + self, + client_id: str, + roles: str | list, + ) -> bytes: + """ + Delete realm roles of a client's scope asynchronously. :param client_id: id of client (not client-id) :type client_id: str @@ -6920,10 +8152,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_realm_roles_of_client_scope(self, client_id): - """Get all realm roles for a client's scope. + async def a_get_realm_roles_of_client_scope(self, client_id: str) -> list: + """ + Get all realm roles for a client's scope. :param client_id: id of client (not client-id) :type client_id: str @@ -6932,12 +8169,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles): - """Assign client roles to a client's dedicated scope asynchronously. + async def a_assign_client_roles_to_client_scope( + self, + client_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> dict | bytes: + """ + Assign client roles to a client's dedicated scope asynchronously. To assign roles to a client scope, use a_add_client_specific_roles_to_client_scope. @@ -6960,10 +8203,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles): - """Delete client roles of a client's dedicated scope asynchronously. + async def a_delete_client_roles_of_client_scope( + self, + client_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles of a client's dedicated scope asynchronously. To remove roles from a client scope, use a_remove_client_specific_roles_of_client_scope. @@ -6986,10 +8239,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_client_roles_of_client_scope(self, client_id, client_roles_owner_id): - """Get all client roles for a client's scope asynchronously. + async def a_get_client_roles_of_client_scope( + self, + client_id: str, + client_roles_owner_id: str, + ) -> list: + """ + Get all client roles for a client's scope asynchronously. To get roles from a client scope, use a_get_client_roles_of_client_scope. @@ -7006,12 +8268,13 @@ class KeycloakAdmin: "client": client_roles_owner_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_assign_realm_roles(self, user_id, roles): - """Assign realm roles to a user asynchronously. + async def a_assign_realm_roles(self, user_id: str, roles: str | list) -> bytes: + """ + Assign realm roles to a user asynchronously. :param user_id: id of user :type user_id: str @@ -7026,10 +8289,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_realm_roles_of_user(self, user_id, roles): - """Delete realm roles of a user asynchronously. + async def a_delete_realm_roles_of_user(self, user_id: str, roles: str | list) -> bytes: + """ + Delete realm roles of a user asynchronously. :param user_id: id of user :type user_id: str @@ -7044,10 +8312,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_realm_roles_of_user(self, user_id): - """Get all realm roles for a user asynchronously. + async def a_get_realm_roles_of_user(self, user_id: str) -> list: + """ + Get all realm roles for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7056,12 +8329,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_available_realm_roles_of_user(self, user_id): - """Get all available (i.e. unassigned) realm roles for a user asynchronously. + async def a_get_available_realm_roles_of_user(self, user_id: str) -> list: + """ + Get all available (i.e. unassigned) realm roles for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7070,12 +8344,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path) + urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_composite_realm_roles_of_user(self, user_id, brief_representation=True): - """Get all composite (i.e. implicit) realm roles for a user asynchronously. + async def a_get_composite_realm_roles_of_user( + self, + user_id: str, + brief_representation: bool = True, + ) -> list: + """ + Get all composite (i.e. implicit) realm roles for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7087,12 +8366,14 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": user_id} params = {"briefRepresentation": brief_representation} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params + urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_assign_group_realm_roles(self, group_id, roles): - """Assign realm roles to a group asynchronously. + async def a_assign_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + """ + Assign realm roles to a group asynchronously. :param group_id: id of group :type group_id: str @@ -7107,10 +8388,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_group_realm_roles(self, group_id, roles): - """Delete realm roles of a group asynchronously. + async def a_delete_group_realm_roles(self, group_id: str, roles: str | list) -> bytes: + """ + Delete realm roles of a group asynchronously. :param group_id: id of group :type group_id: str @@ -7125,10 +8411,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_group_realm_roles(self, group_id, brief_representation=True): - """Get all realm roles for a group asynchronously. + async def a_get_group_realm_roles( + self, + group_id: str, + brief_representation: bool = True, + ) -> list: + """ + Get all realm roles for a group asynchronously. :param group_id: id of the group :type group_id: str @@ -7140,12 +8435,19 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name, "id": group_id} params = {"briefRepresentation": brief_representation} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params + urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_assign_group_client_roles(self, group_id, client_id, roles): - """Assign client roles to a group asynchronously. + async def a_assign_group_client_roles( + self, + group_id: str, + client_id: str, + roles: str | list, + ) -> bytes: + """ + Assign client roles to a group asynchronously. :param group_id: id of group :type group_id: str @@ -7166,10 +8468,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_group_client_roles(self, group_id, client_id): - """Get client roles of a group asynchronously. + async def a_get_group_client_roles(self, group_id: str, client_id: str) -> list: + """ + Get client roles of a group asynchronously. :param group_id: id of group :type group_id: str @@ -7184,12 +8491,18 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_group_client_roles(self, group_id, client_id, roles): - """Delete client roles of a group asynchronously. + async def a_delete_group_client_roles( + self, + group_id: str, + client_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles of a group asynchronously. :param group_id: id of group :type group_id: str @@ -7210,10 +8523,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_all_roles_of_user(self, user_id): - """Get all level roles for a user asynchronously. + async def a_get_all_roles_of_user(self, user_id: str) -> list: + """ + Get all level roles for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7222,12 +8540,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path) + urls_patterns.URL_ADMIN_USER_ALL_ROLES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_roles_of_user(self, user_id, client_id): - """Get all client roles for a user asynchronously. + async def a_get_client_roles_of_user(self, user_id: str, client_id: str) -> list: + """ + Get all client roles for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7237,11 +8556,14 @@ class KeycloakAdmin: :rtype: list """ return await self.a__get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, user_id, client_id + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, + user_id, + client_id, ) - async def a_get_available_client_roles_of_user(self, user_id, client_id): - """Get available client role-mappings for a user asynchronously. + async def a_get_available_client_roles_of_user(self, user_id: str, client_id: str) -> list: + """ + Get available client role-mappings for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7251,13 +8573,19 @@ class KeycloakAdmin: :rtype: list """ return await self.a__get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, + user_id, + client_id, ) async def a_get_composite_client_roles_of_user( - self, user_id, client_id, brief_representation=False - ): - """Get composite client role-mappings for a user asynchronously. + self, + user_id: str, + client_id: str, + brief_representation: bool = False, + ) -> list: + """ + Get composite client role-mappings for a user asynchronously. :param user_id: id of user :type user_id: str @@ -7270,13 +8598,21 @@ class KeycloakAdmin: """ params = {"briefRepresentation": brief_representation} return await self.a__get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id, **params + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, + user_id, + client_id, + **params, ) async def a__get_client_roles_of_user( - self, client_level_role_mapping_url, user_id, client_id, **params - ): - """Get client roles of a single user helper asynchronously. + self, + client_level_role_mapping_url: str, + user_id: str, + client_id: str, + **params: dict, + ) -> list: + """ + Get client roles of a single user helper asynchronously. :param client_level_role_mapping_url: Url for the client role mapping :type client_level_role_mapping_url: str @@ -7295,12 +8631,19 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = await self.connection.a_raw_get( - client_level_role_mapping_url.format(**params_path), **params + client_level_role_mapping_url.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_client_roles_of_user(self, user_id, client_id, roles): - """Delete client roles from a user asynchronously. + async def a_delete_client_roles_of_user( + self, + user_id: str, + client_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles from a user asynchronously. :param user_id: id of user :type user_id: str @@ -7321,10 +8664,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_authentication_flows(self): - """Get authentication flows asynchronously. + async def a_get_authentication_flows(self) -> list: + """ + Get authentication flows asynchronously. Returns all flow details @@ -7336,12 +8684,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_FLOWS.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_authentication_flow_for_id(self, flow_id): - """Get one authentication flow by it's id asynchronously. + async def a_get_authentication_flow_for_id(self, flow_id: str) -> dict: + """ + Get one authentication flow by it's id asynchronously. Returns all flow details @@ -7355,12 +8704,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-id": flow_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_authentication_flow(self, payload, skip_exists=False): - """Create a new authentication flow asynchronously. + async def a_create_authentication_flow( + self, + payload: dict, + skip_exists: bool = False, + ) -> bytes: + """ + Create a new authentication flow asynchronously. AuthenticationFlowRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationflowrepresentation @@ -7374,14 +8728,19 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_FLOWS.format(**params_path), + data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_copy_authentication_flow(self, payload, flow_alias): - """Copy existing authentication flow under a new name asynchronously. + async def a_copy_authentication_flow(self, payload: dict, flow_alias: str) -> bytes: + """ + Copy existing authentication flow under a new name asynchronously. The new name is given as 'newName' attribute of the passed payload. @@ -7394,12 +8753,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - async def a_delete_authentication_flow(self, flow_id): - """Delete authentication flow asynchronously. + async def a_delete_authentication_flow(self, flow_id: str) -> bytes: + """ + Delete authentication flow asynchronously. AuthenticationInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationinforepresentation @@ -7411,12 +8776,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": flow_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_FLOW.format(**params_path) + urls_patterns.URL_ADMIN_FLOW.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_authentication_flow_executions(self, flow_alias): - """Get authentication flow executions asynchronously. + async def a_get_authentication_flow_executions(self, flow_alias: str) -> list: + """ + Get authentication flow executions asynchronously. Returns all execution steps @@ -7427,12 +8797,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_authentication_flow_executions(self, payload, flow_alias): - """Update an authentication flow execution asynchronously. + async def a_update_authentication_flow_executions( + self, + payload: dict, + flow_alias: str, + ) -> bytes: + """ + Update an authentication flow execution asynchronously. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -7449,10 +8824,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[202, 204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_ACCEPTED, HTTP_NO_CONTENT], + ) - async def a_get_authentication_flow_execution(self, execution_id): - """Get authentication flow execution asynchronously. + async def a_get_authentication_flow_execution(self, execution_id: str) -> dict: + """ + Get authentication flow execution asynchronously. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -7464,12 +8844,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_authentication_flow_execution(self, payload, flow_alias): - """Create an authentication flow execution asynchronously. + async def a_create_authentication_flow_execution( + self, + payload: dict, + flow_alias: str, + ) -> bytes: + """ + Create an authentication flow execution asynchronously. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -7486,10 +8871,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - async def a_delete_authentication_flow_execution(self, execution_id): - """Delete authentication flow execution asynchronously. + async def a_delete_authentication_flow_execution(self, execution_id: str) -> bytes: + """ + Delete authentication flow execution asynchronously. AuthenticationExecutionInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationexecutioninforepresentation @@ -7501,12 +8891,22 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": execution_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path) + urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_create_authentication_flow_subflow(self, payload, flow_alias, skip_exists=False): - """Create a new sub authentication flow for a given authentication flow asynchronously. + async def a_create_authentication_flow_subflow( + self, + payload: dict, + flow_alias: str, + skip_exists: bool = False, + ) -> bytes: + """ + Create a new sub authentication flow for a given authentication flow asynchronously. AuthenticationFlowRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticationflowrepresentation @@ -7526,23 +8926,28 @@ class KeycloakAdmin: data=json.dumps(payload), ) return raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) - async def a_get_authenticator_providers(self): - """Get authenticator providers list asynchronously. + async def a_get_authenticator_providers(self) -> list: + """ + Get authenticator providers list asynchronously. :return: Authenticator providers :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_authenticator_provider_config_description(self, provider_id): - """Get authenticator's provider configuration description asynchronously. + async def a_get_authenticator_provider_config_description(self, provider_id: str) -> dict: + """ + Get authenticator's provider configuration description asynchronously. AuthenticatorConfigInfoRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticatorconfiginforepresentation @@ -7554,12 +8959,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "provider-id": provider_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_authenticator_config(self, config_id): - """Get authenticator configuration asynchronously. + async def a_get_authenticator_config(self, config_id: str) -> dict: + """ + Get authenticator configuration asynchronously. Returns all configuration details. @@ -7570,12 +8976,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_authenticator_config(self, payload, config_id): - """Update an authenticator configuration asynchronously. + async def a_update_authenticator_config(self, payload: dict, config_id: str) -> bytes: + """ + Update an authenticator configuration asynchronously. AuthenticatorConfigRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authenticatorconfigrepresentation @@ -7592,10 +8999,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_delete_authenticator_config(self, config_id): - """Delete a authenticator configuration asynchronously. + async def a_delete_authenticator_config(self, config_id: str) -> bytes: + """ + Delete a authenticator configuration asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_authentication_management_resource @@ -7606,12 +9018,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": config_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path) + urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_sync_users(self, storage_id, action): - """Trigger user sync from provider asynchronously. + async def a_sync_users(self, storage_id: str, action: str) -> bytes: + """ + Trigger user sync from provider asynchronously. :param storage_id: The id of the user storage provider :type storage_id: str @@ -7631,8 +9048,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_get_client_scopes(self): - """Get client scopes asynchronously. + async def a_get_client_scopes(self) -> list: + """ + Get client scopes asynchronously. Get representation of the client scopes for the realm where we are connected to https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -7642,12 +9060,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_scope(self, client_scope_id): - """Get client scope asynchronously. + async def a_get_client_scope(self, client_scope_id: str) -> dict: + """ + Get client scope asynchronously. Get representation of the client scopes for the realm where we are connected to https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -7659,12 +9078,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_scope_by_name(self, client_scope_name): - """Get client scope by name asynchronously. + async def a_get_client_scope_by_name(self, client_scope_name: str) -> dict | None: + """ + Get client scope by name asynchronously. Get representation of the client scope identified by the client scope name. @@ -7681,8 +9101,9 @@ class KeycloakAdmin: return None - async def a_create_client_scope(self, payload, skip_exists=False): - """Create a client scope asynchronously. + async def a_create_client_scope(self, payload: dict, skip_exists: bool = False) -> str: + """ + Create a client scope asynchronously. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientscopes @@ -7702,16 +9123,21 @@ class KeycloakAdmin: params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), + data=json.dumps(payload), ) raise_error_from_response( - data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + skip_exists=skip_exists, ) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_update_client_scope(self, client_scope_id, payload): - """Update a client scope asynchronously. + async def a_update_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + """ + Update a client scope asynchronously. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_client_scopes_resource @@ -7725,12 +9151,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_delete_client_scope(self, client_scope_id): - """Delete existing client scope asynchronously. + async def a_delete_client_scope(self, client_scope_id: str) -> bytes: + """ + Delete existing client scope asynchronously. ClientScopeRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_client_scopes_resource @@ -7742,12 +9174,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_mappers_from_client_scope(self, client_scope_id): - """Get a list of all mappers connected to the client scope asynchronously. + async def a_get_mappers_from_client_scope(self, client_scope_id: str) -> list: + """ + Get a list of all mappers connected to the client scope asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource :param client_scope_id: Client scope id @@ -7757,12 +9194,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_add_mapper_to_client_scope(self, client_scope_id, payload): - """Add a mapper to a client scope asynchronously. + async def a_add_mapper_to_client_scope(self, client_scope_id: str, payload: dict) -> bytes: + """ + Add a mapper to a client scope asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_create_mapper @@ -7774,16 +9212,23 @@ class KeycloakAdmin: :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} - data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - - async def a_delete_mapper_from_client_scope(self, client_scope_id, protocol_mapper_id): - """Delete a mapper from a client scope asynchronously. + async def a_delete_mapper_from_client_scope( + self, + client_scope_id: str, + protocol_mapper_id: str, + ) -> bytes: + """ + Delete a mapper from a client scope asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_delete_mapper @@ -7799,14 +9244,23 @@ class KeycloakAdmin: "scope-id": client_scope_id, "protocol-mapper-id": protocol_mapper_id, } - data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_update_mapper_in_client_scope(self, client_scope_id, protocol_mapper_id, payload): - """Update an existing protocol mapper in a client scope asynchronously. + async def a_update_mapper_in_client_scope( + self, + client_scope_id: str, + protocol_mapper_id: str, + payload: dict, + ) -> bytes: + """ + Update an existing protocol mapper in a client scope asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource @@ -7825,16 +9279,19 @@ class KeycloakAdmin: "scope-id": client_scope_id, "protocol-mapper-id": protocol_mapper_id, } - data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - - async def a_get_default_default_client_scopes(self): - """Get default default client scopes asynchronously. + async def a_get_default_default_client_scopes(self) -> list: + """ + Get default default client scopes asynchronously. Return list of default default client scopes @@ -7843,12 +9300,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_default_default_client_scope(self, scope_id): - """Delete default default client scope asynchronously. + async def a_delete_default_default_client_scope(self, scope_id: str) -> bytes: + """ + Delete default default client scope asynchronously. :param scope_id: default default client scope id :type scope_id: str @@ -7857,12 +9315,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_add_default_default_client_scope(self, scope_id): - """Add default default client scope asynchronously. + async def a_add_default_default_client_scope(self, scope_id: str) -> bytes: + """ + Add default default client scope asynchronously. :param scope_id: default default client scope id :type scope_id: str @@ -7875,10 +9338,15 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_get_default_optional_client_scopes(self): - """Get default optional client scopes asynchronously. + async def a_get_default_optional_client_scopes(self) -> list: + """ + Get default optional client scopes asynchronously. Return list of default optional client scopes @@ -7887,12 +9355,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_delete_default_optional_client_scope(self, scope_id): - """Delete default optional client scope asynchronously. + async def a_delete_default_optional_client_scope(self, scope_id: str) -> bytes: + """ + Delete default optional client scope asynchronously. :param scope_id: default optional client scope id :type scope_id: str @@ -7901,12 +9370,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": scope_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path) + urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_add_default_optional_client_scope(self, scope_id): - """Add default optional client scope asynchronously. + async def a_add_default_optional_client_scope(self, scope_id: str) -> bytes: + """ + Add default optional client scope asynchronously. :param scope_id: default optional client scope id :type scope_id: str @@ -7919,12 +9393,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) async def a_add_client_specific_roles_to_client_scope( - self, client_scope_id, client_roles_owner_id, roles - ): - """Assign client roles to a client scope asynchronously. + self, + client_scope_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Assign client roles to a client scope asynchronously. To assign roles to a client's dedicated scope, use a_assign_client_roles_to_client_scope. @@ -7948,12 +9430,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) async def a_remove_client_specific_roles_of_client_scope( - self, client_scope_id, client_roles_owner_id, roles - ): - """Delete client roles of a client scope asynchronously. + self, + client_scope_id: str, + client_roles_owner_id: str, + roles: str | list, + ) -> bytes: + """ + Delete client roles of a client scope asynchronously. To delete roles from a client's dedicated scope, use a_delete_client_roles_of_client_scope. @@ -7965,7 +9455,7 @@ class KeycloakAdmin: :param roles: roles list or role (use RoleRepresentation, must include id and name) :type roles: list :return: Keycloak server response - :rtype: dict + :rtype: bytes """ payload = roles if isinstance(roles, list) else [roles] params_path = { @@ -7977,12 +9467,19 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) async def a_get_client_specific_roles_of_client_scope( - self, client_scope_id, client_roles_owner_id - ): - """Get all client roles for a client scope asynchronously. + self, + client_scope_id: str, + client_roles_owner_id: str, + ) -> list: + """ + Get all client roles for a client scope asynchronously. To get roles for a client's dedicated scope, use a_get_client_roles_of_client_scope. @@ -7992,7 +9489,7 @@ class KeycloakAdmin: :param client_roles_owner_id: id of client (not client-id) who has the roles :type client_roles_owner_id: str :return: Keycloak server response (array RoleRepresentation) - :rtype: dict + :rtype: list """ params_path = { "realm-name": self.connection.realm_name, @@ -8000,12 +9497,13 @@ class KeycloakAdmin: "client-id": client_roles_owner_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_all_roles_of_client_scope(self, client_scope_id): - """Get all client roles for a client scope. + async def a_get_all_roles_of_client_scope(self, client_scope_id: str) -> list: + """ + Get all client roles for a client scope. To get roles for a client's dedicated scope, use a_get_client_roles_of_client_scope. @@ -8017,12 +9515,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_mappers_from_client(self, client_id): - """List of all client mappers asynchronously. + async def a_get_mappers_from_client(self, client_id: str) -> list: + """ + List of all client mappers asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocolmapperrepresentation @@ -8032,15 +9531,15 @@ class KeycloakAdmin: :rtype: list """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_OK]) - async def a_add_mapper_to_client(self, client_id, payload): - """Add a mapper to a client asynchronously. + async def a_add_mapper_to_client(self, client_id: str, payload: dict) -> bytes: + """ + Add a mapper to a client asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_create_mapper @@ -8052,16 +9551,19 @@ class KeycloakAdmin: :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} - data_raw = await self.connection.a_raw_post( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - - async def a_update_client_mapper(self, client_id, mapper_id, payload): - """Update client mapper asynchronously. + async def a_update_client_mapper(self, client_id: str, mapper_id: str, payload: dict) -> bytes: + """ + Update client mapper asynchronously. :param client_id: The id of the client :type client_id: str @@ -8077,16 +9579,19 @@ class KeycloakAdmin: "id": client_id, "protocol-mapper-id": mapper_id, } - data_raw = await self.connection.a_raw_put( urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), data=json.dumps(payload), ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - - async def a_remove_client_mapper(self, client_id, client_mapper_id): - """Remove a mapper from the client asynchronously. + async def a_remove_client_mapper(self, client_id: str, client_mapper_id: str) -> bytes: + """ + Remove a mapper from the client asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_protocol_mappers_resource @@ -8102,14 +9607,18 @@ class KeycloakAdmin: "id": client_id, "protocol-mapper-id": client_mapper_id, } - data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_generate_client_secrets(self, client_id): - """Generate a new secret for the client asynchronously. + async def a_generate_client_secrets(self, client_id: str) -> bytes: + """ + Generate a new secret for the client asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_regeneratesecret @@ -8120,12 +9629,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None + urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), + data=None, ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_get_client_secrets(self, client_id): - """Get representation of the client secrets asynchronously. + async def a_get_client_secrets(self, client_id: str) -> list: + """ + Get representation of the client secrets asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsecret @@ -8136,12 +9647,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_components(self, query=None): - """Get components asynchronously. + async def a_get_components(self, query: dict | None = None) -> list: + """ + Get components asynchronously. Return a list of components, filtered according to query parameters @@ -8153,15 +9665,18 @@ class KeycloakAdmin: :return: components list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_component(self, payload): - """Create a new component asynchronously. + async def a_create_component(self, payload: dict) -> str: + """ + Create a new component asynchronously. ComponentRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_componentrepresentation @@ -8173,14 +9688,16 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), + data=json.dumps(payload), ) - raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[HTTP_CREATED]) _last_slash_idx = data_raw.headers["Location"].rindex("/") - return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203 + return data_raw.headers["Location"][_last_slash_idx + 1 :] - async def a_get_component(self, component_id): - """Get representation of the component asynchronously. + async def a_get_component(self, component_id: str) -> dict | bytes: + """ + Get representation of the component asynchronously. :param component_id: Component id @@ -8194,12 +9711,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_COMPONENT.format(**params_path) + urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_component(self, component_id, payload): - """Update the component asynchronously. + async def a_update_component(self, component_id: str, payload: dict) -> bytes: + """ + Update the component asynchronously. :param component_id: Component id :type component_id: str @@ -8211,12 +9729,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_delete_component(self, component_id): - """Delete the component asynchronously. + async def a_delete_component(self, component_id: str) -> bytes: + """ + Delete the component asynchronously. :param component_id: Component id :type component_id: str @@ -8225,12 +9749,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "component-id": component_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_COMPONENT.format(**params_path) + urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), + ) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - async def a_get_keys(self): - """Get keys asynchronously. + async def a_get_keys(self) -> list: + """ + Get keys asynchronously. Return a list of keys, filtered according to query parameters @@ -8242,12 +9771,14 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None + urls_patterns.URL_ADMIN_KEYS.format(**params_path), + data=None, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_admin_events(self, query=None): - """Get Administrative events asynchronously. + async def a_get_admin_events(self, query: dict | None = None) -> list: + """ + Get Administrative events asynchronously. Return a list of events, filtered according to query parameters @@ -8260,15 +9791,18 @@ class KeycloakAdmin: :return: events list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_ADMIN_EVENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_ADMIN_EVENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_events(self, query=None): - """Get events asynchronously. + async def a_get_events(self, query: dict | None = None) -> list: + """ + Get events asynchronously. Return a list of events, filtered according to query parameters @@ -8280,15 +9814,18 @@ class KeycloakAdmin: :return: events list :rtype: list """ - query = query or dict() + query = query or {} params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_USER_EVENTS.format(**params_path), data=None, **query + urls_patterns.URL_ADMIN_USER_EVENTS.format(**params_path), + data=None, + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_set_events(self, payload): - """Set realm events configuration asynchronously. + async def a_set_events(self, payload: dict) -> bytes: + """ + Set realm events configuration asynchronously. RealmEventsConfigRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_realmeventsconfigrepresentation @@ -8300,12 +9837,18 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload) + urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - async def a_get_client_all_sessions(self, client_id, query=None): - """Get sessions associated with the client asynchronously. + async def a_get_client_all_sessions(self, client_id: str, query: dict | None = None) -> list: + """ + Get sessions associated with the client asynchronously. UserSessionRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation @@ -8325,8 +9868,9 @@ class KeycloakAdmin: return await self.a___fetch_all(url, query) - async def a_get_client_sessions_stats(self): - """Get current session count for all clients with active sessions asynchronously. + async def a_get_client_sessions_stats(self) -> dict: + """ + Get current session count for all clients with active sessions asynchronously. https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_getclientsessionstats @@ -8335,12 +9879,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_management_permissions(self, client_id): - """Get management permissions for a client asynchronously. + async def a_get_client_management_permissions(self, client_id: str) -> list: + """ + Get management permissions for a client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8350,12 +9895,13 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_client_management_permissions(self, payload, client_id): - """Update management permissions for a client asynchronously. + async def a_update_client_management_permissions(self, payload: dict, client_id: str) -> bytes: + """ + Update management permissions for a client asynchronously. ManagementPermissionReference https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_managementpermissionreference @@ -8379,10 +9925,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_OK]) - async def a_get_client_authz_policy_scopes(self, client_id, policy_id): - """Get scopes for a given policy asynchronously. + async def a_get_client_authz_policy_scopes(self, client_id: str, policy_id: str) -> list: + """ + Get scopes for a given policy asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8398,12 +9945,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_authz_policy_resources(self, client_id, policy_id): - """Get resources for a given policy asynchronously. + async def a_get_client_authz_policy_resources(self, client_id: str, policy_id: str) -> list: + """ + Get resources for a given policy asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8419,12 +9967,13 @@ class KeycloakAdmin: "policy-id": policy_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_client_authz_scope_permission(self, client_id, scope_id): - """Get permissions for a given scope asynchronously. + async def a_get_client_authz_scope_permission(self, client_id: str, scope_id: str) -> list: + """ + Get permissions for a given scope asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8440,12 +9989,13 @@ class KeycloakAdmin: "scope-id": scope_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_create_client_authz_scope_permission(self, payload, client_id): - """Create permissions for a authz scope asynchronously. + async def a_create_client_authz_scope_permission(self, payload: dict, client_id: str) -> bytes: + """ + Create permissions for a authz scope asynchronously. Payload example:: @@ -8473,10 +10023,20 @@ class KeycloakAdmin: data=json.dumps(payload), max=-1, ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - async def a_update_client_authz_scope_permission(self, payload, client_id, scope_id): - """Update permissions for a given scope asynchronously. + async def a_update_client_authz_scope_permission( + self, + payload: dict, + client_id: str, + scope_id: str, + ) -> bytes: + """ + Update permissions for a given scope asynchronously. Payload example:: @@ -8510,10 +10070,16 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) - async def a_update_client_authz_resource_permission(self, payload, client_id, resource_id): - """Update permissions for a given resource asynchronously. + async def a_update_client_authz_resource_permission( + self, + payload: dict, + client_id: str, + resource_id: str, + ) -> bytes: + """ + Update permissions for a given resource asynchronously. Payload example:: @@ -8547,10 +10113,11 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_PERMISSION.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201]) + return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[HTTP_CREATED]) - async def a_get_client_authz_client_policies(self, client_id): - """Get policies for a given client asynchronously. + async def a_get_client_authz_client_policies(self, client_id: str) -> list: + """ + Get policies for a given client asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8560,12 +10127,17 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": client_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_get_client_authz_permission_associated_policies(self, client_id, policy_id): - """Get associated policies for a given client permission asynchronously. + async def a_get_client_authz_permission_associated_policies( + self, + client_id: str, + policy_id: str, + ) -> list: + """ + Get associated policies for a given client permission asynchronously. :param client_id: id in ClientRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -8583,13 +10155,14 @@ class KeycloakAdmin: } data_raw = await self.connection.a_raw_get( urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY_ASSOCIATED_POLICIES.format( - **params_path - ) + **params_path, + ), ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_create_client_authz_client_policy(self, payload, client_id): - """Create a new policy for a given client asynchronously. + async def a_create_client_authz_client_policy(self, payload: dict, client_id: str) -> bytes: + """ + Create a new policy for a given client asynchronously. Payload example:: @@ -8614,12 +10187,20 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path), data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) async def a_get_composite_client_roles_of_group( - self, client_id, group_id, brief_representation=True - ): - """Get the composite client roles of the given group for the given client asynchronously. + self, + client_id: str, + group_id: str, + brief_representation: bool = True, + ) -> list: + """ + Get the composite client roles of the given group for the given client asynchronously. :param client_id: id of the client. :type client_id: str @@ -8637,12 +10218,14 @@ class KeycloakAdmin: } params = {"briefRepresentation": brief_representation} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params + urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), + **params, ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_get_role_client_level_children(self, client_id, role_id): - """Get the child roles async of which the given composite client role is composed of. + async def a_get_role_client_level_children(self, client_id: str, role_id: str) -> list: + """ + Get the child roles async of which the given composite client role is composed of. :param client_id: id of the client. :type client_id: str @@ -8657,12 +10240,13 @@ class KeycloakAdmin: "client-id": client_id, } data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path) + urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_upload_certificate(self, client_id, certcont): - """Upload a new certificate for the client asynchronously. + async def a_upload_certificate(self, client_id: str, certcont: str) -> dict: + """ + Upload a new certificate for the client asynchronously. :param client_id: id of the client. :type client_id: str @@ -8688,8 +10272,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_get_required_action_by_alias(self, action_alias): - """Get a required action by its alias asynchronously. + async def a_get_required_action_by_alias(self, action_alias: str) -> dict: + """ + Get a required action by its alias asynchronously. :param action_alias: the alias of the required action. :type action_alias: str @@ -8702,38 +10287,42 @@ class KeycloakAdmin: return a return None - async def a_get_required_actions(self): - """Get the required actions for the realms asynchronously. + async def a_get_required_actions(self) -> list: + """ + Get the required actions for the realms asynchronously. :return: the required actions (list of RequiredActionProviderRepresentation). :rtype: list """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path) + urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_update_required_action(self, action_alias, payload): - """Update a required action asynchronously. + async def a_update_required_action(self, action_alias: str, payload: dict) -> bytes: + """ + Update a required action asynchronously. :param action_alias: the action alias. :type action_alias: str :param payload: the new required action (RequiredActionProviderRepresentation). :type payload: dict :return: empty dictionary. - :rtype: dict + :rtype: bytes """ if not isinstance(payload, str): payload = json.dumps(payload) params_path = {"realm-name": self.connection.realm_name, "action-alias": action_alias} data_raw = await self.connection.a_raw_put( - urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), data=payload + urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), + data=payload, ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_get_bruteforce_detection_status(self, user_id): - """Get bruteforce detection status for user asynchronously. + async def a_get_bruteforce_detection_status(self, user_id: str) -> dict: + """ + Get bruteforce detection status for user asynchronously. :param user_id: User id :type user_id: str @@ -8742,68 +10331,88 @@ class KeycloakAdmin: """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_clear_bruteforce_attempts_for_user(self, user_id): - """Clear bruteforce attempts for user asynchronously. + async def a_clear_bruteforce_attempts_for_user(self, user_id: str) -> bytes: + """ + Clear bruteforce attempts for user asynchronously. :param user_id: User id :type user_id: str :return: empty dictionary. - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name, "id": user_id} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_clear_all_bruteforce_attempts(self): - """Clear bruteforce attempts for all users in realm asynchronously. + async def a_clear_all_bruteforce_attempts(self) -> bytes: + """ + Clear bruteforce attempts for all users in realm asynchronously. :return: empty dictionary. - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_delete( - urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path) + urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path), ) return raise_error_from_response(data_raw, KeycloakDeleteError) - async def a_clear_keys_cache(self): - """Clear keys cache asynchronously. + async def a_clear_keys_cache(self) -> bytes: + """ + Clear keys cache asynchronously. :return: empty dictionary. - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - async def a_clear_realm_cache(self): - """Clear realm cache asynchronously. + async def a_clear_realm_cache(self) -> bytes: + """ + Clear realm cache asynchronously. :return: empty dictionary. - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) - async def a_clear_user_cache(self): - """Clear user cache asynchronously. + async def a_clear_user_cache(self) -> bytes: + """ + Clear user cache asynchronously. :return: empty dictionary. - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.connection.realm_name} data_raw = await self.connection.a_raw_post( - urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), data="" + urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), + data="", + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) diff --git a/src/keycloak/keycloak_openid.py b/src/keycloak/keycloak_openid.py index 30670a4..60f85e6 100644 --- a/src/keycloak/keycloak_openid.py +++ b/src/keycloak/keycloak_openid.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -21,20 +20,29 @@ # 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 OpenID module. +""" +Keycloak OpenID module. The module contains mainly the implementation of KeycloakOpenID class, the main class to handle authentication and token manipulation. """ +from __future__ import annotations + import json -from typing import Optional, Union +import pathlib +import aiofiles from jwcrypto import jwk, jwt from .authorization import Authorization from .connection import ConnectionManager from .exceptions import ( + HTTP_FORBIDDEN, + HTTP_NO_CONTENT, + HTTP_NOT_ALLOWED, + HTTP_NOT_FOUND, + HTTP_UNAUTHORIZED, KeycloakAuthenticationError, KeycloakAuthorizationConfigError, KeycloakDeprecationError, @@ -63,7 +71,8 @@ from .urls_patterns import ( class KeycloakOpenID: - """Keycloak OpenID client. + """ + Keycloak OpenID client. :param server_url: Keycloak server url :param client_id: client id @@ -83,18 +92,19 @@ class KeycloakOpenID: def __init__( self, - server_url, - realm_name, - client_id, - client_secret_key=None, - verify=True, - custom_headers=None, - proxies=None, - timeout=60, - cert=None, - max_retries=1, - ): - """Init method. + server_url: str, + realm_name: str, + client_id: str, + client_secret_key: str | None = None, + verify: bool | str = True, + custom_headers: dict | None = None, + proxies: dict | None = None, + timeout: int = 60, + cert: str | tuple | None = None, + max_retries: int = 1, + ) -> None: + """ + Init method. :param server_url: Keycloak server url :type server_url: str @@ -123,7 +133,7 @@ class KeycloakOpenID: self.client_id = client_id self.client_secret_key = client_secret_key self.realm_name = realm_name - headers = custom_headers if custom_headers is not None else dict() + headers = custom_headers if custom_headers is not None else {} self.connection = ConnectionManager( base_url=server_url, headers=headers, @@ -137,8 +147,9 @@ class KeycloakOpenID: self.authorization = Authorization() @property - def client_id(self): - """Get client id. + def client_id(self) -> str: + """ + Get client id. :returns: Client id :rtype: str @@ -146,12 +157,13 @@ class KeycloakOpenID: return self._client_id @client_id.setter - def client_id(self, value): + def client_id(self, value: str) -> None: self._client_id = value @property - def client_secret_key(self): - """Get the client secret key. + def client_secret_key(self) -> str: + """ + Get the client secret key. :returns: Client secret key :rtype: str @@ -159,12 +171,13 @@ class KeycloakOpenID: return self._client_secret_key @client_secret_key.setter - def client_secret_key(self, value): + def client_secret_key(self, value: str) -> None: self._client_secret_key = value @property - def realm_name(self): - """Get the realm name. + def realm_name(self) -> str: + """ + Get the realm name. :returns: Realm name :rtype: str @@ -172,12 +185,13 @@ class KeycloakOpenID: return self._realm_name @realm_name.setter - def realm_name(self, value): + def realm_name(self, value: str) -> None: self._realm_name = value @property - def connection(self): - """Get connection. + def connection(self) -> ConnectionManager: + """ + Get connection. :returns: Connection manager object :rtype: ConnectionManager @@ -185,12 +199,13 @@ class KeycloakOpenID: return self._connection @connection.setter - def connection(self, value): + def connection(self, value: ConnectionManager) -> None: self._connection = value @property - def authorization(self): - """Get authorization. + def authorization(self) -> Authorization: + """ + Get authorization. :returns: The authorization manager :rtype: Authorization @@ -198,11 +213,12 @@ class KeycloakOpenID: return self._authorization @authorization.setter - def authorization(self, value): + def authorization(self, value: Authorization) -> None: self._authorization = value - def _add_secret_key(self, payload): - """Add secret key if exists. + def _add_secret_key(self, payload: dict) -> dict: + """ + Add secret key if exists. :param payload: Payload :type payload: dict @@ -214,8 +230,9 @@ class KeycloakOpenID: return payload - def _build_name_role(self, role): - """Build name of a role. + def _build_name_role(self, role: str) -> str: + """ + Build name of a role. :param role: Role name :type role: str @@ -224,8 +241,9 @@ class KeycloakOpenID: """ return self.client_id + "/" + role - def _token_info(self, token, method_token_info, **kwargs): - """Getter for the token data. + def _token_info(self, token: str, method_token_info: str, **kwargs: dict) -> dict: + """ + Getter for the token data. :param token: Token :type token: str @@ -236,15 +254,16 @@ class KeycloakOpenID: :returns: Token info :rtype: dict """ - if method_token_info == "introspect": + if method_token_info == "introspect": # noqa: S105 token_info = self.introspect(token) else: token_info = self.decode_token(token, **kwargs) return token_info - def well_known(self): - """Get the well_known object. + def well_known(self) -> dict: + """ + Get the well_known object. The most important endpoint to understand is the well-known configuration endpoint. It lists endpoints and other configuration options relevant to @@ -257,8 +276,15 @@ class KeycloakOpenID: data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def auth_url(self, redirect_uri, scope="email", state="", nonce=""): - """Get authorization URL endpoint. + def auth_url( + self, + redirect_uri: str, + scope: str = "email", + state: str = "", + nonce: str = "", + ) -> str: + """ + Get authorization URL endpoint. :param redirect_uri: Redirect url to receive oauth code :type redirect_uri: str @@ -283,16 +309,17 @@ class KeycloakOpenID: def token( self, - username="", - password="", - grant_type="password", - code="", - redirect_uri="", - totp=None, - scope="openid", - **extra - ): - """Retrieve user token. + username: str = "", + password: str = "", + grant_type: str = "password", + code: str = "", + redirect_uri: str = "", + totp: int | None = None, + scope: str = "openid", + **extra: dict, + ) -> dict: + """ + Retrieve user token. The token endpoint is used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on @@ -347,8 +374,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - def refresh_token(self, refresh_token, grant_type="refresh_token"): - """Refresh the user token. + def refresh_token(self, refresh_token: str, grant_type: str = "refresh_token") -> dict: + """ + Refresh the user token. The token endpoint is used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on @@ -384,15 +412,16 @@ class KeycloakOpenID: def exchange_token( self, token: str, - audience: Optional[str] = None, - subject: Optional[str] = None, - subject_token_type: Optional[str] = None, - subject_issuer: Optional[str] = None, - requested_issuer: Optional[str] = None, - requested_token_type: str = "urn:ietf:params:oauth:token-type:refresh_token", + audience: str | None = None, + subject: str | None = None, + subject_token_type: str | None = None, + subject_issuer: str | None = None, + requested_issuer: str | None = None, + requested_token_type: str = "urn:ietf:params:oauth:token-type:refresh_token", # noqa: S107 scope: str = "openid", ) -> dict: - """Exchange user token. + """ + Exchange user token. Use a token to obtain an entirely different token. See https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange @@ -440,8 +469,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - def userinfo(self, token): - """Get the user info object. + def userinfo(self, token: str) -> dict: + """ + Get the user info object. The userinfo endpoint returns standard claims about the authenticated user, and is protected by a bearer token. @@ -464,22 +494,28 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakGetError) - def logout(self, refresh_token): - """Log out the authenticated user. + def logout(self, refresh_token: str) -> bytes: + """ + Log out the authenticated user. :param refresh_token: Refresh token from Keycloak :type refresh_token: str :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "refresh_token": refresh_token} payload = self._add_secret_key(payload) data_raw = self.connection.raw_post(URL_LOGOUT.format(**params_path), data=payload) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - def certs(self): - """Get certificates. + def certs(self) -> dict: + """ + Get certificates. The certificate endpoint returns the public keys enabled by the realm, encoded as a JSON Web Key (JWK). Depending on the realm settings there can be one or more keys enabled @@ -494,8 +530,9 @@ class KeycloakOpenID: data_raw = self.connection.raw_get(URL_CERTS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def public_key(self): - """Retrieve the public key. + def public_key(self) -> str: + """ + Retrieve the public key. The public key is exposed by the realm page directly. @@ -506,8 +543,9 @@ class KeycloakOpenID: data_raw = self.connection.raw_get(URL_REALM.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError)["public_key"] - def entitlement(self, token, resource_server_id): - """Get entitlements from the token. + def entitlement(self, token: str, resource_server_id: str) -> dict: + """ + Get entitlements from the token. Client applications can use a specific endpoint to obtain a special security token called a requesting party token (RPT). This token consists of all the entitlements @@ -532,13 +570,19 @@ class KeycloakOpenID: else self.connection.del_param_headers("Authorization") ) - if data_raw.status_code == 404 or data_raw.status_code == 405: + if data_raw.status_code in {HTTP_NOT_FOUND, HTTP_NOT_ALLOWED}: return raise_error_from_response(data_raw, KeycloakDeprecationError) return raise_error_from_response(data_raw, KeycloakGetError) # pragma: no cover - def introspect(self, token, rpt=None, token_type_hint=None): - """Introspect the user token. + def introspect( + self, + token: str, + rpt: str | None = None, + token_type_hint: str | None = None, + ) -> dict: + """ + Introspect the user token. The introspection endpoint is used to retrieve the active state of a token. It is can only be invoked by confidential clients. @@ -561,14 +605,15 @@ class KeycloakOpenID: bearer_changed = False orig_bearer = None - if token_type_hint == "requesting_party_token": + if token_type_hint == "requesting_party_token": # noqa: S105 if rpt: payload.update({"token": rpt, "token_type_hint": token_type_hint}) orig_bearer = self.connection.headers.get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) bearer_changed = True else: - raise KeycloakRPTNotFound("Can't found RPT.") + msg = "Can't find RPT" + raise KeycloakRPTNotFound(msg) payload = self._add_secret_key(payload) @@ -582,8 +627,9 @@ class KeycloakOpenID: return raise_error_from_response(data_raw, KeycloakPostError) @staticmethod - def _verify_token(token, key: Union[jwk.JWK, jwk.JWKSet, None], **kwargs): - """Decode and optionally validate a token. + def _verify_token(token: str, key: jwk.JWK | jwk.JWKSet | None, **kwargs: dict) -> dict: + """ + Decode and optionally validate a token. :param token: The token to verify :type token: str @@ -603,13 +649,13 @@ class KeycloakOpenID: full_jwt.leeway = leeway full_jwt.validate(key) return jwt.json_decode(full_jwt.claims) - else: - full_jwt = jwt.JWT(jwt=token, **kwargs) - full_jwt.token.objects["valid"] = True - return json.loads(full_jwt.token.payload.decode("utf-8")) + full_jwt = jwt.JWT(jwt=token, **kwargs) + full_jwt.token.objects["valid"] = True + return json.loads(full_jwt.token.payload.decode("utf-8")) - def decode_token(self, token, validate: bool = True, **kwargs): - """Decode user token. + def decode_token(self, token: str, validate: bool = True, **kwargs: dict) -> dict: + """ + Decode user token. A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. This specification @@ -644,19 +690,26 @@ class KeycloakOpenID: return self._verify_token(token, key, **kwargs) - def load_authorization_config(self, path): - """Load Keycloak settings (authorization). + def load_authorization_config(self, path: str) -> None: + """ + Load Keycloak settings (authorization). :param path: settings file (json) :type path: str """ - with open(path, "r") as fp: + with pathlib.Path(path).open("r") as fp: authorization_json = json.load(fp) self.authorization.load_config(authorization_json) - def get_policies(self, token, method_token_info="introspect", **kwargs): - """Get policies by user token. + def get_policies( + self, + token: str, + method_token_info: str = "introspect", # noqa: S107 + **kwargs: dict, + ) -> list: + """ + Get policies by user token. :param token: User token :type token: str @@ -670,31 +723,37 @@ class KeycloakOpenID: :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: - raise KeycloakAuthorizationConfigError( - "Keycloak settings not found. Load Authorization Keycloak settings." - ) + msg = "Keycloak settings not found. Load Authorization Keycloak settings." + raise KeycloakAuthorizationConfigError(msg) token_info = self._token_info(token, method_token_info, **kwargs) - if method_token_info == "introspect" and not token_info["active"]: - raise KeycloakInvalidTokenError("Token expired or invalid.") + if method_token_info == "introspect" and not token_info["active"]: # noqa: S105 + msg = "Token expired or invalid." + raise KeycloakInvalidTokenError(msg) user_resources = token_info["resource_access"].get(self.client_id) if not user_resources: return None - policies = [] - - for policy_name, policy in self.authorization.policies.items(): - for role in user_resources["roles"]: - if self._build_name_role(role) in policy.roles: - policies.append(policy) + policies = [ + policy + for policy in self.authorization.policies.values() + for role in user_resources["roles"] + if self._build_name_role(role) in policy.roles + ] return list(set(policies)) - def get_permissions(self, token, method_token_info="introspect", **kwargs): - """Get permission by user token. + def get_permissions( + self, + token: str, + method_token_info: str = "introspect", # noqa: S107 + **kwargs: dict, + ) -> list: + """ + Get permission by user token. :param token: user token :type token: str @@ -708,14 +767,14 @@ class KeycloakOpenID: :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: - raise KeycloakAuthorizationConfigError( - "Keycloak settings not found. Load Authorization Keycloak settings." - ) + msg = "Keycloak settings not found. Load Authorization Keycloak settings." + raise KeycloakAuthorizationConfigError(msg) token_info = self._token_info(token, method_token_info, **kwargs) - if method_token_info == "introspect" and not token_info["active"]: - raise KeycloakInvalidTokenError("Token expired or invalid.") + if method_token_info == "introspect" and not token_info["active"]: # noqa: S105 + msg = "Token expired or invalid." + raise KeycloakInvalidTokenError(msg) user_resources = token_info["resource_access"].get(self.client_id) @@ -724,15 +783,16 @@ class KeycloakOpenID: permissions = [] - for policy_name, policy in self.authorization.policies.items(): + for policy in self.authorization.policies.values(): for role in user_resources["roles"]: if self._build_name_role(role) in policy.roles: permissions += policy.permissions return list(set(permissions)) - def uma_permissions(self, token, permissions="", **extra_payload): - """Get UMA permissions by user token with requested permissions. + def uma_permissions(self, token: str, permissions: str = "", **extra_payload: dict) -> dict: + """ + Get UMA permissions by user token with requested permissions. The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be invoked by confidential clients. @@ -776,8 +836,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - def has_uma_access(self, token, permissions): - """Determine whether user has uma permissions with specified user token. + def has_uma_access(self, token: str, permissions: list) -> AuthStatus: + """ + Determine whether user has uma permissions with specified user token. :param token: user token :type token: str @@ -792,13 +853,17 @@ class KeycloakOpenID: try: granted = self.uma_permissions(token, permissions) except (KeycloakPostError, KeycloakAuthenticationError) as e: - if e.response_code == 403: # pragma: no cover + if e.response_code == HTTP_FORBIDDEN: # pragma: no cover return AuthStatus( - is_logged_in=True, is_authorized=False, missing_permissions=needed + is_logged_in=True, + is_authorized=False, + missing_permissions=needed, ) - elif e.response_code == 401: + if e.response_code == HTTP_UNAUTHORIZED: return AuthStatus( - is_logged_in=False, is_authorized=False, missing_permissions=needed + is_logged_in=False, + is_authorized=False, + missing_permissions=needed, ) raise @@ -809,14 +874,17 @@ class KeycloakOpenID: needed.discard(resource) continue for scope in scopes: # pragma: no cover - needed.discard("{}#{}".format(resource, scope)) + needed.discard(f"{resource}#{scope}") return AuthStatus( - is_logged_in=True, is_authorized=len(needed) == 0, missing_permissions=needed + is_logged_in=True, + is_authorized=len(needed) == 0, + missing_permissions=needed, ) - def register_client(self, token: str, payload: dict): - """Create a client. + def register_client(self, token: str, payload: dict) -> dict: + """ + Create a client. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -834,7 +902,8 @@ class KeycloakOpenID: orig_content_type = self.connection.headers.get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") data_raw = self.connection.raw_post( - URL_CLIENT_REGISTRATION.format(**params_path), data=json.dumps(payload) + URL_CLIENT_REGISTRATION.format(**params_path), + data=json.dumps(payload), ) ( self.connection.add_param_headers("Authorization", orig_bearer) @@ -848,8 +917,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - def device(self, scope: str = ""): - """Get device authorization grant. + def device(self, scope: str = "") -> dict: + """ + Get device authorization grant. The device endpoint is used to obtain a user code verification and user authentication. The response contains a device_code, user_code, verification_uri, @@ -875,8 +945,9 @@ class KeycloakOpenID: data_raw = self.connection.raw_post(URL_DEVICE.format(**params_path), data=payload) return raise_error_from_response(data_raw, KeycloakPostError) - def update_client(self, token: str, client_id: str, payload: dict): - """Update a client. + def update_client(self, token: str, client_id: str, payload: dict) -> bytes: + """ + Update a client. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -888,7 +959,7 @@ class KeycloakOpenID: :param payload: ClientRepresentation :type payload: dict :return: Client Representation - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.realm_name, "client-id": client_id} orig_bearer = self.connection.headers.get("Authorization") @@ -901,7 +972,8 @@ class KeycloakOpenID: payload["clientId"] = client_id data_raw = self.connection.raw_put( - URL_CLIENT_UPDATE.format(**params_path), data=json.dumps(payload) + URL_CLIENT_UPDATE.format(**params_path), + data=json.dumps(payload), ) ( self.connection.add_param_headers("Authorization", orig_bearer) @@ -915,8 +987,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def _a_token_info(self, token, method_token_info, **kwargs): - """Asynchronous getter for the token data. + async def _a_token_info(self, token: str, method_token_info: str, **kwargs: dict) -> dict: + """ + Asynchronous getter for the token data. :param token: Token :type token: str @@ -927,15 +1000,16 @@ class KeycloakOpenID: :returns: Token info :rtype: dict """ - if method_token_info == "introspect": + if method_token_info == "introspect": # noqa: S105 token_info = await self.a_introspect(token) else: token_info = await self.a_decode_token(token, **kwargs) return token_info - async def a_well_known(self): - """Get the well_known object asynchronously. + async def a_well_known(self) -> dict: + """ + Get the well_known object asynchronously. The most important endpoint to understand is the well-known configuration endpoint. It lists endpoints and other configuration options relevant to @@ -948,8 +1022,15 @@ class KeycloakOpenID: data_raw = await self.connection.a_raw_get(URL_WELL_KNOWN.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_auth_url(self, redirect_uri, scope="email", state="", nonce=""): - """Get authorization URL endpoint asynchronously. + async def a_auth_url( + self, + redirect_uri: str, + scope: str = "email", + state: str = "", + nonce: str = "", + ) -> str: + """ + Get authorization URL endpoint asynchronously. :param redirect_uri: Redirect url to receive oauth code :type redirect_uri: str @@ -974,16 +1055,17 @@ class KeycloakOpenID: async def a_token( self, - username="", - password="", - grant_type="password", - code="", - redirect_uri="", - totp=None, - scope="openid", - **extra - ): - """Retrieve user token asynchronously. + username: str = "", + password: str = "", + grant_type: str = "password", + code: str = "", + redirect_uri: str = "", + totp: int | None = None, + scope: str = "openid", + **extra: dict, + ) -> dict: + """ + Retrieve user token asynchronously. The token endpoint is used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on @@ -1038,8 +1120,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_refresh_token(self, refresh_token, grant_type="refresh_token"): - """Refresh the user token asynchronously. + async def a_refresh_token(self, refresh_token: str, grant_type: str = "refresh_token") -> dict: + """ + Refresh the user token asynchronously. The token endpoint is used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on @@ -1075,15 +1158,16 @@ class KeycloakOpenID: async def a_exchange_token( self, token: str, - audience: Optional[str] = None, - subject: Optional[str] = None, - subject_token_type: Optional[str] = None, - subject_issuer: Optional[str] = None, - requested_issuer: Optional[str] = None, - requested_token_type: str = "urn:ietf:params:oauth:token-type:refresh_token", + audience: str | None = None, + subject: str | None = None, + subject_token_type: str | None = None, + subject_issuer: str | None = None, + requested_issuer: str | None = None, + requested_token_type: str = "urn:ietf:params:oauth:token-type:refresh_token", # noqa: S107 scope: str = "openid", ) -> dict: - """Exchange user token asynchronously. + """ + Exchange user token asynchronously. Use a token to obtain an entirely different token. See https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange @@ -1131,8 +1215,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_userinfo(self, token): - """Get the user info object asynchronously. + async def a_userinfo(self, token: str) -> dict: + """ + Get the user info object asynchronously. The userinfo endpoint returns standard claims about the authenticated user, and is protected by a bearer token. @@ -1155,22 +1240,28 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_logout(self, refresh_token): - """Log out the authenticated user asynchronously. + async def a_logout(self, refresh_token: str) -> bytes: + """ + Log out the authenticated user asynchronously. :param refresh_token: Refresh token from Keycloak :type refresh_token: str :returns: Keycloak server response - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "refresh_token": refresh_token} payload = self._add_secret_key(payload) data_raw = await self.connection.a_raw_post(URL_LOGOUT.format(**params_path), data=payload) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_certs(self): - """Get certificates asynchronously. + async def a_certs(self) -> dict: + """ + Get certificates asynchronously. The certificate endpoint returns the public keys enabled by the realm, encoded as a JSON Web Key (JWK). Depending on the realm settings there can be one or more keys enabled @@ -1185,8 +1276,9 @@ class KeycloakOpenID: data_raw = await self.connection.a_raw_get(URL_CERTS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_public_key(self): - """Retrieve the public key asynchronously. + async def a_public_key(self) -> str: + """ + Retrieve the public key asynchronously. The public key is exposed by the realm page directly. @@ -1197,8 +1289,9 @@ class KeycloakOpenID: data_raw = await self.connection.a_raw_get(URL_REALM.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError)["public_key"] - async def a_entitlement(self, token, resource_server_id): - """Get entitlements from the token asynchronously. + async def a_entitlement(self, token: str, resource_server_id: str) -> dict: + """ + Get entitlements from the token asynchronously. Client applications can use a specific endpoint to obtain a special security token called a requesting party token (RPT). This token consists of all the entitlements @@ -1223,13 +1316,19 @@ class KeycloakOpenID: else self.connection.del_param_headers("Authorization") ) - if data_raw.status_code == 404 or data_raw.status_code == 405: + if data_raw.status_code in [HTTP_NOT_FOUND, HTTP_NOT_ALLOWED]: return raise_error_from_response(data_raw, KeycloakDeprecationError) return raise_error_from_response(data_raw, KeycloakGetError) # pragma: no cover - async def a_introspect(self, token, rpt=None, token_type_hint=None): - """Introspect the user token asynchronously. + async def a_introspect( + self, + token: str, + rpt: str | None = None, + token_type_hint: str | None = None, + ) -> dict: + """ + Introspect the user token asynchronously. The introspection endpoint is used to retrieve the active state of a token. It is can only be invoked by confidential clients. @@ -1252,19 +1351,21 @@ class KeycloakOpenID: orig_bearer = None bearer_changed = False - if token_type_hint == "requesting_party_token": + if token_type_hint == "requesting_party_token": # noqa: S105 if rpt: payload.update({"token": rpt, "token_type_hint": token_type_hint}) orig_bearer = self.connection.headers.get("Authorization") self.connection.add_param_headers("Authorization", "Bearer " + token) bearer_changed = True else: - raise KeycloakRPTNotFound("Can't found RPT.") + msg = "Can't find RPT." + raise KeycloakRPTNotFound(msg) payload = self._add_secret_key(payload) data_raw = await self.connection.a_raw_post( - URL_INTROSPECT.format(**params_path), data=payload + URL_INTROSPECT.format(**params_path), + data=payload, ) if bearer_changed: ( @@ -1274,8 +1375,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_decode_token(self, token, validate: bool = True, **kwargs): - """Decode user token asynchronously. + async def a_decode_token(self, token: str, validate: bool = True, **kwargs: dict) -> dict: + """ + Decode user token asynchronously. A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. This specification @@ -1310,19 +1412,26 @@ class KeycloakOpenID: return self._verify_token(token, key, **kwargs) - async def a_load_authorization_config(self, path): - """Load Keycloak settings (authorization) asynchronously. + async def a_load_authorization_config(self, path: str) -> None: + """ + Load Keycloak settings (authorization) asynchronously. :param path: settings file (json) :type path: str """ - with open(path, "r") as fp: - authorization_json = json.load(fp) + async with aiofiles.open(path) as fp: + authorization_json = json.loads(await fp.read()) self.authorization.load_config(authorization_json) - async def a_get_policies(self, token, method_token_info="introspect", **kwargs): - """Get policies by user token asynchronously. + async def a_get_policies( + self, + token: str, + method_token_info: str = "introspect", # noqa: S107 + **kwargs: dict, + ) -> list: + """ + Get policies by user token asynchronously. :param token: User token :type token: str @@ -1331,36 +1440,42 @@ class KeycloakOpenID: :param kwargs: Additional keyword arguments :type kwargs: dict :return: Policies - :rtype: dict + :rtype: list :raises KeycloakAuthorizationConfigError: In case of bad authorization configuration :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: - raise KeycloakAuthorizationConfigError( - "Keycloak settings not found. Load Authorization Keycloak settings." - ) + msg = "Keycloak settings not found. Load Authorization Keycloak settings." + raise KeycloakAuthorizationConfigError(msg) token_info = await self._a_token_info(token, method_token_info, **kwargs) - if method_token_info == "introspect" and not token_info["active"]: - raise KeycloakInvalidTokenError("Token expired or invalid.") + if method_token_info == "introspect" and not token_info["active"]: # noqa: S105 + msg = "Token expired or invalid." + raise KeycloakInvalidTokenError(msg) user_resources = token_info["resource_access"].get(self.client_id) if not user_resources: return None - policies = [] - - for policy_name, policy in self.authorization.policies.items(): - for role in user_resources["roles"]: - if self._build_name_role(role) in policy.roles: - policies.append(policy) + policies = [ + policy + for policy in self.authorization.policies.values() + for role in user_resources["roles"] + if self._build_name_role(role) in policy.roles + ] return list(set(policies)) - async def a_get_permissions(self, token, method_token_info="introspect", **kwargs): - """Get permission by user token asynchronously. + async def a_get_permissions( + self, + token: str, + method_token_info: str = "introspect", # noqa: S107 + **kwargs: dict, + ) -> list: + """ + Get permission by user token asynchronously. :param token: user token :type token: str @@ -1374,14 +1489,14 @@ class KeycloakOpenID: :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: - raise KeycloakAuthorizationConfigError( - "Keycloak settings not found. Load Authorization Keycloak settings." - ) + msg = "Keycloak settings not found. Load Authorization Keycloak settings." + raise KeycloakAuthorizationConfigError(msg) token_info = await self._a_token_info(token, method_token_info, **kwargs) - if method_token_info == "introspect" and not token_info["active"]: - raise KeycloakInvalidTokenError("Token expired or invalid.") + if method_token_info == "introspect" and not token_info["active"]: # noqa: S105 + msg = "Token expired or invalid." + raise KeycloakInvalidTokenError(msg) user_resources = token_info["resource_access"].get(self.client_id) @@ -1389,16 +1504,21 @@ class KeycloakOpenID: return None permissions = [] - - for policy_name, policy in self.authorization.policies.items(): + for policy in self.authorization.policies.values(): for role in user_resources["roles"]: if self._build_name_role(role) in policy.roles: permissions += policy.permissions return list(set(permissions)) - async def a_uma_permissions(self, token, permissions="", **extra_payload): - """Get UMA permissions by user token with requested permissions asynchronously. + async def a_uma_permissions( + self, + token: str, + permissions: str = "", + **extra_payload: dict, + ) -> dict: + """ + Get UMA permissions by user token with requested permissions asynchronously. The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be invoked by confidential clients. @@ -1442,8 +1562,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_has_uma_access(self, token, permissions): - """Determine whether user has uma permissions with specified user token asynchronously. + async def a_has_uma_access(self, token: str, permissions: list) -> AuthStatus: + """ + Determine whether user has uma permissions with specified user token asynchronously. :param token: user token :type token: str @@ -1458,13 +1579,17 @@ class KeycloakOpenID: try: granted = await self.a_uma_permissions(token, permissions) except (KeycloakPostError, KeycloakAuthenticationError) as e: - if e.response_code == 403: # pragma: no cover + if e.response_code == HTTP_FORBIDDEN: # pragma: no cover return AuthStatus( - is_logged_in=True, is_authorized=False, missing_permissions=needed + is_logged_in=True, + is_authorized=False, + missing_permissions=needed, ) - elif e.response_code == 401: + if e.response_code == HTTP_UNAUTHORIZED: return AuthStatus( - is_logged_in=False, is_authorized=False, missing_permissions=needed + is_logged_in=False, + is_authorized=False, + missing_permissions=needed, ) raise @@ -1475,14 +1600,17 @@ class KeycloakOpenID: needed.discard(resource) continue for scope in scopes: # pragma: no cover - needed.discard("{}#{}".format(resource, scope)) + needed.discard(f"{resource}#{scope}") return AuthStatus( - is_logged_in=True, is_authorized=len(needed) == 0, missing_permissions=needed + is_logged_in=True, + is_authorized=len(needed) == 0, + missing_permissions=needed, ) - async def a_register_client(self, token: str, payload: dict): - """Create a client asynchronously. + async def a_register_client(self, token: str, payload: dict) -> dict: + """ + Create a client asynchronously. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1500,7 +1628,8 @@ class KeycloakOpenID: orig_content_type = self.connection.headers.get("Content-Type") self.connection.add_param_headers("Content-Type", "application/json") data_raw = await self.connection.a_raw_post( - URL_CLIENT_REGISTRATION.format(**params_path), data=json.dumps(payload) + URL_CLIENT_REGISTRATION.format(**params_path), + data=json.dumps(payload), ) ( self.connection.add_param_headers("Authorization", orig_bearer) @@ -1514,8 +1643,9 @@ class KeycloakOpenID: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_device(self, scope: str = ""): - """Get device authorization grant asynchronously. + async def a_device(self, scope: str = "") -> dict: + """ + Get device authorization grant asynchronously. The device endpoint is used to obtain a user code verification and user authentication. The response contains a device_code, user_code, verification_uri, @@ -1541,8 +1671,9 @@ class KeycloakOpenID: data_raw = await self.connection.a_raw_post(URL_DEVICE.format(**params_path), data=payload) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_update_client(self, token: str, client_id: str, payload: dict): - """Update a client asynchronously. + async def a_update_client(self, token: str, client_id: str, payload: dict) -> bytes: + """ + Update a client asynchronously. ClientRepresentation: https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation @@ -1554,7 +1685,7 @@ class KeycloakOpenID: :param payload: ClientRepresentation :type payload: dict :return: Client Representation - :rtype: dict + :rtype: bytes """ params_path = {"realm-name": self.realm_name, "client-id": client_id} orig_bearer = self.connection.headers.get("Authorization") @@ -1567,7 +1698,8 @@ class KeycloakOpenID: payload["clientId"] = client_id data_raw = await self.connection.a_raw_put( - URL_CLIENT_UPDATE.format(**params_path), data=json.dumps(payload) + URL_CLIENT_UPDATE.format(**params_path), + data=json.dumps(payload), ) ( self.connection.add_param_headers("Authorization", orig_bearer) diff --git a/src/keycloak/keycloak_uma.py b/src/keycloak/keycloak_uma.py index 1c00c0f..511e68e 100644 --- a/src/keycloak/keycloak_uma.py +++ b/src/keycloak/keycloak_uma.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -21,38 +20,51 @@ # 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 UMA module. +""" +Keycloak UMA module. The module contains a UMA compatible client for keycloak: https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-federated-authz-2.0.html """ + +from __future__ import annotations + import json -from typing import Iterable +from typing import TYPE_CHECKING from urllib.parse import quote_plus from async_property import async_property from .connection import ConnectionManager from .exceptions import ( + HTTP_CREATED, + HTTP_NO_CONTENT, + HTTP_OK, KeycloakDeleteError, KeycloakGetError, KeycloakPostError, KeycloakPutError, raise_error_from_response, ) -from .openid_connection import KeycloakOpenIDConnection -from .uma_permissions import UMAPermission from .urls_patterns import URL_UMA_WELL_KNOWN +if TYPE_CHECKING: + from collections.abc import Iterable + + from .openid_connection import KeycloakOpenIDConnection + from .uma_permissions import UMAPermission + class KeycloakUMA: - """Keycloak UMA client. + """ + Keycloak UMA client. :param connection: OpenID connection manager """ - def __init__(self, connection: KeycloakOpenIDConnection): - """Init method. + def __init__(self, connection: KeycloakOpenIDConnection) -> None: + """ + Init method. :param connection: OpenID connection manager :type connection: KeycloakOpenIDConnection @@ -60,14 +72,15 @@ class KeycloakUMA: self.connection = connection self._well_known = None - def _fetch_well_known(self): + def _fetch_well_known(self) -> dict: params_path = {"realm-name": self.connection.realm_name} data_raw = self.connection.raw_get(URL_UMA_WELL_KNOWN.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) @staticmethod - def format_url(url, **kwargs): - """Substitute url path parameters. + def format_url(url: str, **kwargs: dict) -> str: + """ + Substitute url path parameters. Given a parameterized url string, returns the string after url encoding and substituting the given params. For example, @@ -84,8 +97,9 @@ class KeycloakUMA: return url.format(**{k: quote_plus(v) for k, v in kwargs.items()}) @staticmethod - async def a_format_url(url, **kwargs): - """Substitute url path parameters. + async def a_format_url(url: str, **kwargs: dict) -> str: + """ + Substitute url path parameters. Given a parameterized url string, returns the string after url encoding and substituting the given params. For example, @@ -102,8 +116,9 @@ class KeycloakUMA: return url.format(**{k: quote_plus(v) for k, v in kwargs.items()}) @property - def uma_well_known(self): - """Get the well_known UMA2 config. + def uma_well_known(self) -> dict: + """ + Get the well_known UMA2 config. :returns: It lists endpoints and other configuration options relevant :rtype: dict @@ -111,21 +126,25 @@ class KeycloakUMA: # per instance cache if not self._well_known: self._well_known = self._fetch_well_known() + return self._well_known @async_property - async def a_uma_well_known(self): - """Get the well_known UMA2 config async. + async def a_uma_well_known(self) -> dict: + """ + Get the well_known UMA2 config async. :returns: It lists endpoints and other configuration options relevant :rtype: dict """ if not self._well_known: self._well_known = await self.a__fetch_well_known() + return self._well_known - def resource_set_create(self, payload): - """Create a resource set. + def resource_set_create(self, payload: dict) -> dict | bytes: + """ + Create a resource set. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#rfc.section.2.2.1 @@ -139,12 +158,18 @@ class KeycloakUMA: :rtype: dict """ data_raw = self.connection.raw_post( - self.uma_well_known["resource_registration_endpoint"], data=json.dumps(payload) + self.uma_well_known["resource_registration_endpoint"], + data=json.dumps(payload), + ) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def resource_set_update(self, resource_id, payload): - """Update a resource set. + def resource_set_update(self, resource_id: str, payload: dict) -> bytes: + """ + Update a resource set. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#update-resource-set @@ -157,16 +182,22 @@ class KeycloakUMA: :param payload: ResourceRepresentation :type payload: dict :return: Response dict (empty) - :rtype: dict + :rtype: bytes """ url = self.format_url( - self.uma_well_known["resource_registration_endpoint"] + "/{id}", id=resource_id + self.uma_well_known["resource_registration_endpoint"] + "/{id}", + id=resource_id, ) data_raw = self.connection.raw_put(url, data=json.dumps(payload)) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - def resource_set_read(self, resource_id): - """Read a resource set. + def resource_set_read(self, resource_id: str) -> dict: + """ + Read a resource set. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#read-resource-set @@ -180,13 +211,15 @@ class KeycloakUMA: :rtype: dict """ url = self.format_url( - self.uma_well_known["resource_registration_endpoint"] + "/{id}", id=resource_id + self.uma_well_known["resource_registration_endpoint"] + "/{id}", + id=resource_id, ) data_raw = self.connection.raw_get(url) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def resource_set_delete(self, resource_id): - """Delete a resource set. + def resource_set_delete(self, resource_id: str) -> bytes: + """ + Delete a resource set. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#delete-resource-set @@ -197,10 +230,15 @@ class KeycloakUMA: :rtype: dict """ url = self.format_url( - self.uma_well_known["resource_registration_endpoint"] + "/{id}", id=resource_id + self.uma_well_known["resource_registration_endpoint"] + "/{id}", + id=resource_id, ) data_raw = self.connection.raw_delete(url) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) def resource_set_list_ids( self, @@ -210,11 +248,12 @@ class KeycloakUMA: owner: str = "", resource_type: str = "", scope: str = "", - matchingUri: bool = False, + matchingUri: bool = False, # noqa: N803 first: int = 0, maximum: int = -1, - ): - """Query for list of resource set ids. + ) -> list: + """ + Query for list of resource set ids. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#list-resource-sets @@ -240,7 +279,7 @@ class KeycloakUMA: :return: List of ids :rtype: List[str] """ - query = dict() + query = {} if name: query["name"] = name if exact_name: @@ -261,12 +300,14 @@ class KeycloakUMA: query["max"] = maximum data_raw = self.connection.raw_get( - self.uma_well_known["resource_registration_endpoint"], **query + self.uma_well_known["resource_registration_endpoint"], + **query, ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def resource_set_list(self): - """List all resource sets. + def resource_set_list(self) -> list: + """ + List all resource sets. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#list-resource-sets @@ -281,8 +322,9 @@ class KeycloakUMA: resource = self.resource_set_read(resource_id) yield resource - def permission_ticket_create(self, permissions: Iterable[UMAPermission]): - """Create a permission ticket. + def permission_ticket_create(self, permissions: Iterable[UMAPermission]) -> dict: + """ + Create a permission ticket. :param permissions: Iterable of uma permissions to validate the token against :type permissions: Iterable[UMAPermission] @@ -290,19 +332,23 @@ class KeycloakUMA: :rtype: boolean :raises KeycloakPostError: In case permission resource not found """ - resources = dict() + resources = {} for permission in permissions: resource_id = getattr(permission, "resource_id", None) if resource_id is None: resource_ids = self.resource_set_list_ids( - exact_name=True, name=permission.resource, first=0, maximum=1 + exact_name=True, + name=permission.resource, + first=0, + maximum=1, ) if not resource_ids: - raise KeycloakPostError("Invalid resource specified") + msg = "Invalid resource specified" + raise KeycloakPostError(msg) - setattr(permission, "resource_id", resource_ids[0]) + permission.resource_id = resource_ids[0] resources.setdefault(resource_id, set()) if permission.scope: @@ -314,12 +360,19 @@ class KeycloakUMA: ] data_raw = self.connection.raw_post( - self.uma_well_known["permission_endpoint"], data=json.dumps(payload) + self.uma_well_known["permission_endpoint"], + data=json.dumps(payload), ) return raise_error_from_response(data_raw, KeycloakPostError) - def permissions_check(self, token, permissions: Iterable[UMAPermission], **extra_payload): - """Check UMA permissions by user token with requested permissions. + def permissions_check( + self, + token: str, + permissions: Iterable[UMAPermission], + **extra_payload: dict, + ) -> bool: + """ + Check UMA permissions by user token with requested permissions. The token endpoint is used to check UMA permissions from Keycloak. It can only be invoked by confidential clients. @@ -358,8 +411,9 @@ class KeycloakUMA: return False return data.get("result", False) - def policy_resource_create(self, resource_id, payload): - """Create permission policy for resource. + def policy_resource_create(self, resource_id: str, payload: dict) -> dict: + """ + Create permission policy for resource. Supports name, description, scopes, roles, groups, clients @@ -373,12 +427,14 @@ class KeycloakUMA: :rtype: dict """ data_raw = self.connection.raw_post( - self.uma_well_known["policy_endpoint"] + f"/{resource_id}", data=json.dumps(payload) + self.uma_well_known["policy_endpoint"] + f"/{resource_id}", + data=json.dumps(payload), ) return raise_error_from_response(data_raw, KeycloakPostError) - def policy_update(self, policy_id, payload): - """Update permission policy. + def policy_update(self, policy_id: str, payload: dict) -> dict: + """ + Update permission policy. https://www.keycloak.org/docs/latest/authorization_services/#associating-a-permission-with-a-resource https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation @@ -391,12 +447,14 @@ class KeycloakUMA: :rtype: dict """ data_raw = self.connection.raw_put( - self.uma_well_known["policy_endpoint"] + f"/{policy_id}", data=json.dumps(payload) + self.uma_well_known["policy_endpoint"] + f"/{policy_id}", + data=json.dumps(payload), ) return raise_error_from_response(data_raw, KeycloakPutError) - def policy_delete(self, policy_id): - """Delete permission policy. + def policy_delete(self, policy_id: str) -> dict: + """ + Delete permission policy. https://www.keycloak.org/docs/latest/authorization_services/#removing-a-permission https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation @@ -407,7 +465,7 @@ class KeycloakUMA: :rtype: dict """ data_raw = self.connection.raw_delete( - self.uma_well_known["policy_endpoint"] + f"/{policy_id}" + self.uma_well_known["policy_endpoint"] + f"/{policy_id}", ) return raise_error_from_response(data_raw, KeycloakDeleteError) @@ -418,8 +476,9 @@ class KeycloakUMA: scope: str = "", first: int = 0, maximum: int = -1, - ): - """Query permission policies. + ) -> list: + """ + Query permission policies. https://www.keycloak.org/docs/latest/authorization_services/#querying-permission @@ -437,7 +496,7 @@ class KeycloakUMA: :return: List of ids :rtype: List[str] """ - query = dict() + query = {} if name: query["name"] = name if resource: @@ -452,8 +511,9 @@ class KeycloakUMA: data_raw = self.connection.raw_get(self.uma_well_known["policy_endpoint"], **query) return raise_error_from_response(data_raw, KeycloakGetError) - async def a__fetch_well_known(self): - """Get the well_known UMA2 config async. + async def a__fetch_well_known(self) -> dict: + """ + Get the well_known UMA2 config async. :returns: It lists endpoints and other configuration options relevant :rtype: dict @@ -462,8 +522,9 @@ class KeycloakUMA: data_raw = await self.connection.a_raw_get(URL_UMA_WELL_KNOWN.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - async def a_resource_set_create(self, payload): - """Create a resource set asynchronously. + async def a_resource_set_create(self, payload: dict) -> dict: + """ + Create a resource set asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#rfc.section.2.2.1 @@ -480,10 +541,15 @@ class KeycloakUMA: (await self.a_uma_well_known)["resource_registration_endpoint"], data=json.dumps(payload), ) - return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_CREATED], + ) - async def a_resource_set_update(self, resource_id, payload): - """Update a resource set asynchronously. + async def a_resource_set_update(self, resource_id: str, payload: dict) -> bytes: + """ + Update a resource set asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#update-resource-set @@ -496,17 +562,22 @@ class KeycloakUMA: :param payload: ResourceRepresentation :type payload: dict :return: Response dict (empty) - :rtype: dict + :rtype: bytes """ url = self.format_url( (await self.a_uma_well_known)["resource_registration_endpoint"] + "/{id}", id=resource_id, ) data_raw = await self.connection.a_raw_put(url, data=json.dumps(payload)) - return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakPutError, + expected_codes=[HTTP_NO_CONTENT], + ) - async def a_resource_set_read(self, resource_id): - """Read a resource set asynchronously. + async def a_resource_set_read(self, resource_id: str) -> dict: + """ + Read a resource set asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#read-resource-set @@ -524,10 +595,11 @@ class KeycloakUMA: id=resource_id, ) data_raw = await self.connection.a_raw_get(url) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_resource_set_delete(self, resource_id): - """Delete a resource set asynchronously. + async def a_resource_set_delete(self, resource_id: str) -> bytes: + """ + Delete a resource set asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#delete-resource-set @@ -535,14 +607,18 @@ class KeycloakUMA: :param resource_id: id of the resource :type resource_id: str :return: Response dict (empty) - :rtype: dict + :rtype: bytes """ url = self.format_url( (await self.a_uma_well_known)["resource_registration_endpoint"] + "/{id}", id=resource_id, ) data_raw = await self.connection.a_raw_delete(url) - return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + return raise_error_from_response( + data_raw, + KeycloakDeleteError, + expected_codes=[HTTP_NO_CONTENT], + ) async def a_resource_set_list_ids( self, @@ -552,11 +628,12 @@ class KeycloakUMA: owner: str = "", resource_type: str = "", scope: str = "", - matchingUri: bool = False, + matchingUri: bool = False, # noqa: N803 first: int = 0, maximum: int = -1, - ): - """Query for list of resource set ids asynchronously. + ) -> list: + """ + Query for list of resource set ids asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#list-resource-sets @@ -582,7 +659,7 @@ class KeycloakUMA: :return: List of ids :rtype: List[str] """ - query = dict() + query = {} if name: query["name"] = name if exact_name: @@ -603,12 +680,14 @@ class KeycloakUMA: query["max"] = maximum data_raw = await self.connection.a_raw_get( - (await self.a_uma_well_known)["resource_registration_endpoint"], **query + (await self.a_uma_well_known)["resource_registration_endpoint"], + **query, ) - return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - async def a_resource_set_list(self): - """List all resource sets asynchronously. + async def a_resource_set_list(self) -> list: + """ + List all resource sets asynchronously. Spec https://docs.kantarainitiative.org/uma/rec-oauth-resource-reg-v1_0_1.html#list-resource-sets @@ -623,8 +702,9 @@ class KeycloakUMA: resource = await self.a_resource_set_read(resource_id) yield resource - async def a_permission_ticket_create(self, permissions: Iterable[UMAPermission]): - """Create a permission ticket asynchronously. + async def a_permission_ticket_create(self, permissions: Iterable[UMAPermission]) -> bool: + """ + Create a permission ticket asynchronously. :param permissions: Iterable of uma permissions to validate the token against :type permissions: Iterable[UMAPermission] @@ -632,19 +712,23 @@ class KeycloakUMA: :rtype: boolean :raises KeycloakPostError: In case permission resource not found """ - resources = dict() + resources = {} for permission in permissions: resource_id = getattr(permission, "resource_id", None) if resource_id is None: resource_ids = await self.a_resource_set_list_ids( - exact_name=True, name=permission.resource, first=0, maximum=1 + exact_name=True, + name=permission.resource, + first=0, + maximum=1, ) if not resource_ids: - raise KeycloakPostError("Invalid resource specified") + msg = "Invalid resource specified" + raise KeycloakPostError(msg) - setattr(permission, "resource_id", resource_ids[0]) + permission.resource_id = resource_ids[0] resources.setdefault(resource_id, set()) if permission.scope: @@ -656,14 +740,19 @@ class KeycloakUMA: ] data_raw = await self.connection.a_raw_post( - (await self.a_uma_well_known)["permission_endpoint"], data=json.dumps(payload) + (await self.a_uma_well_known)["permission_endpoint"], + data=json.dumps(payload), ) return raise_error_from_response(data_raw, KeycloakPostError) async def a_permissions_check( - self, token, permissions: Iterable[UMAPermission], **extra_payload - ): - """Check UMA permissions by user token with requested permissions asynchronously. + self, + token: str, + permissions: Iterable[UMAPermission], + **extra_payload: dict, + ) -> bool: + """ + Check UMA permissions by user token with requested permissions asynchronously. The token endpoint is used to check UMA permissions from Keycloak. It can only be invoked by confidential clients. @@ -696,7 +785,8 @@ class KeycloakUMA: connection.add_param_headers("Authorization", "Bearer " + token) connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await connection.a_raw_post( - (await self.a_uma_well_known)["token_endpoint"], data=payload + (await self.a_uma_well_known)["token_endpoint"], + data=payload, ) try: data = raise_error_from_response(data_raw, KeycloakPostError) @@ -704,8 +794,9 @@ class KeycloakUMA: return False return data.get("result", False) - async def a_policy_resource_create(self, resource_id, payload): - """Create permission policy for resource asynchronously. + async def a_policy_resource_create(self, resource_id: str, payload: dict) -> dict: + """ + Create permission policy for resource asynchronously. Supports name, description, scopes, roles, groups, clients @@ -724,8 +815,9 @@ class KeycloakUMA: ) return raise_error_from_response(data_raw, KeycloakPostError) - async def a_policy_update(self, policy_id, payload): - """Update permission policy asynchronously. + async def a_policy_update(self, policy_id: str, payload: dict) -> dict: + """ + Update permission policy asynchronously. https://www.keycloak.org/docs/latest/authorization_services/#associating-a-permission-with-a-resource https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation @@ -743,8 +835,9 @@ class KeycloakUMA: ) return raise_error_from_response(data_raw, KeycloakPutError) - async def a_policy_delete(self, policy_id): - """Delete permission policy asynchronously. + async def a_policy_delete(self, policy_id: str) -> dict: + """ + Delete permission policy asynchronously. https://www.keycloak.org/docs/latest/authorization_services/#removing-a-permission https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_policyrepresentation @@ -755,7 +848,7 @@ class KeycloakUMA: :rtype: dict """ data_raw = await self.connection.a_raw_delete( - (await self.a_uma_well_known)["policy_endpoint"] + f"/{policy_id}" + (await self.a_uma_well_known)["policy_endpoint"] + f"/{policy_id}", ) return raise_error_from_response(data_raw, KeycloakDeleteError) @@ -766,8 +859,9 @@ class KeycloakUMA: scope: str = "", first: int = 0, maximum: int = -1, - ): - """Query permission policies asynchronously. + ) -> list: + """ + Query permission policies asynchronously. https://www.keycloak.org/docs/latest/authorization_services/#querying-permission @@ -785,7 +879,7 @@ class KeycloakUMA: :return: List of ids :rtype: List[str] """ - query = dict() + query = {} if name: query["name"] = name if resource: @@ -798,6 +892,7 @@ class KeycloakUMA: query["max"] = maximum data_raw = await self.connection.a_raw_get( - (await self.a_uma_well_known)["policy_endpoint"], **query + (await self.a_uma_well_known)["policy_endpoint"], + **query, ) return raise_error_from_response(data_raw, KeycloakGetError) diff --git a/src/keycloak/openid_connection.py b/src/keycloak/openid_connection.py index bf601bb..3ec0519 100644 --- a/src/keycloak/openid_connection.py +++ b/src/keycloak/openid_connection.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -21,22 +20,31 @@ # 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 OpenID Connection Manager module. +""" +Keycloak OpenID Connection Manager module. The module contains mainly the implementation of KeycloakOpenIDConnection class. This is an extension of the ConnectionManager class, and handles the automatic refresh of openid tokens when required. """ -from datetime import datetime, timedelta +from __future__ import annotations + +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from httpx import Response as AsyncResponse + from requests import Response from .connection import ConnectionManager -from .exceptions import KeycloakPostError +from .exceptions import HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, KeycloakPostError from .keycloak_openid import KeycloakOpenID class KeycloakOpenIDConnection(ConnectionManager): - """A class to help with OpenID connections which can auto refresh tokens. + """ + A class to help with OpenID connections which can auto refresh tokens. :param object: _description_ :type object: _type_ @@ -59,23 +67,24 @@ class KeycloakOpenIDConnection(ConnectionManager): def __init__( self, - server_url, - grant_type=None, - username=None, - password=None, - token=None, - totp=None, - realm_name="master", - client_id="admin-cli", - verify=True, - client_secret_key=None, - custom_headers=None, - user_realm_name=None, - timeout=60, - cert=None, - max_retries=1, - ): - """Init method. + server_url: str, + grant_type: str | None = None, + username: str | None = None, + password: str | None = None, + token: str | None = None, + totp: str | None = None, + realm_name: str = "master", + client_id: str = "admin-cli", + verify: str | bool = True, + client_secret_key: str | None = None, + custom_headers: dict | None = None, + user_realm_name: str | None = None, + timeout: int | None = 60, + cert: str | tuple | None = None, + max_retries: int = 1, + ) -> None: + """ + Init method. :param server_url: Keycloak server url :type server_url: str @@ -144,11 +153,13 @@ class KeycloakOpenIDConnection(ConnectionManager): timeout=self.timeout, verify=self.verify, cert=cert, + max_retries=max_retries, ) @property - def server_url(self): - """Get server url. + def server_url(self) -> str: + """ + Get server url. :returns: Keycloak server url :rtype: str @@ -156,12 +167,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self.base_url @server_url.setter - def server_url(self, value): + def server_url(self, value: str) -> None: self.base_url = value @property - def grant_type(self): - """Get grant type. + def grant_type(self) -> str: + """ + Get grant type. :returns: Grant type :rtype: str @@ -169,12 +181,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._grant_type @grant_type.setter - def grant_type(self, value): + def grant_type(self, value: str) -> None: self._grant_type = value @property - def realm_name(self): - """Get realm name. + def realm_name(self) -> str: + """ + Get realm name. :returns: Realm name :rtype: str @@ -182,12 +195,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._realm_name @realm_name.setter - def realm_name(self, value): + def realm_name(self, value: str) -> None: self._realm_name = value @property - def client_id(self): - """Get client id. + def client_id(self) -> str: + """ + Get client id. :returns: Client id :rtype: str @@ -195,12 +209,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._client_id @client_id.setter - def client_id(self, value): + def client_id(self, value: str) -> None: self._client_id = value @property - def client_secret_key(self): - """Get client secret key. + def client_secret_key(self) -> str: + """ + Get client secret key. :returns: Client secret key :rtype: str @@ -208,12 +223,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._client_secret_key @client_secret_key.setter - def client_secret_key(self, value): + def client_secret_key(self, value: str) -> None: self._client_secret_key = value @property - def username(self): - """Get username. + def username(self) -> str: + """ + Get username. :returns: Admin username :rtype: str @@ -221,12 +237,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._username @username.setter - def username(self, value): + def username(self, value: str) -> None: self._username = value @property - def password(self): - """Get password. + def password(self) -> str: + """ + Get password. :returns: Admin password :rtype: str @@ -234,12 +251,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._password @password.setter - def password(self, value): + def password(self, value: str) -> None: self._password = value @property - def totp(self): - """Get totp. + def totp(self) -> str: + """ + Get totp. :returns: TOTP :rtype: str @@ -247,12 +265,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._totp @totp.setter - def totp(self, value): + def totp(self, value: str) -> None: self._totp = value @property - def token(self): - """Get token. + def token(self) -> dict: + """ + Get token. :returns: Access and refresh token :rtype: dict @@ -260,17 +279,18 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._token @token.setter - def token(self, value): + def token(self, value: dict) -> None: self._token = value - self._expires_at = datetime.now() + timedelta( - seconds=int(self.token_lifetime_fraction * self.token["expires_in"] if value else 0) + self._expires_at = datetime.now(tz=timezone.utc) + timedelta( + seconds=int(self.token_lifetime_fraction * self.token["expires_in"] if value else 0), ) if value is not None: self.add_param_headers("Authorization", "Bearer " + value.get("access_token")) @property - def expires_at(self): - """Get token expiry time. + def expires_at(self) -> datetime: + """ + Get token expiry time. :returns: Datetime at which the current token will expire :rtype: datetime @@ -278,8 +298,9 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._expires_at @property - def user_realm_name(self): - """Get user realm name. + def user_realm_name(self) -> str: + """ + Get user realm name. :returns: User realm name :rtype: str @@ -287,12 +308,13 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._user_realm_name @user_realm_name.setter - def user_realm_name(self, value): + def user_realm_name(self, value: str) -> None: self._user_realm_name = value @property - def custom_headers(self): - """Get custom headers. + def custom_headers(self) -> dict: + """ + Get custom headers. :returns: Custom headers :rtype: dict @@ -300,7 +322,7 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._custom_headers @custom_headers.setter - def custom_headers(self, value): + def custom_headers(self, value: dict) -> None: self._custom_headers = value if self.custom_headers is not None: # merge custom headers to main headers @@ -308,7 +330,8 @@ class KeycloakOpenIDConnection(ConnectionManager): @property def keycloak_openid(self) -> KeycloakOpenID: - """Get the KeycloakOpenID object. + """ + Get the KeycloakOpenID object. The KeycloakOpenID is used to refresh tokens @@ -321,7 +344,7 @@ class KeycloakOpenIDConnection(ConnectionManager): elif self.realm_name: token_realm_name = self.realm_name else: - token_realm_name = "master" + token_realm_name = "master" # noqa: S105 self._keycloak_openid = KeycloakOpenID( server_url=self.server_url, @@ -336,20 +359,25 @@ class KeycloakOpenIDConnection(ConnectionManager): return self._keycloak_openid - def get_token(self): - """Get admin token. + def get_token(self) -> None: + """ + Get admin token. The admin token is then set in the `token` attribute. """ if self.grant_type: self.token = self.keycloak_openid.token( - self.username, self.password, grant_type=self.grant_type, totp=self.totp + self.username, + self.password, + grant_type=self.grant_type, + totp=self.totp, ) else: self.token = None - def refresh_token(self): - """Refresh the token. + def refresh_token(self) -> None: + """ + Refresh the token. :raises KeycloakPostError: In case the refresh token request failed. """ @@ -365,17 +393,20 @@ class KeycloakOpenIDConnection(ConnectionManager): b"Token is not active", b"Session not active", ] - if e.response_code == 400 and any(err in e.response_body for err in list_errors): + if e.response_code == HTTP_BAD_REQUEST and any( + err in e.response_body for err in list_errors + ): self.get_token() else: raise - def _refresh_if_required(self): - if datetime.now() >= self.expires_at: + def _refresh_if_required(self) -> None: + if datetime.now(tz=timezone.utc) >= self.expires_at: self.refresh_token() - def raw_get(self, *args, **kwargs): - """Call connection.raw_get. + def raw_get(self, *args: list, **kwargs: dict) -> Response: + """ + Call connection.raw_get. If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token and try *get* once more. @@ -389,14 +420,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ self._refresh_if_required() r = super().raw_get(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: self.refresh_token() r = super().raw_get(*args, **kwargs) return r - def raw_post(self, *args, **kwargs): - """Call connection.raw_post. + def raw_post(self, *args: list, **kwargs: dict) -> Response: + """ + Call connection.raw_post. If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token and try *post* once more. @@ -410,14 +442,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ self._refresh_if_required() r = super().raw_post(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: self.refresh_token() r = super().raw_post(*args, **kwargs) return r - def raw_put(self, *args, **kwargs): - """Call connection.raw_put. + def raw_put(self, *args: list, **kwargs: dict) -> Response: + """ + Call connection.raw_put. If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token and try *put* once more. @@ -431,14 +464,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ self._refresh_if_required() r = super().raw_put(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: self.refresh_token() r = super().raw_put(*args, **kwargs) return r - def raw_delete(self, *args, **kwargs): - """Call connection.raw_delete. + def raw_delete(self, *args: list, **kwargs: dict) -> Response: + """ + Call connection.raw_delete. If auto_refresh is set for *delete* and *access_token* is expired, it will refresh the token and try *delete* once more. @@ -452,26 +486,31 @@ class KeycloakOpenIDConnection(ConnectionManager): """ self._refresh_if_required() r = super().raw_delete(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: self.refresh_token() r = super().raw_delete(*args, **kwargs) return r - async def a_get_token(self): - """Get admin token. + async def a_get_token(self) -> None: + """ + Get admin token. The admin token is then set in the `token` attribute. """ if self.grant_type: self.token = await self.keycloak_openid.a_token( - self.username, self.password, grant_type=self.grant_type, totp=self.totp + self.username, + self.password, + grant_type=self.grant_type, + totp=self.totp, ) else: self.token = None - async def a_refresh_token(self): - """Refresh the token. + async def a_refresh_token(self) -> None: + """ + Refresh the token. :raises KeycloakPostError: In case the refresh token request failed. """ @@ -487,18 +526,21 @@ class KeycloakOpenIDConnection(ConnectionManager): b"Token is not active", b"Session not active", ] - if e.response_code == 400 and any(err in e.response_body for err in list_errors): + if e.response_code == HTTP_BAD_REQUEST and any( + err in e.response_body for err in list_errors + ): await self.a_get_token() else: raise - async def a__refresh_if_required(self): + async def a__refresh_if_required(self) -> None: """Refresh the token if it is expired.""" - if datetime.now() >= self.expires_at: + if datetime.now(tz=timezone.utc) >= self.expires_at: await self.a_refresh_token() - async def a_raw_get(self, *args, **kwargs): - """Call connection.raw_get. + async def a_raw_get(self, *args: list, **kwargs: dict) -> AsyncResponse: + """ + Call connection.raw_get. If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token and try *get* once more. @@ -512,14 +554,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ await self.a__refresh_if_required() r = await super().a_raw_get(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: await self.a_refresh_token() r = await super().a_raw_get(*args, **kwargs) return r - async def a_raw_post(self, *args, **kwargs): - """Call connection.raw_post. + async def a_raw_post(self, *args: list, **kwargs: dict) -> AsyncResponse: + """ + Call connection.raw_post. If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token and try *post* once more. @@ -533,14 +576,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ await self.a__refresh_if_required() r = await super().a_raw_post(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: await self.a_refresh_token() r = await super().a_raw_post(*args, **kwargs) return r - async def a_raw_put(self, *args, **kwargs): - """Call connection.raw_put. + async def a_raw_put(self, *args: list, **kwargs: dict) -> AsyncResponse: + """ + Call connection.raw_put. If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token and try *put* once more. @@ -554,14 +598,15 @@ class KeycloakOpenIDConnection(ConnectionManager): """ await self.a__refresh_if_required() r = await super().a_raw_put(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: await self.a_refresh_token() r = await super().a_raw_put(*args, **kwargs) return r - async def a_raw_delete(self, *args, **kwargs): - """Call connection.raw_delete. + async def a_raw_delete(self, *args: list, **kwargs: dict) -> AsyncResponse: + """ + Call connection.raw_delete. If auto_refresh is set for *delete* and *access_token* is expired, it will refresh the token and try *delete* once more. @@ -575,7 +620,7 @@ class KeycloakOpenIDConnection(ConnectionManager): """ await self.a__refresh_if_required() r = await super().a_raw_delete(*args, **kwargs) - if r.status_code == 401: + if r.status_code == HTTP_UNAUTHORIZED: await self.a_refresh_token() r = await super().a_raw_delete(*args, **kwargs) diff --git a/src/keycloak/uma_permissions.py b/src/keycloak/uma_permissions.py index 2e21bc6..0f7cffd 100644 --- a/src/keycloak/uma_permissions.py +++ b/src/keycloak/uma_permissions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -23,11 +22,14 @@ """User-managed access permissions module.""" +from __future__ import annotations + from keycloak.exceptions import KeycloakPermissionFormatError, PermissionDefinitionError class UMAPermission: - """A class to conveniently assemble permissions. + """ + A class to conveniently assemble permissions. The class itself is callable, and will return the assembled permission. @@ -47,8 +49,14 @@ class UMAPermission: :type scope: str """ - def __init__(self, permission=None, resource="", scope=""): - """Init method. + def __init__( + self, + permission: UMAPermission | None = None, + resource: str = "", + scope: str = "", + ) -> None: + """ + Init method. :param permission: Permission :type permission: UMAPermission @@ -63,16 +71,16 @@ class UMAPermission: if permission: if not isinstance(permission, UMAPermission): - raise PermissionDefinitionError( - "can't determine if '{}' is a resource or scope".format(permission) - ) + msg = f"can't determine if '{permission}' is a resource or scope" + raise PermissionDefinitionError(msg) if permission.resource: self.resource = str(permission.resource) if permission.scope: self.scope = str(permission.scope) - def __str__(self): - """Str method. + def __str__(self) -> str: + """ + Str method. :returns: String representation :rtype: str @@ -80,20 +88,22 @@ class UMAPermission: scope = self.scope if scope: scope = "#" + scope - return "{}{}".format(self.resource, scope) + return f"{self.resource}{scope}" - def __eq__(self, __o: object) -> bool: - """Eq method. + def __eq__(self, other: object) -> bool: + """ + Eq method. :param __o: The other object :type __o: object :returns: Equality boolean :rtype: bool """ - return str(self) == str(__o) + return str(self) == str(other) def __repr__(self) -> str: - """Repr method. + """ + Repr method. :returns: The object representation :rtype: str @@ -101,15 +111,22 @@ class UMAPermission: 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="") -> "UMAPermission": - """Call method. + def __call__( + self, + permission: UMAPermission | None = None, + resource: str = "", + scope: str = "", + ) -> UMAPermission: + """ + Call method. :param permission: Permission :type permission: UMAPermission @@ -131,9 +148,8 @@ class UMAPermission: if permission: if not isinstance(permission, UMAPermission): - raise PermissionDefinitionError( - "can't determine if '{}' is a resource or scope".format(permission) - ) + msg = f"can't determine if '{permission}' is a resource or scope" + raise PermissionDefinitionError(msg) if permission.resource: result_resource = str(permission.resource) if permission.scope: @@ -143,7 +159,8 @@ class UMAPermission: class Resource(UMAPermission): - """A UMAPermission Resource class to conveniently assemble permissions. + """ + A UMAPermission Resource class to conveniently assemble permissions. The class itself is callable, and will return the assembled permission. @@ -151,8 +168,9 @@ class Resource(UMAPermission): :type resource: str """ - def __init__(self, resource): - """Init method. + def __init__(self, resource: Resource) -> None: + """ + Init method. :param resource: Resource :type resource: str @@ -161,7 +179,8 @@ class Resource(UMAPermission): class Scope(UMAPermission): - """A UMAPermission Scope class to conveniently assemble permissions. + """ + A UMAPermission Scope class to conveniently assemble permissions. The class itself is callable, and will return the assembled permission. @@ -169,8 +188,9 @@ class Scope(UMAPermission): :type scope: str """ - def __init__(self, scope): - """Init method. + def __init__(self, scope: Scope) -> None: + """ + Init method. :param scope: Scope :type scope: str @@ -179,7 +199,8 @@ class Scope(UMAPermission): class AuthStatus: - """A class that represents the authorization/login status of a user associated with a token. + """ + A class that represents the authorization/login status of a user associated with a token. This has to evaluate to True if and only if the user is properly authorized for the requested resource. @@ -192,8 +213,9 @@ class AuthStatus: :type missing_permissions: set """ - def __init__(self, is_logged_in, is_authorized, missing_permissions): - """Init method. + def __init__(self, is_logged_in: bool, is_authorized: bool, missing_permissions: set) -> None: + """ + Init method. :param is_logged_in: Is logged in indicator :type is_logged_in: bool @@ -206,16 +228,18 @@ class AuthStatus: self.is_authorized = is_authorized self.missing_permissions = missing_permissions - def __bool__(self): - """Bool method. + def __bool__(self) -> bool: + """ + Bool method. :returns: Boolean representation :rtype: bool """ return self.is_authorized - def __repr__(self): - """Repr method. + def __repr__(self) -> str: + """ + Repr method. :returns: The object representation :rtype: str @@ -228,8 +252,9 @@ class AuthStatus: ) -def build_permission_param(permissions): - """Transform permissions to a set, so they are usable for requests. +def build_permission_param(permissions: str | list | dict) -> set: + """ + Transform permissions to a set, so they are usable for requests. :param permissions: Permissions :type permissions: str | Iterable[str] | dict[str, str] | dict[str, Iterabble[str]] @@ -240,9 +265,9 @@ def build_permission_param(permissions): if permissions is None or permissions == "": return set() if isinstance(permissions, str): - return set((permissions,)) + return set(permissions) if isinstance(permissions, UMAPermission): - return set((str(permissions),)) + return set(str(permissions)) try: # treat as dictionary of permissions result = set() @@ -250,26 +275,26 @@ def build_permission_param(permissions): if scopes is None: result.add(resource) elif isinstance(scopes, str): - result.add("{}#{}".format(resource, scopes)) + result.add(f"{resource}#{scopes}") else: try: for scope in scopes: if not isinstance(scope, str): - raise KeycloakPermissionFormatError( - "misbuilt permission {}".format(permissions) - ) - result.add("{}#{}".format(resource, scope)) - except TypeError: - raise KeycloakPermissionFormatError( - "misbuilt permission {}".format(permissions) - ) - return result + msg = f"misbuilt permission {permissions}" + raise KeycloakPermissionFormatError(msg) + result.add(f"{resource}#{scope}") + except TypeError as e: + msg = f"misbuilt permission {permissions}" + raise KeycloakPermissionFormatError(msg) from e except AttributeError: pass + else: + return result result = set() for permission in permissions: if not isinstance(permission, (str, UMAPermission)): - raise KeycloakPermissionFormatError("misbuilt permission {}".format(permissions)) + msg = f"misbuilt permission {permissions}" + raise KeycloakPermissionFormatError(msg) result.add(str(permission)) return result diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index 07aaccd..95f82b8 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # The MIT License (MIT) # @@ -27,7 +26,7 @@ URL_REALM = "realms/{realm-name}" URL_WELL_KNOWN_BASE = "realms/{realm-name}/.well-known" URL_WELL_KNOWN = URL_WELL_KNOWN_BASE + "/openid-configuration" -URL_TOKEN = "realms/{realm-name}/protocol/openid-connect/token" +URL_TOKEN = "realms/{realm-name}/protocol/openid-connect/token" # noqa: S105 URL_USERINFO = "realms/{realm-name}/protocol/openid-connect/userinfo" URL_LOGOUT = "realms/{realm-name}/protocol/openid-connect/logout" URL_CERTS = "realms/{realm-name}/protocol/openid-connect/certs" @@ -49,7 +48,7 @@ URL_ADMIN_USER = "admin/realms/{realm-name}/users/{id}" URL_ADMIN_USER_CONSENTS = "admin/realms/{realm-name}/users/{id}/consents" URL_ADMIN_SEND_UPDATE_ACCOUNT = "admin/realms/{realm-name}/users/{id}/execute-actions-email" URL_ADMIN_SEND_VERIFY_EMAIL = "admin/realms/{realm-name}/users/{id}/send-verify-email" -URL_ADMIN_RESET_PASSWORD = "admin/realms/{realm-name}/users/{id}/reset-password" +URL_ADMIN_RESET_PASSWORD = "admin/realms/{realm-name}/users/{id}/reset-password" # noqa: S105 URL_ADMIN_GET_SESSIONS = "admin/realms/{realm-name}/users/{id}/sessions" URL_ADMIN_USER_ALL_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings" URL_ADMIN_USER_CLIENT_ROLES = ( diff --git a/tests/conftest.py b/tests/conftest.py index af1f9af..3887cb2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,8 +3,9 @@ import ipaddress import os import uuid +from collections.abc import Generator from datetime import datetime, timedelta -from typing import Generator, Tuple +from typing import Tuple import freezegun import pytest @@ -17,7 +18,7 @@ from cryptography.x509.oid import NameOID from keycloak import KeycloakAdmin, KeycloakOpenID, KeycloakOpenIDConnection, KeycloakUMA -class KeycloakTestEnv(object): +class KeycloakTestEnv: """Wrapper for test Keycloak connection configuration. :param host: Hostname @@ -193,7 +194,7 @@ def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin): "enabled": True, "publicClient": True, "protocol": "openid-connect", - } + }, ) # Return OID yield KeycloakOpenID( @@ -232,7 +233,7 @@ def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin) "protocol": "openid-connect", "secret": secret, "clientAuthenticatorType": "client-secret", - } + }, ) # Create user username = str(uuid.uuid4()) @@ -247,7 +248,7 @@ def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin) "emailVerified": True, "requiredActions": [], "credentials": [{"type": "password", "value": password, "temporary": False}], - } + }, ) yield ( @@ -295,7 +296,7 @@ def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: Keycloak "clientAuthenticatorType": "client-secret", "authorizationServicesEnabled": True, "serviceAccountsEnabled": True, - } + }, ) admin.create_client_authz_role_based_policy( client_id=client_id, @@ -317,7 +318,7 @@ def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: Keycloak "lastName": "last", "requiredActions": [], "credentials": [{"type": "password", "value": password, "temporary": False}], - } + }, ) yield ( @@ -364,7 +365,7 @@ def oid_with_credentials_device(env: KeycloakTestEnv, realm: str, admin: Keycloa "secret": secret, "clientAuthenticatorType": "client-secret", "attributes": {"oauth2.device.authorization.grant.enabled": True}, - } + }, ) # Create user username = str(uuid.uuid4()) @@ -379,7 +380,7 @@ def oid_with_credentials_device(env: KeycloakTestEnv, realm: str, admin: Keycloa "emailVerified": True, "requiredActions": [], "credentials": [{"type": "password", "value": password, "temporary": False}], - } + }, ) yield ( @@ -489,7 +490,10 @@ def client_role(admin: KeycloakAdmin, realm: str, client: str) -> Generator[str, @pytest.fixture def composite_client_role( - admin: KeycloakAdmin, realm: str, client: str, client_role: str + admin: KeycloakAdmin, + realm: str, + client: str, + client_role: str, ) -> Generator[str, None, None]: """Fixture for a new random composite client role. @@ -526,7 +530,9 @@ def selfsigned_cert(): # Generate our key if key is None: key = rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() + public_exponent=65537, + key_size=2048, + backend=default_backend(), ) name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, hostname)]) @@ -585,7 +591,7 @@ def oid_connection_with_authz(oid_with_credentials_authz: Tuple[KeycloakOpenID, client_secret_key=oid.client_secret_key, timeout=60, ) - yield connection + return connection @pytest.fixture @@ -599,4 +605,4 @@ def uma(oid_connection_with_authz: KeycloakOpenIDConnection): """ connection = oid_connection_with_authz # Return UMA - yield KeycloakUMA(connection=connection) + return KeycloakUMA(connection=connection) diff --git a/tests/test_connection.py b/tests/test_connection.py index 448fcee..4ef19af 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -11,7 +11,7 @@ from keycloak.exceptions import KeycloakConnectionError def test_connection_proxy(): """Test proxies of connection manager.""" cm = ConnectionManager( - base_url="http://test.test", proxies={"http://test.test": "http://localhost:8080"} + base_url="http://test.test", proxies={"http://test.test": "http://localhost:8080"}, ) assert cm._s.proxies == {"http://test.test": "http://localhost:8080"} diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 72e7161..f39c479 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -16,5 +16,5 @@ def test_raise_error_from_response_from_dict(): with pytest.raises(KeycloakOperationError): raise_error_from_response( - response=response, error=dict(), expected_codes=[200], skip_exists=False + response=response, error=dict(), expected_codes=[200], skip_exists=False, ) diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 11fc2f3..7b4297b 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -55,9 +55,9 @@ def test_keycloak_admin_init(env): username=env.KEYCLOAK_ADMIN, password=env.KEYCLOAK_ADMIN_PASSWORD, ) - assert ( - admin.connection.server_url == f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}" - ), admin.connection.server_url + assert admin.connection.server_url == f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", ( + admin.connection.server_url + ) assert admin.connection.realm_name == "master", admin.connection.realm_name assert isinstance(admin.connection, ConnectionManager), type(admin.connection) assert admin.connection.client_id == "admin-cli", admin.connection.client_id @@ -110,7 +110,7 @@ def test_keycloak_admin_init(env): "enabled": True, "implicitFlowEnabled": False, "publicClient": False, - } + }, ) secret = admin.generate_client_secrets(client_id=admin.get_client_id("authz-client")) adminAuth = KeycloakAdmin( @@ -156,7 +156,7 @@ def test_realms(admin: KeycloakAdmin): # Get realms realms = admin.get_realms() assert len(realms) == 1, realms - assert "master" == realms[0]["realm"] + assert realms[0]["realm"] == "master" # Create a test realm res = admin.create_realm(payload={"realm": "test"}) @@ -192,7 +192,7 @@ def test_realms(admin: KeycloakAdmin): # Update wrong payload with pytest.raises(KeycloakPutError) as err: admin.update_realm(realm_name="test", payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Check that get realms returns both realms realms = admin.get_realms() @@ -251,7 +251,7 @@ def test_import_export_realms(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPostError) as err: admin.import_realm(payload=dict()) assert err.match( - '500: b\'{"error":"unknown_error"}\'|400: b\'{"errorMessage":"Realm name cannot be empty"}\'' # noqa: E501 + '500: b\'{"error":"unknown_error"}\'|400: b\'{"errorMessage":"Realm name cannot be empty"}\'', # noqa: E501 ) @@ -327,7 +327,7 @@ def test_users(admin: KeycloakAdmin, realm: str): # Test create the same user, exists_ok true user_id_2 = admin.create_user( - payload={"username": "test", "email": "test@test.test"}, exist_ok=True + payload={"username": "test", "email": "test@test.test"}, exist_ok=True, ) assert user_id == user_id_2 @@ -345,7 +345,7 @@ def test_users(admin: KeycloakAdmin, realm: str): # Test update user fail with pytest.raises(KeycloakPutError) as err: admin.update_user(user_id=user_id, payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Test disable user res = admin.disable_user(user_id=user_id) @@ -421,13 +421,13 @@ def test_enable_disable_all_users(admin: KeycloakAdmin, realm: str): admin.change_current_realm(realm) user_id_1 = admin.create_user( - payload={"username": "test", "email": "test@test.test", "enabled": True} + payload={"username": "test", "email": "test@test.test", "enabled": True}, ) user_id_2 = admin.create_user( - payload={"username": "test2", "email": "test2@test.test", "enabled": True} + payload={"username": "test2", "email": "test2@test.test", "enabled": True}, ) user_id_3 = admin.create_user( - payload={"username": "test3", "email": "test3@test.test", "enabled": True} + payload={"username": "test3", "email": "test3@test.test", "enabled": True}, ) assert admin.get_user(user_id_1)["enabled"] @@ -515,7 +515,7 @@ def test_user_groups_pagination(admin: KeycloakAdmin, realm: str): admin.change_current_realm(realm) user_id = admin.create_user( - payload={"username": "username_1", "email": "username_1@test.test"} + payload={"username": "username_1", "email": "username_1@test.test"}, ) for ind in range(admin.PAGE_SIZE + 50): @@ -546,8 +546,8 @@ def test_idps(admin: KeycloakAdmin, realm: str): # Create IDP res = admin.create_idp( payload=dict( - providerId="github", alias="github", config=dict(clientId="test", clientSecret="test") - ) + providerId="github", alias="github", config=dict(clientId="test", clientSecret="test"), + ), ) assert res == b"", res @@ -559,14 +559,14 @@ def test_idps(admin: KeycloakAdmin, realm: str): # Test listing idps = admin.get_idps() assert len(idps) == 1 - assert "github" == idps[0]["alias"] + assert idps[0]["alias"] == "github" # Test get idp idp = admin.get_idp("github") - assert "github" == idp["alias"] + assert idp["alias"] == "github" assert idp.get("config") - assert "test" == idp["config"]["clientId"] - assert "**********" == idp["config"]["clientSecret"] + assert idp["config"]["clientId"] == "test" + assert idp["config"]["clientSecret"] == "**********" # Test get idp fail with pytest.raises(KeycloakGetError) as err: @@ -666,11 +666,11 @@ def test_social_logins(admin: KeycloakAdmin, user: str): :type user: str """ res = admin.add_user_social_login( - user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test" + user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test", ) assert res == dict(), res admin.add_user_social_login( - user_id=user, provider_id="github", provider_userid="test", provider_username="test" + user_id=user, provider_id="github", provider_userid="test", provider_username="test", ) assert res == dict(), res @@ -727,8 +727,7 @@ def test_server_info(admin: KeycloakAdmin): "passwordPolicies", "enums", "cryptoInfo", - "features", - } + }, ), info.keys() @@ -767,7 +766,7 @@ def test_groups(admin: KeycloakAdmin, user: str): # Test skip exists OK subgroup_id_1_eq = admin.create_group( - payload={"name": "subgroup-1"}, parent=group_id, skip_exists=True + payload={"name": "subgroup-1"}, parent=group_id, skip_exists=True, ) assert subgroup_id_1_eq is None @@ -826,7 +825,7 @@ def test_groups(admin: KeycloakAdmin, user: str): # Test that query params are passed if os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] == "latest" or Version( - os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] + os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"], ) >= Version("23"): res = admin.get_group_children(group_id=group_id, query={"max": 1}) assert len(res) == 1 @@ -937,7 +936,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): "${client_broker}", "${client_account}", "${client_realm-management}", - ] + ], ), clients # Test create client @@ -949,7 +948,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): assert err.match('409: b\'{"errorMessage":"Client test-client already exists"}\''), err client_id_2 = admin.create_client( - payload={"name": "test-client", "clientId": "test-client"}, skip_exists=True + payload={"name": "test-client", "clientId": "test-client"}, skip_exists=True, ) assert client_id == client_id_2, client_id_2 @@ -1024,7 +1023,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): "clientId": "authz-client", "authorizationServicesEnabled": True, "serviceAccountsEnabled": True, - } + }, ) res = admin.get_client_authz_settings(client_id=auth_client_id) assert res["allowRemoteResourceManagement"] @@ -1045,7 +1044,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): assert err.match(HTTP_404_REGEX) res = admin.create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"} + client_id=auth_client_id, payload={"name": "test-resource"}, ) assert res["name"] == "test-resource", res test_resource_id = res["_id"] @@ -1056,11 +1055,11 @@ def test_clients(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPostError) as err: admin.create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"} + client_id=auth_client_id, payload={"name": "test-resource"}, ) assert err.match('409: b\'{"error":"invalid_request"') assert admin.create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"}, skip_exists=True + client_id=auth_client_id, payload={"name": "test-resource"}, skip_exists=True, ) == {"msg": "Already exists"} res = admin.get_client_authz_resources(client_id=auth_client_id) @@ -1068,7 +1067,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): assert {x["name"] for x in res} == {"Default Resource", "test-resource"} res = admin.create_client_authz_resource( - client_id=auth_client_id, payload={"name": "temp-resource"} + client_id=auth_client_id, payload={"name": "temp-resource"}, ) assert res["name"] == "temp-resource", res temp_resource_id: str = res["_id"] @@ -1215,7 +1214,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): # Test getting associated policies for a permission associated_policies = admin.get_client_authz_permission_associated_policies( - client_id=auth_client_id, policy_id=resource_based_permission_id + client_id=auth_client_id, policy_id=resource_based_permission_id, ) assert len(associated_policies) == 1 assert associated_policies[0]["name"].startswith(role_based_policy_name) @@ -1229,17 +1228,17 @@ def test_clients(admin: KeycloakAdmin, realm: str): assert err.match(HTTP_404_REGEX) res = admin.create_client_authz_scopes( - client_id=auth_client_id, payload={"name": "test-authz-scope"} + client_id=auth_client_id, payload={"name": "test-authz-scope"}, ) assert res["name"] == "test-authz-scope", res with pytest.raises(KeycloakPostError) as err: admin.create_client_authz_scopes( - client_id="invalid_client_id", payload={"name": "test-authz-scope"} + client_id="invalid_client_id", payload={"name": "test-authz-scope"}, ) assert err.match('404: b\'{"error":"Could not find client".*}\'') assert admin.create_client_authz_scopes( - client_id=auth_client_id, payload={"name": "test-authz-scope"} + client_id=auth_client_id, payload={"name": "test-authz-scope"}, ) res = admin.get_client_authz_scopes(client_id=auth_client_id) @@ -1254,7 +1253,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): admin.get_client_service_account_user(client_id=client_id) assert ('b\'{"error":"Service account not enabled for the client' in str(err)) or err.match( - UNKOWN_ERROR_REGEX + UNKOWN_ERROR_REGEX, ) # Test delete client @@ -1276,14 +1275,14 @@ def test_clients(admin: KeycloakAdmin, realm: str): "clientId": "test-confidential", "secret": "test-secret", "clientAuthenticatorType": "client-secret", - } + }, ) with pytest.raises(KeycloakGetError) as err: admin.get_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') secrets = admin.get_client_secrets( - client_id=admin.get_client_id(client_id="test-confidential") + client_id=admin.get_client_id(client_id="test-confidential"), ) assert secrets == {"type": "secret", "value": "test-secret"} @@ -1292,7 +1291,7 @@ def test_clients(admin: KeycloakAdmin, realm: str): assert err.match('404: b\'{"error":"Could not find client".*}\'') res = admin.generate_client_secrets( - client_id=admin.get_client_id(client_id="test-confidential") + client_id=admin.get_client_id(client_id="test-confidential"), ) assert res assert ( @@ -1347,12 +1346,12 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): # Test update realm role res = admin.update_realm_role( - role_name="test-realm-role", payload={"name": "test-realm-role-update"} + role_name="test-realm-role", payload={"name": "test-realm-role-update"}, ) assert res == dict(), res with pytest.raises(KeycloakPutError) as err: admin.update_realm_role( - role_name="test-realm-role", payload={"name": "test-realm-role-update"} + role_name="test-realm-role", payload={"name": "test-realm-role-update"}, ) assert err.match(COULD_NOT_FIND_ROLE_REGEX) @@ -1385,7 +1384,7 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): admin.delete_realm_roles_of_user(user_id=user_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.delete_realm_roles_of_user( - user_id=user_id, roles=[admin.get_realm_role(role_name="offline_access")] + user_id=user_id, roles=[admin.get_realm_role(role_name="offline_access")], ) assert res == dict(), res assert admin.get_realm_role_members(role_name="offline_access") == list() @@ -1422,7 +1421,7 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): admin.delete_group_realm_roles(group_id=group_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX) res = admin.delete_group_realm_roles( - group_id=group_id, roles=[admin.get_realm_role(role_name="offline_access")] + group_id=group_id, roles=[admin.get_realm_role(role_name="offline_access")], ) assert res == dict(), res roles = admin.get_group_realm_roles(group_id=group_id) @@ -1435,7 +1434,7 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): admin.add_composite_realm_roles_to_role(role_name=composite_role, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.add_composite_realm_roles_to_role( - role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")] + role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")], ) assert res == dict(), res @@ -1459,7 +1458,7 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): admin.remove_composite_realm_roles_to_role(role_name=composite_role, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.remove_composite_realm_roles_to_role( - role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")] + role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")], ) assert res == dict(), res @@ -1521,12 +1520,12 @@ def test_role_attributes( attribute_role = "test-realm-role-w-attr" test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]} role_id = admin.create_realm_role( - payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True + payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True, ) assert role_id, role_id cli_role_id = admin.create_client_role( - client, payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True + client, payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True, ) assert cli_role_id, cli_role_id @@ -1577,7 +1576,7 @@ def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): # Test realm role client assignment client_id = admin.create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) with pytest.raises(KeycloakPostError) as err: admin.assign_realm_roles_to_client_scope(client_id=client_id, roles=["bad"]) @@ -1603,7 +1602,7 @@ def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): admin.delete_realm_roles_of_client_scope(client_id=client_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.delete_realm_roles_of_client_scope( - client_id=client_id, roles=[admin.get_realm_role(role_name="offline_access")] + client_id=client_id, roles=[admin.get_realm_role(role_name="offline_access")], ) assert res == dict(), res roles = admin.get_realm_roles_of_client_scope(client_id=client_id) @@ -1611,7 +1610,7 @@ def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): assert "test-realm-role" in [x["name"] for x in roles] res = admin.delete_realm_roles_of_client_scope( - client_id=client_id, roles=[admin.get_realm_role(role_name="test-realm-role")] + client_id=client_id, roles=[admin.get_realm_role(role_name="test-realm-role")], ) assert res == dict(), res roles = admin.get_realm_roles_of_client_scope(client_id=client_id) @@ -1631,7 +1630,7 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str admin.change_current_realm(realm) client_id = admin.create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client roles @@ -1640,14 +1639,14 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str # create client role for test client_role_id = admin.create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) assert client_role_id, client_role_id # Test client role assignment to other client with pytest.raises(KeycloakPostError) as err: admin.assign_client_roles_to_client_scope( - client_id=client_id, client_roles_owner_id=client, roles=["bad"] + client_id=client_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.assign_client_roles_to_client_scope( @@ -1658,7 +1657,7 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str assert res == dict(), res roles = admin.get_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client + client_id=client_id, client_roles_owner_id=client, ) assert len(roles) == 1 client_role_names = [x["name"] for x in roles] @@ -1667,7 +1666,7 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str # Test remove realm role of client with pytest.raises(KeycloakDeleteError) as err: admin.delete_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client, roles=["bad"] + client_id=client_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.delete_client_roles_of_client_scope( @@ -1677,7 +1676,7 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str ) assert res == dict(), res roles = admin.get_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client + client_id=client_id, client_roles_owner_id=client, ) assert len(roles) == 0 @@ -1708,7 +1707,7 @@ def test_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: str, cli # Test get client roles client_specific_roles = admin.get_client_specific_roles_of_client_scope( - client_scope_id, client + client_scope_id, client, ) assert len(client_specific_roles) == 0, client_specific_roles all_roles = admin.get_all_roles_of_client_scope(client_scope_id) @@ -1716,14 +1715,14 @@ def test_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: str, cli # create client role for test client_role_name = admin.create_client_role( - client_role_id=client, payload={"name": CLIENT_ROLE_NAME}, skip_exists=True + client_role_id=client, payload={"name": CLIENT_ROLE_NAME}, skip_exists=True, ) assert client_role_name, client_role_name # Test client role assignment to other client with pytest.raises(KeycloakPostError) as err: admin.add_client_specific_roles_to_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"] + client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err @@ -1736,7 +1735,7 @@ def test_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: str, cli # Test when getting roles for the specific owner client client_specific_roles = admin.get_client_specific_roles_of_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client + client_scope_id=client_scope_id, client_roles_owner_id=client, ) assert len(client_specific_roles) == 1 client_role_names = [x["name"] for x in client_specific_roles] @@ -1754,7 +1753,7 @@ def test_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: str, cli # Test remove realm role of client with pytest.raises(KeycloakDeleteError) as err: admin.remove_client_specific_roles_of_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"] + client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err @@ -1782,7 +1781,7 @@ def test_client_default_client_scopes(admin: KeycloakAdmin, realm: str, client: admin.change_current_realm(realm) client_id = admin.create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client default scopes # keycloak default roles: web-origins, acr, profile, roles, email @@ -1804,7 +1803,7 @@ def test_client_default_client_scopes(admin: KeycloakAdmin, realm: str, client: "clientScopeId": new_client_scope_id, } admin.add_client_default_client_scope( - client_id, new_client_scope_id, new_default_client_scope_data + client_id, new_client_scope_id, new_default_client_scope_data, ) default_client_scopes = admin.get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [6, 7], default_client_scopes @@ -1828,7 +1827,7 @@ def test_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, client: admin.change_current_realm(realm) client_id = admin.create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client optional scopes # keycloak optional roles: microprofile-jwt, offline_access, address, --> for versions < 26.0.0 @@ -1851,7 +1850,7 @@ def test_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, client: "clientScopeId": new_client_scope_id, } admin.add_client_optional_client_scope( - client_id, new_client_scope_id, new_optional_client_scope_data + client_id, new_client_scope_id, new_optional_client_scope_data, ) optional_client_scopes = admin.get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [5, 6], optional_client_scopes @@ -1879,13 +1878,13 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test create client role client_role_id = admin.create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) with pytest.raises(KeycloakPostError) as err: admin.create_client_role(client_role_id=client, payload={"name": "client-role-test"}) assert err.match('409: b\'{"errorMessage":"Role with name client-role-test already exists"}\'') client_role_id_2 = admin.create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) assert client_role_id == client_role_id_2 @@ -1905,7 +1904,7 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test update client role res = admin.update_client_role( - client_id=client, role_name="client-role-test", payload={"name": "client-role-test-update"} + client_id=client, role_name="client-role-test", payload={"name": "client-role-test-update"}, ) assert res == dict() with pytest.raises(KeycloakPutError) as err: @@ -2008,7 +2007,7 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test composite client roles with pytest.raises(KeycloakPostError) as err: admin.add_composite_client_roles_to_role( - client_role_id=client, role_name="client-role-test-update", roles=["bad"] + client_role_id=client, role_name="client-role-test-update", roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.add_composite_client_roles_to_role( @@ -2024,7 +2023,7 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test removal of composite client roles with pytest.raises(KeycloakDeleteError) as err: admin.remove_composite_client_roles_from_role( - client_role_id=client, role_name="client-role-test-update", roles=["bad"] + client_role_id=client, role_name="client-role-test-update", roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = admin.remove_composite_client_roles_from_role( @@ -2046,7 +2045,7 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test of roles by id - Get role admin.create_client_role( - client_role_id=client, payload={"name": "client-role-by-id-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-by-id-test"}, skip_exists=True, ) role = admin.get_client_role(client_id=client, role_name="client-role-by-id-test") res = admin.get_role_by_id(role_id=role["id"]) @@ -2057,12 +2056,12 @@ def test_client_roles(admin: KeycloakAdmin, client: str): # Test of roles by id - Update role res = admin.update_role_by_id( - role_id=role["id"], payload={"name": "client-role-by-id-test-update"} + role_id=role["id"], payload={"name": "client-role-by-id-test-update"}, ) assert res == dict() with pytest.raises(KeycloakPutError) as err: res = admin.update_role_by_id( - role_id="bad", payload={"name": "client-role-by-id-test-update"} + role_id="bad", payload={"name": "client-role-by-id-test-update"}, ) assert err.match(COULD_NOT_FIND_ROLE_WITH_ID_REGEX) @@ -2088,10 +2087,10 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Create test clients source_client_id = admin.create_client( - payload={"name": "Source Client", "clientId": "source-client"} + payload={"name": "Source Client", "clientId": "source-client"}, ) target_client_id = admin.create_client( - payload={"name": "Target Client", "clientId": "target-client"} + payload={"name": "Target Client", "clientId": "target-client"}, ) for c in admin.get_clients(): if c["clientId"] == "realm-management": @@ -2102,15 +2101,15 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Enable permissions on the Superset client admin.update_client_management_permissions( - payload={"enabled": True}, client_id=target_client_id + payload={"enabled": True}, client_id=target_client_id, ) # Fetch various IDs and strings needed when creating the permission token_exchange_permission_id = admin.get_client_management_permissions( - client_id=target_client_id + client_id=target_client_id, )["scopePermissions"]["token-exchange"] scopes = admin.get_client_authz_policy_scopes( - client_id=realm_management_id, policy_id=token_exchange_permission_id + client_id=realm_management_id, policy_id=token_exchange_permission_id, ) for s in scopes: @@ -2121,7 +2120,7 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): raise AssertionError("Missing token-exchange scope") resources = admin.get_client_authz_policy_resources( - client_id=realm_management_id, policy_id=token_exchange_permission_id + client_id=realm_management_id, policy_id=token_exchange_permission_id, ) for r in resources: if r["name"] == f"client.resource.{target_client_id}": @@ -2152,7 +2151,7 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Update permissions on the target client to reference this policy permission_name = admin.get_client_authz_scope_permission( - client_id=realm_management_id, scope_id=token_exchange_permission_id + client_id=realm_management_id, scope_id=token_exchange_permission_id, )["name"] admin.update_client_authz_scope_permission( payload={ @@ -2184,7 +2183,7 @@ def test_enable_token_exchange(admin: KeycloakAdmin, realm: str): client_id=realm_management_id, ) permission_name = admin.get_client_authz_scope_permission( - client_id=realm_management_id, scope_id=token_exchange_permission_id + client_id=realm_management_id, scope_id=token_exchange_permission_id, )["name"] assert permission_name.startswith("token-exchange.permission.client.") with pytest.raises(KeycloakPostError) as err: @@ -2240,7 +2239,7 @@ def test_get_client_installation_provider(admin: KeycloakAdmin, client: str): assert err.match('404: b\'{"error":"Unknown Provider".*}\'') installation = admin.get_client_installation_provider( - client_id=client, provider_id="keycloak-oidc-keycloak-json" + client_id=client, provider_id="keycloak-oidc-keycloak-json", ) assert set(installation.keys()) == { "auth-server-url", @@ -2275,7 +2274,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str): "direct grant", "first broker login", "clients", - } + }, ) assert set(res[0].keys()) == { "alias", @@ -2296,7 +2295,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str): "first broker login", "clients", "http challenge", - } + }, ) with pytest.raises(KeycloakGetError) as err: @@ -2317,14 +2316,14 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str): # Test create res = admin.create_authentication_flow( - payload={"alias": "test-create", "providerId": "basic-flow"} + payload={"alias": "test-create", "providerId": "basic-flow"}, ) assert res == b"" with pytest.raises(KeycloakPostError) as err: admin.create_authentication_flow(payload={"alias": "test-create", "builtIn": False}) assert err.match('409: b\'{"errorMessage":"Flow test-create already exists"}\'') assert admin.create_authentication_flow( - payload={"alias": "test-create"}, skip_exists=True + payload={"alias": "test-create"}, skip_exists=True, ) == {"msg": "Already exists"} # Test flow executions @@ -2351,7 +2350,7 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str): "priority", "required", "requirement", - } + }, ), res.keys() with pytest.raises(KeycloakGetError) as err: admin.get_authentication_flow_execution(execution_id="bad") @@ -2362,16 +2361,16 @@ def test_auth_flows(admin: KeycloakAdmin, realm: str): assert err.match('400: b\'{"error":"It is illegal to add execution to a built in flow".*}\'') res = admin.create_authentication_flow_execution( - payload={"provider": "auth-cookie"}, flow_alias="test-create" + payload={"provider": "auth-cookie"}, flow_alias="test-create", ) assert res == b"" assert len(admin.get_authentication_flow_executions(flow_alias="test-create")) == 1 with pytest.raises(KeycloakPutError) as err: admin.update_authentication_flow_executions( - payload={"required": "yes"}, flow_alias="test-create" + payload={"required": "yes"}, flow_alias="test-create", ) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") payload = admin.get_authentication_flow_executions(flow_alias="test-create")[0] payload["displayName"] = "test" res = admin.update_authentication_flow_executions(payload=payload, flow_alias="test-create") @@ -2510,16 +2509,16 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str): # Test create client scope res = admin.create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True, ) assert res res2 = admin.create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True, ) assert res == res2 with pytest.raises(KeycloakPostError) as err: admin.create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=False + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=False, ) assert err.match('409: b\'{"errorMessage":"Client Scope test-scope already exists"}\'') @@ -2529,7 +2528,7 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str): assert err.match(NO_CLIENT_SCOPE_REGEX) res_update = admin.update_client_scope( - client_scope_id=res, payload={"name": "test-scope-update"} + client_scope_id=res, payload={"name": "test-scope-update"}, ) assert res_update == dict() assert admin.get_client_scope(client_scope_id=res)["name"] == "test-scope-update" @@ -2558,12 +2557,12 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str): test_mapper = admin.get_mappers_from_client_scope(client_scope_id=res)[0] with pytest.raises(KeycloakPutError) as err: admin.update_mapper_in_client_scope( - client_scope_id="does-not-exist", protocol_mapper_id=test_mapper["id"], payload=dict() + client_scope_id="does-not-exist", protocol_mapper_id=test_mapper["id"], payload=dict(), ) assert err.match(NO_CLIENT_SCOPE_REGEX) test_mapper["config"]["user.attribute"] = "test" res_update = admin.update_mapper_in_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"], payload=test_mapper + client_scope_id=res, protocol_mapper_id=test_mapper["id"], payload=test_mapper, ) assert res_update == dict() assert ( @@ -2573,12 +2572,12 @@ def test_client_scopes(admin: KeycloakAdmin, realm: str): # Test delete mapper res_del = admin.delete_mapper_from_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"] + client_scope_id=res, protocol_mapper_id=test_mapper["id"], ) assert res_del == dict() with pytest.raises(KeycloakDeleteError) as err: admin.delete_mapper_from_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"] + client_scope_id=res, protocol_mapper_id=test_mapper["id"], ) assert err.match('404: b\'{"error":"Model not found".*}\'') @@ -2654,7 +2653,7 @@ def test_components(admin: KeycloakAdmin, realm: str): # Test create component with pytest.raises(KeycloakPostError) as err: admin.create_component(payload={"bad": "dict"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") res = admin.create_component( payload={ @@ -2663,7 +2662,7 @@ def test_components(admin: KeycloakAdmin, realm: str): "providerType": "org.keycloak.services.clientregistration." + "policy.ClientRegistrationPolicy", "config": {"max-clients": ["1000"]}, - } + }, ) assert res assert admin.get_component(component_id=res)["name"] == "Test Component" @@ -2697,7 +2696,7 @@ def test_keys(admin: KeycloakAdmin, realm: str): """ admin.change_current_realm(realm) assert set(admin.get_keys()["active"].keys()) == {"AES", "HS256", "RS256", "RSA-OAEP"} or set( - admin.get_keys()["active"].keys() + admin.get_keys()["active"].keys(), ) == {"RSA-OAEP", "RS256", "HS512", "AES"} assert {k["algorithm"] for k in admin.get_keys()["keys"]} == { "HS256", @@ -2743,7 +2742,7 @@ def test_user_events(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPutError) as err: admin.set_events(payload={"bad": "conf"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") res = admin.set_events(payload={"adminEventsDetailsEnabled": True, "adminEventsEnabled": True}) assert res == dict() @@ -2787,7 +2786,7 @@ def test_auto_refresh(admin_frozen: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPostError) as err: admin.get_realm(realm_name="test-refresh") assert err.match( - '400: b\'{"error":"invalid_grant","error_description":"Invalid refresh token"}\'' + '400: b\'{"error":"invalid_grant","error_description":"Invalid refresh token"}\'', ) admin.connection.get_token() @@ -2875,7 +2874,7 @@ 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 + admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str, ): """Test get composite client roles of group. @@ -2898,7 +2897,7 @@ 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 + admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str, ): """Test get children of composite client role. @@ -2941,7 +2940,7 @@ def test_upload_certificate(admin: KeycloakAdmin, realm: str, client: str, selfs def test_get_bruteforce_status_for_user( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -2978,7 +2977,7 @@ def test_get_bruteforce_status_for_user( def test_clear_bruteforce_attempts_for_user( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -3018,7 +3017,7 @@ def test_clear_bruteforce_attempts_for_user( def test_clear_bruteforce_attempts_for_all_users( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -3170,7 +3169,7 @@ def test_clear_user_cache(realm: str, admin: KeycloakAdmin) -> None: def test_initial_access_token( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str] + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], ) -> None: """Test initial access token and client creation. @@ -3233,7 +3232,7 @@ async def test_a_realms(admin: KeycloakAdmin): # Get realms realms = await admin.a_get_realms() assert len(realms) == 1, realms - assert "master" == realms[0]["realm"] + assert realms[0]["realm"] == "master" # Create a test realm res = await admin.a_create_realm(payload={"realm": "test"}) @@ -3269,7 +3268,7 @@ async def test_a_realms(admin: KeycloakAdmin): # Update wrong payload with pytest.raises(KeycloakPutError) as err: await admin.a_update_realm(realm_name="test", payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Check that get realms returns both realms realms = await admin.a_get_realms() @@ -3330,7 +3329,7 @@ async def test_a_import_export_realms(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPostError) as err: await admin.a_import_realm(payload=dict()) assert err.match( - '500: b\'{"error":"unknown_error"}\'|400: b\'{"errorMessage":"Realm name cannot be empty"}\'' # noqa: E501 + '500: b\'{"error":"unknown_error"}\'|400: b\'{"errorMessage":"Realm name cannot be empty"}\'', # noqa: E501 ) @@ -3408,7 +3407,7 @@ async def test_a_users(admin: KeycloakAdmin, realm: str): # Test create the same user, exists_ok true user_id_2 = await admin.a_create_user( - payload={"username": "test", "email": "test@test.test"}, exist_ok=True + payload={"username": "test", "email": "test@test.test"}, exist_ok=True, ) assert user_id == user_id_2 @@ -3426,7 +3425,7 @@ async def test_a_users(admin: KeycloakAdmin, realm: str): # Test update user fail with pytest.raises(KeycloakPutError) as err: await admin.a_update_user(user_id=user_id, payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Test disable user res = await admin.a_disable_user(user_id=user_id) @@ -3503,13 +3502,13 @@ async def test_a_enable_disable_all_users(admin: KeycloakAdmin, realm: str): admin.change_current_realm(realm) user_id_1 = await admin.a_create_user( - payload={"username": "test", "email": "test@test.test", "enabled": True} + payload={"username": "test", "email": "test@test.test", "enabled": True}, ) user_id_2 = await admin.a_create_user( - payload={"username": "test2", "email": "test2@test.test", "enabled": True} + payload={"username": "test2", "email": "test2@test.test", "enabled": True}, ) user_id_3 = await admin.a_create_user( - payload={"username": "test3", "email": "test3@test.test", "enabled": True} + payload={"username": "test3", "email": "test3@test.test", "enabled": True}, ) assert (await admin.a_get_user(user_id_1))["enabled"] @@ -3542,7 +3541,7 @@ async def test_a_users_roles(admin: KeycloakAdmin, realm: str): # Test all level user roles client_id = await admin.a_create_client( - payload={"name": "test-client", "clientId": "test-client"} + payload={"name": "test-client", "clientId": "test-client"}, ) await admin.a_create_client_role(client_role_id=client_id, payload={"name": "test-role"}) await admin.a_assign_client_role( @@ -3602,7 +3601,7 @@ async def test_a_user_groups_pagination(admin: KeycloakAdmin, realm: str): await admin.a_change_current_realm(realm) user_id = await admin.a_create_user( - payload={"username": "username_1", "email": "username_1@test.test"} + payload={"username": "username_1", "email": "username_1@test.test"}, ) for ind in range(admin.PAGE_SIZE + 50): @@ -3614,12 +3613,12 @@ async def test_a_user_groups_pagination(admin: KeycloakAdmin, realm: str): assert len(groups) == admin.PAGE_SIZE + 50, len(groups) groups = await admin.a_get_user_groups( - user_id=user_id, query={"first": 100, "max": -1, "search": ""} + user_id=user_id, query={"first": 100, "max": -1, "search": ""}, ) assert len(groups) == 50, len(groups) groups = await admin.a_get_user_groups( - user_id=user_id, query={"max": 20, "first": -1, "search": ""} + user_id=user_id, query={"max": 20, "first": -1, "search": ""}, ) assert len(groups) == 20, len(groups) @@ -3638,8 +3637,8 @@ async def test_a_idps(admin: KeycloakAdmin, realm: str): # Create IDP res = await admin.a_create_idp( payload=dict( - providerId="github", alias="github", config=dict(clientId="test", clientSecret="test") - ) + providerId="github", alias="github", config=dict(clientId="test", clientSecret="test"), + ), ) assert res == b"", res @@ -3651,14 +3650,14 @@ async def test_a_idps(admin: KeycloakAdmin, realm: str): # Test listing idps = await admin.a_get_idps() assert len(idps) == 1 - assert "github" == idps[0]["alias"] + assert idps[0]["alias"] == "github" # Test get idp idp = await admin.a_get_idp("github") - assert "github" == idp["alias"] + assert idp["alias"] == "github" assert idp.get("config") - assert "test" == idp["config"]["clientId"] - assert "**********" == idp["config"]["clientSecret"] + assert idp["config"]["clientId"] == "test" + assert idp["config"]["clientSecret"] == "**********" # Test get idp fail with pytest.raises(KeycloakGetError) as err: @@ -3760,11 +3759,11 @@ async def test_a_social_logins(admin: KeycloakAdmin, user: str): :type user: str """ res = await admin.a_add_user_social_login( - user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test" + user_id=user, provider_id="gitlab", provider_userid="test", provider_username="test", ) assert res == dict(), res await admin.a_add_user_social_login( - user_id=user, provider_id="github", provider_userid="test", provider_username="test" + user_id=user, provider_id="github", provider_userid="test", provider_username="test", ) assert res == dict(), res @@ -3822,8 +3821,7 @@ async def test_a_server_info(admin: KeycloakAdmin): "passwordPolicies", "enums", "cryptoInfo", - "features", - } + }, ), info.keys() @@ -3863,7 +3861,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str): # Test skip exists OK subgroup_id_1_eq = await admin.a_create_group( - payload={"name": "subgroup-1"}, parent=group_id, skip_exists=True + payload={"name": "subgroup-1"}, parent=group_id, skip_exists=True, ) assert subgroup_id_1_eq is None @@ -3894,14 +3892,14 @@ async def test_a_groups(admin: KeycloakAdmin, user: str): # Create 1 more subgroup subsubgroup_id_1 = await admin.a_create_group( - payload={"name": "subsubgroup-1"}, parent=subgroup_id_2 + payload={"name": "subsubgroup-1"}, parent=subgroup_id_2, ) main_group = await admin.a_get_group(group_id=group_id) # Test nested searches subgroup_2 = await admin.a_get_group(group_id=subgroup_id_2) res = await admin.a_get_subgroups( - group=subgroup_2, path="/main-group/subgroup-2/subsubgroup-1" + group=subgroup_2, path="/main-group/subgroup-2/subsubgroup-1", ) assert res is not None, res assert res["id"] == subsubgroup_id_1 @@ -3926,7 +3924,7 @@ async def test_a_groups(admin: KeycloakAdmin, user: str): # Test that query params are passed if os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] == "latest" or Version( - os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] + os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"], ) >= Version("23"): res = await admin.a_get_group_children(group_id=group_id, query={"max": 1}) assert len(res) == 1 @@ -4038,12 +4036,12 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): "${client_broker}", "${client_account}", "${client_realm-management}", - ] + ], ), clients # Test create client client_id = await admin.a_create_client( - payload={"name": "test-client", "clientId": "test-client"} + payload={"name": "test-client", "clientId": "test-client"}, ) assert client_id, client_id @@ -4052,7 +4050,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): assert err.match('409: b\'{"errorMessage":"Client test-client already exists"}\''), err client_id_2 = await admin.a_create_client( - payload={"name": "test-client", "clientId": "test-client"}, skip_exists=True + payload={"name": "test-client", "clientId": "test-client"}, skip_exists=True, ) assert client_id == client_id_2, client_id_2 @@ -4077,7 +4075,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPutError) as err: await admin.a_update_client( - client_id="does-not-exist", payload={"name": "test-client-change"} + client_id="does-not-exist", payload={"name": "test-client-change"}, ) assert err.match('404: b\'{"error":"Could not find client".*}\'') @@ -4103,12 +4101,12 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): mapper = (await admin.a_get_mappers_from_client(client_id=client_id))[0] with pytest.raises(KeycloakPutError) as err: await admin.a_update_client_mapper( - client_id=client_id, mapper_id="does-not-exist", payload=dict() + client_id=client_id, mapper_id="does-not-exist", payload=dict(), ) assert err.match('404: b\'{"error":"Model not found".*}\'') mapper["config"]["user.attribute"] = "test" res = await admin.a_update_client_mapper( - client_id=client_id, mapper_id=mapper["id"], payload=mapper + client_id=client_id, mapper_id=mapper["id"], payload=mapper, ) assert res == dict() @@ -4133,7 +4131,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): "clientId": "authz-client", "authorizationServicesEnabled": True, "serviceAccountsEnabled": True, - } + }, ) res = await admin.a_get_client_authz_settings(client_id=auth_client_id) assert res["allowRemoteResourceManagement"] @@ -4154,24 +4152,24 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): assert err.match(HTTP_404_REGEX) res = await admin.a_create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"} + client_id=auth_client_id, payload={"name": "test-resource"}, ) assert res["name"] == "test-resource", res test_resource_id = res["_id"] res = await admin.a_get_client_authz_resource( - client_id=auth_client_id, resource_id=test_resource_id + client_id=auth_client_id, resource_id=test_resource_id, ) assert res["_id"] == test_resource_id, res assert res["name"] == "test-resource", res with pytest.raises(KeycloakPostError) as err: await admin.a_create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"} + client_id=auth_client_id, payload={"name": "test-resource"}, ) assert err.match('409: b\'{"error":"invalid_request"') assert await admin.a_create_client_authz_resource( - client_id=auth_client_id, payload={"name": "test-resource"}, skip_exists=True + client_id=auth_client_id, payload={"name": "test-resource"}, skip_exists=True, ) == {"msg": "Already exists"} res = await admin.a_get_client_authz_resources(client_id=auth_client_id) @@ -4179,7 +4177,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): assert {x["name"] for x in res} == {"Default Resource", "test-resource"} res = await admin.a_create_client_authz_resource( - client_id=auth_client_id, payload={"name": "temp-resource"} + client_id=auth_client_id, payload={"name": "temp-resource"}, ) assert res["name"] == "temp-resource", res temp_resource_id: str = res["_id"] @@ -4190,7 +4188,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): payload={"name": "temp-updated-resource"}, ) res = await admin.a_get_client_authz_resource( - client_id=auth_client_id, resource_id=temp_resource_id + client_id=auth_client_id, resource_id=temp_resource_id, ) assert res["name"] == "temp-updated-resource", res with pytest.raises(KeycloakPutError) as err: @@ -4201,11 +4199,11 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): ) assert err.match("404: b''"), err await admin.a_delete_client_authz_resource( - client_id=auth_client_id, resource_id=temp_resource_id + client_id=auth_client_id, resource_id=temp_resource_id, ) with pytest.raises(KeycloakGetError) as err: await admin.a_get_client_authz_resource( - client_id=auth_client_id, resource_id=temp_resource_id + client_id=auth_client_id, resource_id=temp_resource_id, ) assert err.match("404: b''") @@ -4332,7 +4330,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): # Test getting associated policies for a permission associated_policies = await admin.a_get_client_authz_permission_associated_policies( - client_id=auth_client_id, policy_id=resource_based_permission_id + client_id=auth_client_id, policy_id=resource_based_permission_id, ) assert len(associated_policies) == 1 assert associated_policies[0]["name"].startswith(role_based_policy_name) @@ -4346,17 +4344,17 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): assert err.match(HTTP_404_REGEX) res = await admin.a_create_client_authz_scopes( - client_id=auth_client_id, payload={"name": "test-authz-scope"} + client_id=auth_client_id, payload={"name": "test-authz-scope"}, ) assert res["name"] == "test-authz-scope", res with pytest.raises(KeycloakPostError) as err: await admin.a_create_client_authz_scopes( - client_id="invalid_client_id", payload={"name": "test-authz-scope"} + client_id="invalid_client_id", payload={"name": "test-authz-scope"}, ) assert err.match('404: b\'{"error":"Could not find client".*}\'') assert await admin.a_create_client_authz_scopes( - client_id=auth_client_id, payload={"name": "test-authz-scope"} + client_id=auth_client_id, payload={"name": "test-authz-scope"}, ) res = await admin.a_get_client_authz_scopes(client_id=auth_client_id) @@ -4370,7 +4368,7 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakGetError) as err: await admin.a_get_client_service_account_user(client_id=client_id) assert ('b\'{"error":"Service account not enabled for the client' in str(err)) or err.match( - UNKOWN_ERROR_REGEX + UNKOWN_ERROR_REGEX, ) # Test delete client @@ -4392,14 +4390,14 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): "clientId": "test-confidential", "secret": "test-secret", "clientAuthenticatorType": "client-secret", - } + }, ) with pytest.raises(KeycloakGetError) as err: await admin.a_get_client_secrets(client_id="does-not-exist") assert err.match('404: b\'{"error":"Could not find client".*}\'') secrets = await admin.a_get_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential") + client_id=await admin.a_get_client_id(client_id="test-confidential"), ) assert secrets == {"type": "secret", "value": "test-secret"} @@ -4408,12 +4406,12 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str): assert err.match('404: b\'{"error":"Could not find client".*}\'') res = await admin.a_generate_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential") + client_id=await admin.a_get_client_id(client_id="test-confidential"), ) assert res assert ( await admin.a_get_client_secrets( - client_id=await admin.a_get_client_id(client_id="test-confidential") + client_id=await admin.a_get_client_id(client_id="test-confidential"), ) == res ) @@ -4452,14 +4450,14 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): # Test create realm role role_id = await admin.a_create_realm_role( - payload={"name": "test-realm-role"}, skip_exists=True + payload={"name": "test-realm-role"}, skip_exists=True, ) assert role_id, role_id with pytest.raises(KeycloakPostError) as err: await admin.a_create_realm_role(payload={"name": "test-realm-role"}) assert err.match('409: b\'{"errorMessage":"Role with name test-realm-role already exists"}\'') role_id_2 = await admin.a_create_realm_role( - payload={"name": "test-realm-role"}, skip_exists=True + payload={"name": "test-realm-role"}, skip_exists=True, ) assert role_id == role_id_2 @@ -4470,18 +4468,18 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): # Test update realm role res = await admin.a_update_realm_role( - role_name="test-realm-role", payload={"name": "test-realm-role-update"} + role_name="test-realm-role", payload={"name": "test-realm-role-update"}, ) assert res == dict(), res with pytest.raises(KeycloakPutError) as err: await admin.a_update_realm_role( - role_name="test-realm-role", payload={"name": "test-realm-role-update"} + role_name="test-realm-role", payload={"name": "test-realm-role-update"}, ) assert err.match(COULD_NOT_FIND_ROLE_REGEX) # Test realm role user assignment user_id = await admin.a_create_user( - payload={"username": "role-testing", "email": "test@test.test"} + payload={"username": "role-testing", "email": "test@test.test"}, ) with pytest.raises(KeycloakPostError) as err: await admin.a_assign_realm_roles(user_id=user_id, roles=["bad"]) @@ -4511,7 +4509,7 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): admin.delete_realm_roles_of_user(user_id=user_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_delete_realm_roles_of_user( - user_id=user_id, roles=[await admin.a_get_realm_role(role_name="offline_access")] + user_id=user_id, roles=[await admin.a_get_realm_role(role_name="offline_access")], ) assert res == dict(), res assert await admin.a_get_realm_role_members(role_name="offline_access") == list() @@ -4548,7 +4546,7 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): await admin.a_delete_group_realm_roles(group_id=group_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX) res = await admin.a_delete_group_realm_roles( - group_id=group_id, roles=[admin.get_realm_role(role_name="offline_access")] + group_id=group_id, roles=[admin.get_realm_role(role_name="offline_access")], ) assert res == dict(), res roles = await admin.a_get_group_realm_roles(group_id=group_id) @@ -4561,7 +4559,7 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): await admin.a_add_composite_realm_roles_to_role(role_name=composite_role, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_add_composite_realm_roles_to_role( - role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")] + role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")], ) assert res == dict(), res @@ -4585,7 +4583,7 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str): await admin.a_remove_composite_realm_roles_to_role(role_name=composite_role, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_remove_composite_realm_roles_to_role( - role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")] + role_name=composite_role, roles=[admin.get_realm_role(role_name="test-realm-role-update")], ) assert res == dict(), res @@ -4648,12 +4646,12 @@ async def test_a_role_attributes( attribute_role = "test-realm-role-w-attr" test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]} role_id = await admin.a_create_realm_role( - payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True + payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True, ) assert role_id, role_id cli_role_id = await admin.a_create_client_role( - client, payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True + client, payload={"name": attribute_role, "attributes": test_attrs}, skip_exists=True, ) assert cli_role_id, cli_role_id @@ -4701,13 +4699,13 @@ async def test_a_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): # create realm role for test role_id = await admin.a_create_realm_role( - payload={"name": "test-realm-role"}, skip_exists=True + payload={"name": "test-realm-role"}, skip_exists=True, ) assert role_id, role_id # Test realm role client assignment client_id = await admin.a_create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) with pytest.raises(KeycloakPostError) as err: await admin.a_assign_realm_roles_to_client_scope(client_id=client_id, roles=["bad"]) @@ -4733,7 +4731,7 @@ async def test_a_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): await admin.a_delete_realm_roles_of_client_scope(client_id=client_id, roles=["bad"]) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_delete_realm_roles_of_client_scope( - client_id=client_id, roles=[await admin.a_get_realm_role(role_name="offline_access")] + client_id=client_id, roles=[await admin.a_get_realm_role(role_name="offline_access")], ) assert res == dict(), res roles = await admin.a_get_realm_roles_of_client_scope(client_id=client_id) @@ -4741,7 +4739,7 @@ async def test_a_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): assert "test-realm-role" in [x["name"] for x in roles] res = await admin.a_delete_realm_roles_of_client_scope( - client_id=client_id, roles=[await admin.a_get_realm_role(role_name="test-realm-role")] + client_id=client_id, roles=[await admin.a_get_realm_role(role_name="test-realm-role")], ) assert res == dict(), res roles = await admin.a_get_realm_roles_of_client_scope(client_id=client_id) @@ -4762,7 +4760,7 @@ async def test_a_client_scope_client_roles(admin: KeycloakAdmin, realm: str, cli await admin.a_change_current_realm(realm) client_id = await admin.a_create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client roles @@ -4771,14 +4769,14 @@ async def test_a_client_scope_client_roles(admin: KeycloakAdmin, realm: str, cli # create client role for test client_role_id = await admin.a_create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) assert client_role_id, client_role_id # Test client role assignment to other client with pytest.raises(KeycloakPostError) as err: await admin.a_assign_client_roles_to_client_scope( - client_id=client_id, client_roles_owner_id=client, roles=["bad"] + client_id=client_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_assign_client_roles_to_client_scope( @@ -4789,7 +4787,7 @@ async def test_a_client_scope_client_roles(admin: KeycloakAdmin, realm: str, cli assert res == dict(), res roles = await admin.a_get_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client + client_id=client_id, client_roles_owner_id=client, ) assert len(roles) == 1 client_role_names = [x["name"] for x in roles] @@ -4798,7 +4796,7 @@ async def test_a_client_scope_client_roles(admin: KeycloakAdmin, realm: str, cli # Test remove realm role of client with pytest.raises(KeycloakDeleteError) as err: await admin.a_delete_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client, roles=["bad"] + client_id=client_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_delete_client_roles_of_client_scope( @@ -4808,7 +4806,7 @@ async def test_a_client_scope_client_roles(admin: KeycloakAdmin, realm: str, cli ) assert res == dict(), res roles = await admin.a_get_client_roles_of_client_scope( - client_id=client_id, client_roles_owner_id=client + client_id=client_id, client_roles_owner_id=client, ) assert len(roles) == 0 @@ -4841,7 +4839,7 @@ async def test_a_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: # Test get client roles client_specific_roles = await admin.a_get_client_specific_roles_of_client_scope( - client_scope_id, client + client_scope_id, client, ) assert len(client_specific_roles) == 0, client_specific_roles all_roles = await admin.a_get_all_roles_of_client_scope(client_scope_id) @@ -4849,14 +4847,14 @@ async def test_a_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: # create client role for test client_role_name = await admin.a_create_client_role( - client_role_id=client, payload={"name": CLIENT_ROLE_NAME}, skip_exists=True + client_role_id=client, payload={"name": CLIENT_ROLE_NAME}, skip_exists=True, ) assert client_role_name, client_role_name # Test client role assignment to other client with pytest.raises(KeycloakPostError) as err: await admin.a_add_client_specific_roles_to_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"] + client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err @@ -4869,7 +4867,7 @@ async def test_a_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: # Test when getting roles for the specific owner client client_specific_roles = await admin.a_get_client_specific_roles_of_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client + client_scope_id=client_scope_id, client_roles_owner_id=client, ) assert len(client_specific_roles) == 1 client_role_names = [x["name"] for x in client_specific_roles] @@ -4887,7 +4885,7 @@ async def test_a_client_scope_mapping_client_roles(admin: KeycloakAdmin, realm: # Test remove realm role of client with pytest.raises(KeycloakDeleteError) as err: await admin.a_remove_client_specific_roles_of_client_scope( - client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"] + client_scope_id=client_scope_id, client_roles_owner_id=client, roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err @@ -4916,7 +4914,7 @@ async def test_a_client_default_client_scopes(admin: KeycloakAdmin, realm: str, await admin.a_change_current_realm(realm) client_id = await admin.a_create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client default scopes # keycloak default roles: web-origins, acr, profile, roles, email @@ -4938,7 +4936,7 @@ async def test_a_client_default_client_scopes(admin: KeycloakAdmin, realm: str, "clientScopeId": new_client_scope_id, } await admin.a_add_client_default_client_scope( - client_id, new_client_scope_id, new_default_client_scope_data + client_id, new_client_scope_id, new_default_client_scope_data, ) default_client_scopes = await admin.a_get_client_default_client_scopes(client_id) assert len(default_client_scopes) in [6, 7], default_client_scopes @@ -4963,7 +4961,7 @@ async def test_a_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, await admin.a_change_current_realm(realm) client_id = await admin.a_create_client( - payload={"name": "role-testing-client", "clientId": "role-testing-client"} + payload={"name": "role-testing-client", "clientId": "role-testing-client"}, ) # Test get client optional scopes # keycloak optional roles: microprofile-jwt, offline_access, address, --> for versions < 26.0.0 @@ -4986,7 +4984,7 @@ async def test_a_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, "clientScopeId": new_client_scope_id, } await admin.a_add_client_optional_client_scope( - client_id, new_client_scope_id, new_optional_client_scope_data + client_id, new_client_scope_id, new_optional_client_scope_data, ) optional_client_scopes = await admin.a_get_client_optional_client_scopes(client_id) assert len(optional_client_scopes) in [5, 6], optional_client_scopes @@ -5015,15 +5013,15 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test create client role client_role_id = await admin.a_create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) with pytest.raises(KeycloakPostError) as err: await admin.a_create_client_role( - client_role_id=client, payload={"name": "client-role-test"} + client_role_id=client, payload={"name": "client-role-test"}, ) assert err.match('409: b\'{"errorMessage":"Role with name client-role-test already exists"}\'') client_role_id_2 = await admin.a_create_client_role( - client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True, ) assert client_role_id == client_role_id_2 @@ -5043,7 +5041,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test update client role res = await admin.a_update_client_role( - client_id=client, role_name="client-role-test", payload={"name": "client-role-test-update"} + client_id=client, role_name="client-role-test", payload={"name": "client-role-test-update"}, ) assert res == dict() with pytest.raises(KeycloakPutError) as err: @@ -5056,7 +5054,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test user with client role res = await admin.a_get_client_role_members( - client_id=client, role_name="client-role-test-update" + client_id=client, role_name="client-role-test-update", ) assert len(res) == 0 with pytest.raises(KeycloakGetError) as err: @@ -5071,15 +5069,15 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): user_id=user_id, client_id=client, roles=[ - await admin.a_get_client_role(client_id=client, role_name="client-role-test-update") + await admin.a_get_client_role(client_id=client, role_name="client-role-test-update"), ], ) assert res == dict() assert ( len( await admin.a_get_client_role_members( - client_id=client, role_name="client-role-test-update" - ) + client_id=client, role_name="client-role-test-update", + ), ) == 1 ) @@ -5109,14 +5107,14 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): user_id=user_id, client_id=client, roles=[ - await admin.a_get_client_role(client_id=client, role_name="client-role-test-update") + await admin.a_get_client_role(client_id=client, role_name="client-role-test-update"), ], ) assert len(await admin.a_get_client_roles_of_user(user_id=user_id, client_id=client)) == 0 # Test groups and client roles res = await admin.a_get_client_role_groups( - client_id=client, role_name="client-role-test-update" + client_id=client, role_name="client-role-test-update", ) assert len(res) == 0 with pytest.raises(KeycloakGetError) as err: @@ -5137,15 +5135,15 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): group_id=group_id, client_id=client, roles=[ - await admin.a_get_client_role(client_id=client, role_name="client-role-test-update") + await admin.a_get_client_role(client_id=client, role_name="client-role-test-update"), ], ) assert res == dict() assert ( len( await admin.a_get_client_role_groups( - client_id=client, role_name="client-role-test-update" - ) + client_id=client, role_name="client-role-test-update", + ), ) == 1 ) @@ -5158,7 +5156,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): group_id=group_id, client_id=client, roles=[ - await admin.a_get_client_role(client_id=client, role_name="client-role-test-update") + await admin.a_get_client_role(client_id=client, role_name="client-role-test-update"), ], ) assert res == dict() @@ -5166,7 +5164,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test composite client roles with pytest.raises(KeycloakPostError) as err: await admin.a_add_composite_client_roles_to_role( - client_role_id=client, role_name="client-role-test-update", roles=["bad"] + client_role_id=client, role_name="client-role-test-update", roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_add_composite_client_roles_to_role( @@ -5182,7 +5180,7 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test removal of composite client roles with pytest.raises(KeycloakDeleteError) as err: await admin.a_remove_composite_client_roles_from_role( - client_role_id=client, role_name="client-role-test-update", roles=["bad"] + client_role_id=client, role_name="client-role-test-update", roles=["bad"], ) assert err.match(UNKOWN_ERROR_REGEX), err res = await admin.a_remove_composite_client_roles_from_role( @@ -5197,18 +5195,18 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test delete of client role res = await admin.a_delete_client_role( - client_role_id=client, role_name="client-role-test-update" + client_role_id=client, role_name="client-role-test-update", ) assert res == dict() with pytest.raises(KeycloakDeleteError) as err: await admin.a_delete_client_role( - client_role_id=client, role_name="client-role-test-update" + client_role_id=client, role_name="client-role-test-update", ) assert err.match(COULD_NOT_FIND_ROLE_REGEX) # Test of roles by id - Get role await admin.a_create_client_role( - client_role_id=client, payload={"name": "client-role-by-id-test"}, skip_exists=True + client_role_id=client, payload={"name": "client-role-by-id-test"}, skip_exists=True, ) role = await admin.a_get_client_role(client_id=client, role_name="client-role-by-id-test") res = await admin.a_get_role_by_id(role_id=role["id"]) @@ -5219,12 +5217,12 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): # Test of roles by id - Update role res = await admin.a_update_role_by_id( - role_id=role["id"], payload={"name": "client-role-by-id-test-update"} + role_id=role["id"], payload={"name": "client-role-by-id-test-update"}, ) assert res == dict() with pytest.raises(KeycloakPutError) as err: res = await admin.a_update_role_by_id( - role_id="bad", payload={"name": "client-role-by-id-test-update"} + role_id="bad", payload={"name": "client-role-by-id-test-update"}, ) assert err.match(COULD_NOT_FIND_ROLE_WITH_ID_REGEX) @@ -5251,10 +5249,10 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Create test clients source_client_id = await admin.a_create_client( - payload={"name": "Source Client", "clientId": "source-client"} + payload={"name": "Source Client", "clientId": "source-client"}, ) target_client_id = await admin.a_create_client( - payload={"name": "Target Client", "clientId": "target-client"} + payload={"name": "Target Client", "clientId": "target-client"}, ) for c in await admin.a_get_clients(): if c["clientId"] == "realm-management": @@ -5265,7 +5263,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Enable permissions on the Superset client await admin.a_update_client_management_permissions( - payload={"enabled": True}, client_id=target_client_id + payload={"enabled": True}, client_id=target_client_id, ) # Fetch various IDs and strings needed when creating the permission @@ -5273,7 +5271,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): await admin.a_get_client_management_permissions(client_id=target_client_id) )["scopePermissions"]["token-exchange"] scopes = await admin.a_get_client_authz_policy_scopes( - client_id=realm_management_id, policy_id=token_exchange_permission_id + client_id=realm_management_id, policy_id=token_exchange_permission_id, ) for s in scopes: @@ -5284,7 +5282,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): raise AssertionError("Missing token-exchange scope") resources = await admin.a_get_client_authz_policy_resources( - client_id=realm_management_id, policy_id=token_exchange_permission_id + client_id=realm_management_id, policy_id=token_exchange_permission_id, ) for r in resources: if r["name"] == f"client.resource.{target_client_id}": @@ -5318,7 +5316,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): # Update permissions on the target client to reference this policy permission_name = ( await admin.a_get_client_authz_scope_permission( - client_id=realm_management_id, scope_id=token_exchange_permission_id + client_id=realm_management_id, scope_id=token_exchange_permission_id, ) )["name"] await admin.a_update_client_authz_scope_permission( @@ -5352,7 +5350,7 @@ async def test_a_enable_token_exchange(admin: KeycloakAdmin, realm: str): ) permission_name = ( await admin.a_get_client_authz_scope_permission( - client_id=realm_management_id, scope_id=token_exchange_permission_id + client_id=realm_management_id, scope_id=token_exchange_permission_id, ) )["name"] assert permission_name.startswith("token-exchange.permission.client.") @@ -5393,9 +5391,12 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str): :param user: Keycloak user :type user: str """ - with patch.object( - admin.connection.async_s, "put", side_effect=Exception("An expected error") - ) as mock_put, pytest.raises(KeycloakConnectionError): + with ( + patch.object( + admin.connection.async_s, "put", side_effect=Exception("An expected error"), + ) as mock_put, + pytest.raises(KeycloakConnectionError), + ): await admin.a_send_update_account( user_id=user, payload=["UPDATE_PASSWORD"], @@ -5411,11 +5412,14 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str): timeout=60, ) - with patch.object( - admin.connection.async_s, "put", side_effect=Exception("An expected error") - ) as mock_put, pytest.raises(KeycloakConnectionError): + with ( + patch.object( + admin.connection.async_s, "put", side_effect=Exception("An expected error"), + ) as mock_put, + pytest.raises(KeycloakConnectionError), + ): await admin.a_send_verify_email( - user_id=user, client_id="verify-client-id", redirect_uri="https://example.com" + user_id=user, client_id="verify-client-id", redirect_uri="https://example.com", ) mock_put.assert_awaited_once_with( @@ -5435,7 +5439,7 @@ async def test_a_get_sessions(admin: KeycloakAdmin): :type admin: KeycloakAdmin """ sessions = await admin.a_get_sessions( - user_id=admin.get_user_id(username=admin.connection.username) + user_id=admin.get_user_id(username=admin.connection.username), ) assert len(sessions) >= 1 with pytest.raises(KeycloakGetError) as err: @@ -5457,7 +5461,7 @@ async def test_a_get_client_installation_provider(admin: KeycloakAdmin, client: assert err.match('404: b\'{"error":"Unknown Provider".*}\'') installation = await admin.a_get_client_installation_provider( - client_id=client, provider_id="keycloak-oidc-keycloak-json" + client_id=client, provider_id="keycloak-oidc-keycloak-json", ) assert set(installation.keys()) == { "auth-server-url", @@ -5493,7 +5497,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str): "direct grant", "first broker login", "clients", - } + }, ) assert set(res[0].keys()) == { "alias", @@ -5514,7 +5518,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str): "first broker login", "clients", "http challenge", - } + }, ) with pytest.raises(KeycloakGetError) as err: @@ -5530,23 +5534,23 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str): assert ('b\'{"error":"Flow not found"' in str(err)) or err.match("404: b''") res = await admin.a_copy_authentication_flow( - payload={"newName": "test-browser"}, flow_alias="browser" + payload={"newName": "test-browser"}, flow_alias="browser", ) assert res == b"", res assert len(await admin.a_get_authentication_flows()) == (default_flows + 1) # Test create res = await admin.a_create_authentication_flow( - payload={"alias": "test-create", "providerId": "basic-flow"} + payload={"alias": "test-create", "providerId": "basic-flow"}, ) assert res == b"" with pytest.raises(KeycloakPostError) as err: await admin.a_create_authentication_flow( - payload={"alias": "test-create", "builtIn": False} + payload={"alias": "test-create", "builtIn": False}, ) assert err.match('409: b\'{"errorMessage":"Flow test-create already exists"}\'') assert await admin.a_create_authentication_flow( - payload={"alias": "test-create"}, skip_exists=True + payload={"alias": "test-create"}, skip_exists=True, ) == {"msg": "Already exists"} # Test flow executions @@ -5573,7 +5577,7 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str): "priority", "required", "requirement", - } + }, ), res.keys() with pytest.raises(KeycloakGetError) as err: await admin.a_get_authentication_flow_execution(execution_id="bad") @@ -5584,20 +5588,20 @@ async def test_a_auth_flows(admin: KeycloakAdmin, realm: str): assert err.match('400: b\'{"error":"It is illegal to add execution to a built in flow".*}\'') res = await admin.a_create_authentication_flow_execution( - payload={"provider": "auth-cookie"}, flow_alias="test-create" + payload={"provider": "auth-cookie"}, flow_alias="test-create", ) assert res == b"" assert len(await admin.a_get_authentication_flow_executions(flow_alias="test-create")) == 1 with pytest.raises(KeycloakPutError) as err: await admin.a_update_authentication_flow_executions( - payload={"required": "yes"}, flow_alias="test-create" + payload={"required": "yes"}, flow_alias="test-create", ) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") payload = (await admin.a_get_authentication_flow_executions(flow_alias="test-create"))[0] payload["displayName"] = "test" res = await admin.a_update_authentication_flow_executions( - payload=payload, flow_alias="test-create" + payload=payload, flow_alias="test-create", ) assert res or (res == {}) @@ -5737,16 +5741,16 @@ async def test_a_client_scopes(admin: KeycloakAdmin, realm: str): # Test create client scope res = await admin.a_create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True, ) assert res res2 = await admin.a_create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=True, ) assert res == res2 with pytest.raises(KeycloakPostError) as err: await admin.a_create_client_scope( - payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=False + payload={"name": "test-scope", "protocol": "openid-connect"}, skip_exists=False, ) assert err.match('409: b\'{"errorMessage":"Client Scope test-scope already exists"}\'') @@ -5756,7 +5760,7 @@ async def test_a_client_scopes(admin: KeycloakAdmin, realm: str): assert err.match(NO_CLIENT_SCOPE_REGEX) res_update = await admin.a_update_client_scope( - client_scope_id=res, payload={"name": "test-scope-update"} + client_scope_id=res, payload={"name": "test-scope-update"}, ) assert res_update == dict() assert (await admin.a_get_client_scope(client_scope_id=res))["name"] == "test-scope-update" @@ -5785,12 +5789,12 @@ async def test_a_client_scopes(admin: KeycloakAdmin, realm: str): test_mapper = (await admin.a_get_mappers_from_client_scope(client_scope_id=res))[0] with pytest.raises(KeycloakPutError) as err: await admin.a_update_mapper_in_client_scope( - client_scope_id="does-not-exist", protocol_mapper_id=test_mapper["id"], payload=dict() + client_scope_id="does-not-exist", protocol_mapper_id=test_mapper["id"], payload=dict(), ) assert err.match(NO_CLIENT_SCOPE_REGEX) test_mapper["config"]["user.attribute"] = "test" res_update = await admin.a_update_mapper_in_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"], payload=test_mapper + client_scope_id=res, protocol_mapper_id=test_mapper["id"], payload=test_mapper, ) assert res_update == dict() assert (await admin.a_get_mappers_from_client_scope(client_scope_id=res))[0]["config"][ @@ -5799,12 +5803,12 @@ async def test_a_client_scopes(admin: KeycloakAdmin, realm: str): # Test delete mapper res_del = await admin.a_delete_mapper_from_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"] + client_scope_id=res, protocol_mapper_id=test_mapper["id"], ) assert res_del == dict() with pytest.raises(KeycloakDeleteError) as err: await admin.a_delete_mapper_from_client_scope( - client_scope_id=res, protocol_mapper_id=test_mapper["id"] + client_scope_id=res, protocol_mapper_id=test_mapper["id"], ) assert err.match('404: b\'{"error":"Model not found".*}\'') @@ -5881,7 +5885,7 @@ async def test_a_components(admin: KeycloakAdmin, realm: str): # Test create component with pytest.raises(KeycloakPostError) as err: await admin.a_create_component(payload={"bad": "dict"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") res = await admin.a_create_component( payload={ @@ -5890,7 +5894,7 @@ async def test_a_components(admin: KeycloakAdmin, realm: str): "providerType": "org.keycloak.services.clientregistration." + "policy.ClientRegistrationPolicy", "config": {"max-clients": ["1000"]}, - } + }, ) assert res assert (await admin.a_get_component(component_id=res))["name"] == "Test Component" @@ -5976,10 +5980,10 @@ async def test_a_user_events(admin: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPutError) as err: await admin.a_set_events(payload={"bad": "conf"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") res = await admin.a_set_events( - payload={"adminEventsDetailsEnabled": True, "adminEventsEnabled": True} + payload={"adminEventsDetailsEnabled": True, "adminEventsEnabled": True}, ) assert res == dict() @@ -6023,7 +6027,7 @@ async def test_a_auto_refresh(admin_frozen: KeycloakAdmin, realm: str): with pytest.raises(KeycloakPostError) as err: await admin.a_get_realm(realm_name="test-refresh") assert err.match( - '400: b\'{"error":"invalid_grant","error_description":"Invalid refresh token"}\'' + '400: b\'{"error":"invalid_grant","error_description":"Invalid refresh token"}\'', ) admin.connection.get_token() @@ -6115,7 +6119,7 @@ async def test_a_update_required_action(admin: KeycloakAdmin, realm: str): @pytest.mark.asyncio async def test_a_get_composite_client_roles_of_group( - admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str + admin: KeycloakAdmin, realm: str, client: str, group: str, composite_client_role: str, ): """Test get composite client roles of group. @@ -6139,7 +6143,7 @@ async def test_a_get_composite_client_roles_of_group( @pytest.mark.asyncio async def test_a_get_role_client_level_children( - admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str + admin: KeycloakAdmin, realm: str, client: str, composite_client_role: str, client_role: str, ): """Test get children of composite client role. @@ -6163,7 +6167,7 @@ async def test_a_get_role_client_level_children( @pytest.mark.asyncio async def test_a_upload_certificate( - admin: KeycloakAdmin, realm: str, client: str, selfsigned_cert: tuple + admin: KeycloakAdmin, realm: str, client: str, selfsigned_cert: tuple, ): """Test upload certificate. @@ -6186,7 +6190,7 @@ async def test_a_upload_certificate( @pytest.mark.asyncio async def test_a_get_bruteforce_status_for_user( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -6224,7 +6228,7 @@ async def test_a_get_bruteforce_status_for_user( @pytest.mark.asyncio async def test_a_clear_bruteforce_attempts_for_user( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -6265,7 +6269,7 @@ async def test_a_clear_bruteforce_attempts_for_user( @pytest.mark.asyncio async def test_a_clear_bruteforce_attempts_for_all_users( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], realm: str, ): """Test users. @@ -6321,7 +6325,7 @@ async def test_a_default_realm_role_present(realm: str, admin: KeycloakAdmin) -> x["name"] for x in await admin.a_get_realm_roles() if x["name"] == f"default-roles-{realm}" - ] + ], ) == 1 ) @@ -6434,7 +6438,7 @@ async def test_a_clear_user_cache(realm: str, admin: KeycloakAdmin) -> None: @pytest.mark.asyncio async def test_a_initial_access_token( - admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str] + admin: KeycloakAdmin, oid_with_credentials: Tuple[KeycloakOpenID, str, str], ) -> None: """Test initial access token and client creation. @@ -6469,7 +6473,7 @@ async def test_a_initial_access_token( new_secret = str(uuid.uuid4()) res = await oid.a_update_client( - res["registrationAccessToken"], client, payload={"secret": new_secret} + res["registrationAccessToken"], client, payload={"secret": new_secret}, ) assert res["secret"] == new_secret diff --git a/tests/test_keycloak_openid.py b/tests/test_keycloak_openid.py index d28ba97..ea0cb16 100644 --- a/tests/test_keycloak_openid.py +++ b/tests/test_keycloak_openid.py @@ -177,7 +177,7 @@ def test_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): def test_exchange_token( - oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin + oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test the exchange token method. @@ -198,7 +198,7 @@ def test_exchange_token( admin.get_client_role( client_id=admin.get_client_id(client_id="realm-management"), role_name="impersonation", - ) + ), ], ) @@ -215,7 +215,7 @@ def test_exchange_token( # Exchange token with the new user new_token = oid.exchange_token( - token=token["access_token"], audience=oid.client_id, subject=username + token=token["access_token"], audience=oid.client_id, subject=username, ) assert oid.userinfo(token=new_token["access_token"]) == { "email": f"{username}@test.test", @@ -264,7 +264,7 @@ def test_public_key(oid: KeycloakOpenID): def test_entitlement( - oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin + oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test entitlement. @@ -277,7 +277,7 @@ def test_entitlement( oid, username, password = oid_with_credentials_authz token = oid.token(username=username, password=password) resource_server_id = admin.get_client_authz_resources( - client_id=admin.get_client_id(oid.client_id) + client_id=admin.get_client_id(oid.client_id), )[0]["_id"] with pytest.raises(KeycloakDeprecationError): @@ -295,7 +295,7 @@ def test_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): assert oid.introspect(token=token["access_token"])["active"] assert oid.introspect( - token=token["access_token"], rpt="some", token_type_hint="requesting_party_token" + token=token["access_token"], rpt="some", token_type_hint="requesting_party_token", ) == {"active": False} with pytest.raises(KeycloakRPTNotFound): @@ -340,14 +340,14 @@ def test_decode_token_invalid_token(oid_with_credentials: Tuple[KeycloakOpenID, with pytest.raises(jwcrypto.jws.InvalidJWSSignature): decoded_invalid_access_token = oid.decode_token( - token=invalid_access_token, validate=True, key=key + token=invalid_access_token, validate=True, key=key, ) decoded_invalid_access_token = oid.decode_token(token=invalid_access_token, validate=False) assert decoded_access_token == decoded_invalid_access_token decoded_invalid_access_token = oid.decode_token( - token=invalid_access_token, validate=False, key=key + token=invalid_access_token, validate=False, key=key, ) assert decoded_access_token == decoded_invalid_access_token @@ -368,7 +368,7 @@ def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpe assert isinstance(oid.authorization.policies["test-authz-rb-policy"].roles[0], Role) assert len(oid.authorization.policies["test-authz-rb-policy"].permissions) == 2 assert isinstance( - oid.authorization.policies["test-authz-rb-policy"].permissions[0], Permission + oid.authorization.policies["test-authz-rb-policy"].permissions[0], Permission, ) @@ -430,8 +430,8 @@ def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, policy.add_role(role="account/view-profile") policy.add_permission( permission=Permission( - name="test-perm", type="resource", logic="POSITIVE", decision_strategy="UNANIMOUS" - ) + name="test-perm", type="resource", logic="POSITIVE", decision_strategy="UNANIMOUS", + ), ) oid.authorization.policies["test"] = policy assert [ @@ -464,7 +464,7 @@ 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 + oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test has UMA access. @@ -497,8 +497,8 @@ def test_has_uma_access( assert ( str( oid.has_uma_access( - token=admin.connection.token["access_token"], permissions="Default Resource" - ) + token=admin.connection.token["access_token"], permissions="Default Resource", + ), ) == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=" + "{'Default Resource'})" @@ -666,7 +666,7 @@ async def test_a_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): @pytest.mark.asyncio async def test_a_exchange_token( - oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin + oid_with_credentials: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test the exchange token method. @@ -687,7 +687,7 @@ async def test_a_exchange_token( await admin.a_get_client_role( client_id=admin.get_client_id(client_id="realm-management"), role_name="impersonation", - ) + ), ], ) @@ -704,7 +704,7 @@ async def test_a_exchange_token( # Exchange token with the new user new_token = oid.exchange_token( - token=token["access_token"], audience=oid.client_id, subject=username + token=token["access_token"], audience=oid.client_id, subject=username, ) assert await oid.a_userinfo(token=new_token["access_token"]) == { "email": f"{username}@test.test", @@ -757,7 +757,7 @@ async def test_a_public_key(oid: KeycloakOpenID): @pytest.mark.asyncio async def test_a_entitlement( - oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin + oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test entitlement. @@ -770,7 +770,7 @@ async def test_a_entitlement( oid, username, password = oid_with_credentials_authz token = await oid.a_token(username=username, password=password) resource_server_id = admin.get_client_authz_resources( - client_id=admin.get_client_id(oid.client_id) + client_id=admin.get_client_id(oid.client_id), )[0]["_id"] with pytest.raises(KeycloakDeprecationError): @@ -789,12 +789,12 @@ async def test_a_introspect(oid_with_credentials: Tuple[KeycloakOpenID, str, str assert (await oid.a_introspect(token=token["access_token"]))["active"] assert await oid.a_introspect( - token=token["access_token"], rpt="some", token_type_hint="requesting_party_token" + token=token["access_token"], rpt="some", token_type_hint="requesting_party_token", ) == {"active": False} with pytest.raises(KeycloakRPTNotFound): await oid.a_introspect( - token=token["access_token"], token_type_hint="requesting_party_token" + token=token["access_token"], token_type_hint="requesting_party_token", ) @@ -835,28 +835,28 @@ async def test_a_decode_token_invalid_token(oid_with_credentials: Tuple[Keycloak invalid_access_token = access_token + "a" with pytest.raises(jwcrypto.jws.InvalidJWSSignature): decoded_invalid_access_token = await oid.a_decode_token( - token=invalid_access_token, validate=True + token=invalid_access_token, validate=True, ) with pytest.raises(jwcrypto.jws.InvalidJWSSignature): decoded_invalid_access_token = await oid.a_decode_token( - token=invalid_access_token, validate=True, key=key + token=invalid_access_token, validate=True, key=key, ) decoded_invalid_access_token = await oid.a_decode_token( - token=invalid_access_token, validate=False + token=invalid_access_token, validate=False, ) assert decoded_access_token == decoded_invalid_access_token decoded_invalid_access_token = await oid.a_decode_token( - token=invalid_access_token, validate=False, key=key + token=invalid_access_token, validate=False, key=key, ) assert decoded_access_token == decoded_invalid_access_token @pytest.mark.asyncio async def test_a_load_authorization_config( - oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str] + oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], ): """Test load authorization config. @@ -873,13 +873,13 @@ async def test_a_load_authorization_config( assert isinstance(oid.authorization.policies["test-authz-rb-policy"].roles[0], Role) assert len(oid.authorization.policies["test-authz-rb-policy"].permissions) == 2 assert isinstance( - oid.authorization.policies["test-authz-rb-policy"].permissions[0], Permission + oid.authorization.policies["test-authz-rb-policy"].permissions[0], Permission, ) @pytest.mark.asyncio async def test_a_has_uma_access( - oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin + oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin, ): """Test has UMA access. @@ -898,7 +898,7 @@ async def test_a_has_uma_access( ) assert ( str( - await oid.a_has_uma_access(token=token["access_token"], permissions="Default Resource") + await oid.a_has_uma_access(token=token["access_token"], permissions="Default Resource"), ) == "AuthStatus(is_authorized=True, is_logged_in=True, missing_permissions=set())" ) @@ -914,8 +914,8 @@ async def test_a_has_uma_access( assert ( str( await oid.a_has_uma_access( - token=admin.connection.token["access_token"], permissions="Default Resource" - ) + token=admin.connection.token["access_token"], permissions="Default Resource", + ), ) == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=" + "{'Default Resource'})" @@ -986,20 +986,20 @@ async def test_a_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenI policy.add_role(role="account/view-profile") policy.add_permission( permission=Permission( - name="test-perm", type="resource", logic="POSITIVE", decision_strategy="UNANIMOUS" - ) + name="test-perm", type="resource", logic="POSITIVE", decision_strategy="UNANIMOUS", + ), ) oid.authorization.policies["test"] = policy assert [ str(x) for x in await oid.a_get_permissions( - token=token["access_token"], method_token_info="decode" + token=token["access_token"], method_token_info="decode", ) ] == ["Permission: test-perm (resource)"] assert [ repr(x) for x in await oid.a_get_permissions( - token=token["access_token"], method_token_info="decode" + token=token["access_token"], method_token_info="decode", ) ] == [""] oid.client_id = orig_client_id diff --git a/tests/test_keycloak_uma.py b/tests/test_keycloak_uma.py index 09f7aa8..223e6e2 100644 --- a/tests/test_keycloak_uma.py +++ b/tests/test_keycloak_uma.py @@ -109,7 +109,7 @@ def test_uma_resource_sets(uma: KeycloakUMA): assert len(resource_set_list_ids) == 0 # With matchingUri query option resource_set_list_ids = uma.resource_set_list_ids( - uri="/some_resources/resource", matchingUri=True + uri="/some_resources/resource", matchingUri=True, ) assert len(resource_set_list_ids) == 1 @@ -119,8 +119,8 @@ def test_uma_resource_sets(uma: KeycloakUMA): assert err.match( re.escape( '409: b\'{"error":"invalid_request","error_description":' - '"Resource with name [mytest] already exists."}\'' - ) + '"Resource with name [mytest] already exists."}\'', + ), ) # Test get resource set @@ -137,7 +137,7 @@ def test_uma_resource_sets(uma: KeycloakUMA): # Test update resource set fail with pytest.raises(KeycloakPutError) as err: uma.resource_set_update(resource_id=created_resource["_id"], payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Test delete resource set res = uma.resource_set_delete(resource_id=created_resource["_id"]) @@ -215,7 +215,7 @@ def test_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin): with pytest.raises(KeycloakDeleteError) as err: uma.policy_delete(policy_id) assert err.match( - '404: b\'{"error":"invalid_request","error_description":"Policy with .* does not exist"}\'' + '404: b\'{"error":"invalid_request","error_description":"Policy with .* does not exist"}\'', ) policies = uma.policy_query() @@ -312,7 +312,7 @@ def test_uma_permission_ticket(uma: KeycloakUMA): response = uma.permission_ticket_create(permissions) rpt = uma.connection.keycloak_openid.token( - grant_type="urn:ietf:params:oauth:grant-type:uma-ticket", ticket=response["ticket"] + grant_type="urn:ietf:params:oauth:grant-type:uma-ticket", ticket=response["ticket"], ) assert rpt assert "access_token" in rpt @@ -406,7 +406,7 @@ async def test_a_uma_resource_sets(uma: KeycloakUMA): assert len(resource_set_list_ids) == 0 # With matchingUri query option resource_set_list_ids = await uma.a_resource_set_list_ids( - uri="/some_resources/resource", matchingUri=True + uri="/some_resources/resource", matchingUri=True, ) assert len(resource_set_list_ids) == 1 @@ -416,8 +416,8 @@ async def test_a_uma_resource_sets(uma: KeycloakUMA): assert err.match( re.escape( '409: b\'{"error":"invalid_request","error_description":' - '"Resource with name [mytest] already exists."}\'' - ) + '"Resource with name [mytest] already exists."}\'', + ), ) # Test get resource set @@ -434,7 +434,7 @@ async def test_a_uma_resource_sets(uma: KeycloakUMA): # Test update resource set fail with pytest.raises(KeycloakPutError) as err: uma.resource_set_update(resource_id=created_resource["_id"], payload={"wrong": "payload"}) - assert err.match('400: b\'{"error":"Unrecognized field') + assert err.match("Unrecognized field") # Test delete resource set res = await uma.a_resource_set_delete(resource_id=created_resource["_id"]) @@ -513,7 +513,7 @@ async def test_a_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin): with pytest.raises(KeycloakDeleteError) as err: await uma.a_policy_delete(policy_id) assert err.match( - '404: b\'{"error":"invalid_request","error_description":"Policy with .* does not exist"}\'' + '404: b\'{"error":"invalid_request","error_description":"Policy with .* does not exist"}\'', ) policies = await uma.a_policy_query() @@ -612,7 +612,7 @@ async def test_a_uma_permission_ticket(uma: KeycloakUMA): response = await uma.a_permission_ticket_create(permissions) rpt = await uma.connection.keycloak_openid.a_token( - grant_type="urn:ietf:params:oauth:grant-type:uma-ticket", ticket=response["ticket"] + grant_type="urn:ietf:params:oauth:grant-type:uma-ticket", ticket=response["ticket"], ) assert rpt assert "access_token" in rpt diff --git a/tests/test_license.py b/tests/test_license.py index 8050299..d405607 100644 --- a/tests/test_license.py +++ b/tests/test_license.py @@ -8,8 +8,8 @@ def test_license_present(): for path, _, files in os.walk("src/keycloak"): for _file in files: if _file.endswith(".py"): - with open(os.path.join(path, _file), "r") as fp: + with open(os.path.join(path, _file)) as fp: content = fp.read() assert content.startswith( - "# -*- coding: utf-8 -*-\n#\n# The MIT License (MIT)\n#\n#" + "# -*- coding: utf-8 -*-\n#\n# The MIT License (MIT)\n#\n#", ) diff --git a/tests/test_uma_permissions.py b/tests/test_uma_permissions.py index 8179ff9..f39c106 100644 --- a/tests/test_uma_permissions.py +++ b/tests/test_uma_permissions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2017 Marcos Pereira # @@ -150,7 +149,7 @@ def test_build_permission_tuple_dict_str_list_str(): def test_build_permission_tuple_dict_str_list_str2(): """Test build permission param with mutliple-keyed dictionary.""" assert build_permission_param( - {"res1": ["scope1", "scope2"], "res2": ["scope2", "scope3"]} + {"res1": ["scope1", "scope2"], "res2": ["scope2", "scope3"]}, ) == {"res1#scope1", "res1#scope2", "res2#scope2", "res2#scope3"} @@ -162,7 +161,7 @@ def test_build_permission_uma(): def test_build_permission_uma_list(): """Test build permission param with list of UMAs.""" assert build_permission_param( - [Resource("res1")(Scope("scope1")), Resource("res1")(Scope("scope2"))] + [Resource("res1")(Scope("scope1")), Resource("res1")(Scope("scope2"))], ) == {"res1#scope1", "res1#scope2"} diff --git a/tests/test_urls_patterns.py b/tests/test_urls_patterns.py index c6b2386..0399ce0 100644 --- a/tests/test_urls_patterns.py +++ b/tests/test_urls_patterns.py @@ -31,7 +31,7 @@ def test_correctness_of_patterns(): for url in urls: url_value = urls_patterns.__dict__[url] assert url_value not in seen_url_values, f"The url {url} has a duplicate value {url_value}" - assert ( - url_value == url_value.strip() - ), f"The url {url} with value '{url_value}' has whitespace values" + assert url_value == url_value.strip(), ( + f"The url {url} with value '{url_value}' has whitespace values" + ) seen_url_values.append(url_value)