-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Title: Refactor Python SDK Models to Standardize Pydantic Usage via ZitadelModel Base Class
Description:
This initiative will refactor the Python SDK's model classes, which already use Pydantic, to standardize their implementation, remove boilerplate, and leverage Pydantic V2 features more directly via a common ZitadelModel base class.
Problem:
The current Python models, while using Pydantic, contain generated boilerplate code:
- Custom
to_json,from_json,to_dict,from_dictmethods that partially replicate or deviate from standard Pydantic V2 functionality. - An
additional_propertiesfield and associated logic (__properties) that may not be necessary and complicates parsing/serialization. - Lack of a common base class for shared configuration and helper methods.
This leads to unnecessary code duplication and potential inconsistencies with idiomatic Pydantic V2 usage.
Impact:
Refactoring will result in:
- Slimmer, cleaner, and more maintainable Pydantic models.
- Consistent use of Pydantic V2's efficient
model_dump,model_dump_json,model_validate, andmodel_validate_jsonmethods. - Removal of potentially confusing or unnecessary custom serialization/deserialization logic and
additional_propertieshandling. - Improved developer experience.
Solution / Tasks:
1. Implement ZitadelModel Base Class:
Create a class ZitadelModel(BaseModel) that serves as the base for all SDK models. It should define the common Pydantic configuration and provide standardized serialization/deserialization methods.
Example zitadel_model.py:
from pydantic import BaseModel, ConfigDict
from typing import Any, Dict, Optional, TypeVar, Type
# Use typing_extensions for Self if Python < 3.11
from typing_extensions import Self
# Define a TypeVar for the class type
T = TypeVar('T', bound='ZitadelModel')
class ZitadelModel(BaseModel):
model_config = ConfigDict(
populate_by_name=True, # Allow using JSON field names (aliases)
validate_assignment=True, # Validate fields on assignment
protected_namespaces=(), # Standard Pydantic setting
extra='ignore', # Ignore unexpected fields in JSON input
# Use 'forbid' to raise an error instead
)
def to_zitadel_dict(self, by_alias: bool = True, exclude_unset: bool = True, exclude_none: bool = False) -> Dict[str, Any]:
"""Standard dictionary representation using Pydantic V2."""
return self.model_dump(
mode='python',
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none
)
def to_zitadel_json(self, by_alias: bool = True, exclude_unset: bool = True, exclude_none: bool = False) -> str:
"""Standard JSON representation using Pydantic V2."""
return self.model_dump_json(
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none
)
@classmethod
def from_zitadel_dict(cls: Type[T], obj: Optional[Dict[str, Any]]) -> Optional[T]:
"""Create an instance from a dictionary using Pydantic V2."""
if obj is None:
return None
return cls.model_validate(obj)
@classmethod
def from_zitadel_json(cls: Type[T], json_str: str) -> Optional[T]:
"""Create an instance from a JSON string using Pydantic V2."""
return cls.model_validate_json(json_str)
# Optional: Keep a consistent debug string representation if needed
def to_str(self) -> str:
"""Returns the string representation of the model using alias for debugging."""
import pprint
return pprint.pformat(self.model_dump(by_alias=True))2. Update OpenAPI Generator Templates for Python Models:
Modify the OpenAPI Generator templates for Python models to:
- Make generated models inherit from
ZitadelModel. - Define model fields using standard Python type hints (
Optional,List, etc.) and Pydantic'sFieldfor aliases (e.g.,user_id: Optional[str] = Field(default=None, alias="userId")). - Remove the
additional_propertiesfield and the__propertiesclass variable. - Remove the custom
to_json,from_json,to_dict,from_dictmethods from the generated models (they will be inherited). - Ensure enums are generated as standard Python
enum.Enumclasses, which Pydantic handles well.
Target structure for a generated model (e.g., user_service_user.py):
from __future__ import annotations
from typing import List, Optional
from pydantic import Field
# Assuming these imports point to other refactored ZitadelModel children or Enums
from .zitadel_model import ZitadelModel
from .user_service_details import UserServiceDetails
from .user_service_human_user import UserServiceHumanUser
from .user_service_machine_user import UserServiceMachineUser
from .user_service_user_state import UserServiceUserState # Assuming this is an Enum
class UserServiceUser(ZitadelModel):
"""
UserServiceUser (Refactored)
"""
user_id: Optional[str] = Field(default=None, alias="userId")
details: Optional[UserServiceDetails] = None
state: Optional[UserServiceUserState] = Field(default=UserServiceUserState.USER_STATE_UNSPECIFIED)
username: Optional[str] = None
login_names: Optional[List[str]] = Field(default=None, alias="loginNames")
preferred_login_name: Optional[str] = Field(default=None, alias="preferredLoginName")
human: Optional[UserServiceHumanUser] = None
machine: Optional[UserServiceMachineUser] = None
# No custom methods, no additional_properties, no __properties3. Update SDK Code to Use New Model Methods:
- Search the SDK codebase (e.g.,
api_client.py, service files) for any remaining calls to the old customto_json,from_json,to_dict,from_dictmethods. - Replace them with calls to the standardized methods inherited from
ZitadelModel(e.g.,instance.to_zitadel_json(),ModelClass.from_zitadel_json(data)). - Ensure the API client uses these methods correctly when preparing request bodies and parsing responses.
Expected Outcomes:
- Python SDK models are significantly leaner, inheriting configuration and core ser/des methods from
ZitadelModel. - Serialization and deserialization consistently use Pydantic V2's
model_dump*andmodel_validate*methods. - Boilerplate related to custom ser/des methods and
additional_propertiesis removed from models. - The SDK aligns better with idiomatic Pydantic V2 usage.
- Functional equivalence for JSON-based API interactions is preserved.
Additional Notes:
- Dependencies: Ensure
pydantic>=2.0is specified. - Null Handling: Review the desired behavior for omitting fields vs. including explicit
null. Theexclude_unset=True(default in example) andexclude_none=False(default in example) parameters in the base class methods control this. Adjust as needed. - Testing: Thorough testing of serialization/deserialization for various model types is essential.
extra='ignore'vs'forbid': Decide if unknown fields in incoming JSON should be silently ignored or raise a validation error.