Skip to content

Add strict return types to async Admin API methods (replace bare list with typed models) #712

Description

@clemlesne

Problem

All async Admin API methods (a_get_sessions, a_get_credentials, a_get_user, etc.) return bare list or dict without type parameters. Consumers must manually index into untyped dicts with string keys, losing all static analysis guarantees.

# Current — untyped, no IDE support, no static verification
sessions: list = await admin.a_get_sessions(user_id)
session_id = sessions[0].get("id")        # Any
ip = sessions[0].get("ipAddress")          # Any
start = sessions[0].get("start")           # Any — is this seconds or milliseconds?
clients = sessions[0].get("clients")       # Any — is this a list or dict?

Every consumer must read the Keycloak REST API docs to know:

  • Which keys exist
  • What types the values are (start is epoch milliseconds, not seconds)
  • Which fields are nullable
  • What the clients structure is (dict[str, str], not list)

This is error-prone and defeats the purpose of using a typed Python client.

What we're asking for

Return Pydantic models (or dataclasses/TypedDicts) from Admin API methods. Example for a_get_sessions:

class UserSessionRepresentation(BaseModel):
    """Keycloak UserSessionRepresentation.

    See: https://www.keycloak.org/docs-api/latest/rest-api/index.html#_usersessionrepresentation
    """
    id: str
    username: str
    userId: str
    ipAddress: str | None = None
    start: int  # Epoch milliseconds
    lastAccess: int  # Epoch milliseconds
    rememberMe: bool = False
    clients: dict[str, str] = Field(default_factory=dict)
    transientUser: bool = False

# After — typed, IDE autocomplete, static verification
async def a_get_sessions(self, user_id: str) -> list[UserSessionRepresentation]:
    ...

Concrete example: a_get_sessions

The Keycloak REST API spec defines UserSessionRepresentation with 9 fields:

Field Type Notes
id String Session UUID
username String
userId String User UUID
ipAddress String Nullable
start long Epoch milliseconds
lastAccess long Epoch milliseconds
rememberMe boolean
clients Map<String, String> Client ID to client name
transientUser boolean

Today a_get_sessions returns list (bare, no type param). The return annotation says -> list — not list[dict[str, Any]], not list[UserSessionRepresentation], just list.

Scope

This applies to all async methods that return Keycloak representations:

  • a_get_sessionslist[UserSessionRepresentation]
  • a_get_credentialslist[CredentialRepresentation]
  • a_get_userUserRepresentation
  • a_get_userslist[UserRepresentation]
  • a_get_realm_roleslist[RoleRepresentation]
  • etc.

Keycloak's REST API spec already defines all these representations. The Java source at core/src/main/java/org/keycloak/representations/idm/ has the canonical field definitions.

Benefits

  1. IDE autocompletesession.ipAddress instead of guessing session["ipAddress"] vs session["ip_address"]
  2. Static type checking — mypy/pyright catch typos and type mismatches at development time
  3. Self-documenting — no need to cross-reference Keycloak REST API docs for every field
  4. Refactoring safety — if Keycloak renames a field, type checkers flag all consumers

Implementation approach

The representations could be Pydantic models, dataclasses, or TypedDicts — whatever fits the library's style. The key requirement is that return types are specific (not list or dict), with field names and types matching the Keycloak REST API spec.

A backward-compatible approach would be to have the models extend dict (or use TypedDict) so existing session["id"] code continues to work while also supporting session.id.

Environment

  • python-keycloak (tested on 5.x, applies to all versions)
  • Python 3.12+
  • Keycloak 26.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions