{"id":21006206,"url":"https://github.com/derlin/mantelo","last_synced_at":"2025-05-15T01:33:46.458Z","repository":{"id":228372734,"uuid":"773760149","full_name":"derlin/mantelo","owner":"derlin","description":"✨ The full Keycloak Admin REST api wrapped in a tiny Python client ✨","archived":false,"fork":false,"pushed_at":"2025-01-17T19:07:51.000Z","size":336,"stargazers_count":25,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-11T19:42:02.463Z","etag":null,"topics":["http","keycloak","keycloak-api","python"],"latest_commit_sha":null,"homepage":"https://mantelo.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/derlin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-18T10:55:07.000Z","updated_at":"2025-05-05T09:01:42.000Z","dependencies_parsed_at":"2024-06-16T13:54:55.398Z","dependency_job_id":"4cd582ee-4427-412a-9780-62a245ad5e2f","html_url":"https://github.com/derlin/mantelo","commit_stats":null,"previous_names":["derlin/mantelo"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derlin%2Fmantelo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derlin%2Fmantelo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derlin%2Fmantelo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derlin%2Fmantelo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/derlin","download_url":"https://codeload.github.com/derlin/mantelo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254256750,"owners_count":22040351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","keycloak","keycloak-api","python"],"created_at":"2024-11-19T08:49:55.526Z","updated_at":"2025-05-15T01:33:46.452Z","avatar_url":"https://github.com/derlin.png","language":"Python","readme":"# mantelo: A Keycloak Admin REST Api Client for Python\n\n[![codecov](https://codecov.io/gh/derlin/mantelo/graph/badge.svg?token=5Y2O7B7342)](https://codecov.io/gh/derlin/mantelo)\n[![PyPI](https://img.shields.io/pypi/v/mantelo)](https://pypi.org/project/mantelo/)\n![PyPI Downloads](https://static.pepy.tech/badge/mantelo)\n[![Documentation Status](https://readthedocs.org/projects/mantelo/badge/?version=latest)](https://mantelo.readthedocs.io/en/latest/?badge=latest)\n---\n\n\u003cimg src=\"https://github.com/derlin/mantelo/blob/main/docs/_static/images/mantelo-text-900.png\" alt=\"Mantelo\" width=\"500\"\u003e\n\n**✨✨ MANTELO is a super small yet super powerful library for interacting with the Keycloak Admin API ✨✨**\n\n\u003e Mantelo [manˈtelo], from German \"*Mantel*\", from Late Latin \"*mantum*\" means \"*cloak*\" in Esperanto.\n\nIt stays fresh and complete because it does not hard-code or wrap any endpoint. Instead, it offers \na clean, object-oriented interface to the Keycloak RESTful API. Acting as a lightweight wrapper \naround the popular [requests](https://requests.readthedocs.io/en/latest/) library, mantelo takes \ncare of all the boring details for you - like authentication (tokens and refresh tokens), URL\nmanagement, serialization, and request processing\n\nAny endpoint your Keycloak supports, mantelo supports!\n\n\u003e [!TIP]\n\u003e Read more in the ${{\\color{Gold}\\huge\\text{full documentation}}}\\$ at https://mantelo.readthedocs.io/en/latest/\n\n---\n\n\u003c!-- TOC start (generated with https://github.com/derlin/bitdowntoc) --\u003e\n\n- [🚀 Why mantelo?](#-why-mantelo)\n- [🏁 Getting started](#-getting-started)\n- [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak)\n   * [Authenticating with username+password](#authenticating-with-usernamepassword)\n   * [Authenticating with client credentials (client ID + secret)](#authenticating-with-client-credentials-client-id--secret)\n   * [Other ways of authenticating](#other-ways-of-authenticating)\n- [📡 Making calls](#-making-calls)\n- [💀 Exceptions](#-exceptions)\n\n\u003c!-- TOC end --\u003e\n\n---\n\n## 🚀 Why mantelo?\n\nYou may ask why using mantelo instead of writing your own requests wrapper, or another library such\nas [python-keycloak](https://python-keycloak.readthedocs.io/en/latest/). Here are some\n(non-exhaustive) arguments to help you make the right choice:\n\n- mantelo only relies on 2 small packages (`requests` and `attrs`).\n- Contrary to other libraries such as python-keycloak, mantelo is always up-to-date and doesn't lack\n  any endpoints.\n- mantelo makes your code look nice and promotes a clean, object-oriented approach, avoiding\n  hard-coded URL strings scattered throughout your code.\n- mantelo abstracts away authentication (and refresh tokens), which is always tricky to get right.\n- mantelo gives you access to the exact URL that was called, and the `requests.Response` in case of\n  error, making debugging easier.\n- mantelo is flexible: you can tweak it easily if you need to.\n\n## 🏁 Getting started\n\nTo get started, install the package:\n\n```bash\npip install mantelo\n```\n\nNow, assuming you have a Keycloak Server running, what's left is to:\n\n1. authenticate, see [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak)\n2. make calls, see [📡 Making calls](#-making-calls)\n\nFor a quick test drive, use the [docker-compose.yml](docker-compose.yml) included in this repo and\nstart a Keycloak server locally using `docker compose up`. Open a Python REPL and type:\n\n```python\nfrom mantelo import KeycloakAdmin\n\nc = KeycloakAdmin.from_username_password(\n    server_url=\"http://localhost:9090\",\n    realm_name=\"master\",\n    client_id=\"admin-cli\",\n    username=\"admin\",\n    password=\"admin\",\n)\n\n# get the list of clients in realm \"master\"\nc.clients.get()\n\n# create a user\nc.users.post({\n    \"username\": \"test\",\n    \"enabled\": True,\n    \"credentials\": [{\"type\": \"password\", \"value\": \"test\"}],\n})\n# get the user id\nc.users.get(username=\"test\")[0][\"id\"]\n\n# ...\n```\n\n## 🔐 Authenticate to Keycloak\n\nTo authenticate to Keycloak, you can either use a username+password, or client credentials (client\nID+client secret, also known as *service account*).\n\nThe library takes care of fetching a token the first time you need it and keeping it fresh. By\ndefault, it tries to use the refresh token (if available) and always guarantees the token is valid\nfor the next 30 seconds.\n\n\u003e [!IMPORTANT]\n\u003e A client is meant to interact with a single realm, which can be different\n\u003e from the realm used for authentication.\n\n### Authenticating with username+password\n\nEnsure your user has the right to interact with the endpoint(s) you are interested in. In doubt or\nfor testing, you can either use the admin user (not recommended) or create a user and assign it the\n`realm-management:realm-admin` role (full access).\n\nThe default client `admin-cli` can always be used for connection.\n\nHere is how to connect to the default realm with the admin user and `admin-cli` client:\n\n```python\nfrom mantelo import KeycloakAdmin\n\nclient = KeycloakAdmin.from_username_password(\n    server_url=\"http://localhost:8080\", # base Keycloak URL\n    realm_name=\"master\",\n    # ↓↓ Authentication\n    client_id=\"admin-cli\",\n    username=\"admin\",\n    password=\"CHANGE-ME\", # TODO\n)\n```\n\nThis client will be able to make calls only to the `master` realm. If you want to authenticate to a\nrealm that is different from the one you want to query, use the argument `authentication_realm`:\n\n```python\nfrom mantelo import KeycloakAdmin\n\nclient = KeycloakAdmin.from_username_password(\n    server_url=\"http://localhost:8080\", # base Keycloak URL\n    realm_name=\"my-realm\", # realm for querying\n    # ↓↓ Authentication\n    authentication_realm_name=\"master\", # realm for authentication only\n    client_id=\"admin-cli\",\n    username=\"admin\",\n    password=\"CHANGE-ME\",\n)\n```\n\n### Authenticating with client credentials (client ID + secret)\n\n\u003e [!TIP]\n\u003e \n\u003e To authenticate via a client, the latter needs:\n\u003e - to have \"Client authentication\" enabled,\n\u003e - to support the `Service accounts roles` authentication flow,\n\u003e \n\u003e - to have one or more service account roles granting access to Admin endpoints.\n\u003e \n\u003e Go to your client's \"Credentials\" tab to find the client secret.\n\nHere is how to connect with a client:\n\n```python\nfrom mantelo import KeycloakAdmin\n\nclient = KeycloakAdmin.from_client_credentials(\n    server_url=\"http://localhost:8080\", # base Keycloak URL\n    realm_name=\"master\",\n    # ↓↓ Authentication\n    client_id=\"my-client-name\",\n    client_secret=\"59c3c211-2e56-4bb8-a07d-2961958f6185\",\n)\n```\n\nThis client will be able to make calls only to the `master` realm. If you want to authenticate to a\nrealm that is different from the one you want to query, use the argument `authentication_realm`:\n\n```python\nfrom mantelo import KeycloakAdmin\n\nclient = KeycloakAdmin.from_client_credentials(\n    server_url=\"http://localhost:8080\", # base Keycloak URL\n    realm_name=\"my-realm\", # realm for querying\n    # ↓↓ Authentication\n    authentication_realm_name=\"master\", # realm for authentication only\n    client_id=\"my-client-name\",\n    client_secret=\"59c3c211-2e56-4bb8-a07d-2961958f6185\",\n)\n```\n\n### Other ways of authenticating\n\nThe supported authentication methods should be enough. If you need more, a pull request or an issue\nis welcome! But just in case, here are some ways to make it more complicated 😉.\n\nTo create a `KeycloakAdmin`, you only need a method that returns a token. For example, you can use\nan existing token directly (not recommended, as tokens are short-lived):\n\n```python\nfrom mantelo.client import BearerAuth, KeycloakAdmin\n\nKeycloakAdmin(\n    server_url=\"http://localhost:8080\",\n    realm_name=\"master\",\n    auth=BearerAuth(lambda: \"my-token\"),\n)\n```\n\nIf you want to go further, you can create your own `Connection` class (or extend the\n`OpenidConnection`), and pass its `.token` method to the `BearerAuth`:\n\n```python\nfrom mantelo.client import BearerAuth, KeycloakAdmin\nfrom mantelo.connection import Connection\n\nclass MyConnection(Connection):\n    def token(self):\n        return \"\u003cdo-something-here\u003e\"\n\nconnection = MyConnection()\n\nKeycloakAdmin(\n    server_url=\"http://localhost:8080\",\n    realm_name=\"master\",\n    auth=BearerAuth(connection.token),\n)\n```\n\n## 📡 Making calls\n\nOnce you have configured how to authenticate to Keycloak, the rest is easy-peasy. mantelo **starts\nwith the URL `\u003cserver-url\u003e/admin/realms/\u003crealm-name\u003e`** and constructs the URL from there, depending\non how you call the client.\n\nThe return value is the HTTP response content, parsed from JSON. In case of error, an\n`HttpException` with access to the raw response is available (see [💀 Exceptions](#-exceptions)).\n\nQuery parameters can be passed as `kwargs` to `.get`, `.post`, etc. `.post`, `.put`, and `.delete`\ntake the payload as the first argument, or as the named argument `data`.\n\nHere are some examples of URL mapping (`c` is the `KeycloakAdmin` object):\n\n| call                                                    | URL                                                                           |\n| ------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `c.users.get()`                                         | `GET /admin/realms/{realm}/users`                                             |\n| `c.users.get(search=\"foo bar\")`                         | `GET /admin/realms/{realm}/users?search=foo+bar`                              |\n| `c.users.count.get()`                                   | `GET /admin/realms/{realm}/users/count`                                       |\n| `c.users(\"725209cd-9076-417b-a404-149a3fb8e35b\").get()` | `GET /admin/realms/{realm}/users/725209cd-9076-417b-a404-149a3fb8e35b`        |\n| `c.users.post({\"username\": ...})`                       | `POST /admin/realms/{realm}/users/725209cd-9076-417b-a404-149a3fb8e35b`       |\n| `c.users.post(foo=1, data={\"username\": ...})`           | `POST /admin/realms/{realm}/users/725209cd-9076-417b-a404-149a3fb8e35b?foo=1` |\n\n\n\u003e [!NOTE]\n\u003e More examples and explanations are available in the docs: https://mantelo.readthedocs.io/en/latest/02-making-calls.html\n\n\nHere are some examples:\n\n```python\n\u003e\u003e client.users.get()\n[{'id': '8d83ecda-766d-4382-8f3a-4c5ac1962961',\n  'username': 'constant',\n  'firstName': 'Jasper',\n  'lastName': 'Fforde',\n  'email': 'j@f.uk',\n  'emailVerified': True,\n  'createdTimestamp': 1710273159287,\n  'enabled': True,\n  'totp': False,\n  'disableableCredentialTypes': [],\n  'requiredActions': [],\n  'notBefore': 0,\n  'access': {'manageGroupMembership': True,\n   'view': True,\n   'mapRoles': True,\n   'impersonate': False,\n   'manage': True}}]\n\n\u003e\u003e client.users.count.get()\n2\n\n\u003e\u003e c.clients.get()\n...\nHttpException: (403, {'error': 'unknown_error', 'error_description': 'For more on this error consult the server log at the debug level.'}, 'http://localhost:9090/admin/realms/orwell/clients', \u003cResponse [403]\u003e)\n```\n\n## 💀 Exceptions\n\nIf the server returns a *401 Unauthorized* during the _authentication_ process, mantelo will raise\nan `AuthenticationException` with the `error` and `errorDescription` from Keycloak. All other HTTP\nexceptions are instances of `HttpException`, with some subclasses (`HttpNotFound`,\n`HttpClientError`, `HttpServerError`).\n\nHere are some examples:\n\n```python\n# Using an inexistant client\nAuthenticationException(\n    error='invalid_client',\n    error_description='Invalid client or Invalid client credentials'\n    response='\u003crequests.Response\u003e',\n)\n\n# Trying to access an endpoint without the proper permissions\nHttpException(\n    status_code=403,\n    json={'error': 'unknown_error', 'error_description': 'For more on this error consult the server log at the debug level.'},\n    url='http://localhost:9090/admin/realms/orwell/clients',\n    response='\u003crequests.Response\u003e',\n)\n```\n\n---\n\nFind more in the docs, https://mantelo.readthedocs.io/en/latest/, and don't forget to leave a ⭐ if you enjoy this library!\n","funding_links":[],"categories":["Integrations"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderlin%2Fmantelo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fderlin%2Fmantelo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderlin%2Fmantelo/lists"}