Source code for pydotorg.domains.users.schemas

"""User domain Pydantic schemas."""

from __future__ import annotations

import datetime
from typing import Annotated
from uuid import UUID

from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator

from pydotorg.domains.users.models import EmailPrivacy, MembershipType, SearchVisibility, UserGroupType

MIN_PASSWORD_LENGTH = 8


[docs] class UserBase(BaseModel): """Base user schema with common fields.""" username: Annotated[str, Field(min_length=1, max_length=150)] email: EmailStr first_name: Annotated[str, Field(max_length=150)] = "" last_name: Annotated[str, Field(max_length=150)] = "" bio: str = "" search_visibility: SearchVisibility = SearchVisibility.PUBLIC email_privacy: EmailPrivacy = EmailPrivacy.PRIVATE public_profile: bool = True
[docs] class UserCreate(UserBase): """Schema for creating a new user.""" model_config = ConfigDict( json_schema_extra={ "example": { "username": "guido_van_rossum", "email": "guido@python.org", "password": "SecurePass123!", "first_name": "Guido", "last_name": "van Rossum", "bio": "Python creator and BDFL", "search_visibility": "public", "email_privacy": "private", "public_profile": True, } } ) password: Annotated[str, Field(min_length=8, max_length=255)] @field_validator("password") @classmethod def validate_password_strength(cls, v: str) -> str: if len(v) < MIN_PASSWORD_LENGTH: msg = f"Password must be at least {MIN_PASSWORD_LENGTH} characters long" raise ValueError(msg) return v
[docs] class UserUpdate(BaseModel): """Schema for updating an existing user.""" model_config = ConfigDict( json_schema_extra={ "example": { "first_name": "Guido", "last_name": "van Rossum", "bio": "Python creator, former BDFL, now retired from leadership but still contributing to Python development", "search_visibility": "public", "email_privacy": "private", } } ) email: EmailStr | None = None first_name: Annotated[str, Field(max_length=150)] | None = None last_name: Annotated[str, Field(max_length=150)] | None = None bio: str | None = None search_visibility: SearchVisibility | None = None email_privacy: EmailPrivacy | None = None public_profile: bool | None = None
[docs] class UserRead(UserBase): """Schema for reading user data.""" model_config = ConfigDict( from_attributes=True, json_schema_extra={ "example": { "id": "550e8400-e29b-41d4-a716-446655440000", "username": "guido_van_rossum", "email": "guido@python.org", "first_name": "Guido", "last_name": "van Rossum", "bio": "Python creator and BDFL", "search_visibility": "public", "email_privacy": "private", "public_profile": True, "is_active": True, "is_staff": False, "is_superuser": False, "date_joined": "2025-01-15T10:30:00Z", "last_login": "2025-11-29T08:45:00Z", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-11-20T14:22:00Z", } }, ) id: UUID is_active: bool is_staff: bool is_superuser: bool date_joined: datetime.datetime last_login: datetime.datetime | None created_at: datetime.datetime updated_at: datetime.datetime @property def full_name(self) -> str: """Get the user's full name.""" return f"{self.first_name} {self.last_name}".strip()
[docs] class UserPublic(BaseModel): """Public user schema with limited fields.""" id: UUID username: str first_name: str last_name: str bio: str model_config = ConfigDict(from_attributes=True) @property def full_name(self) -> str: """Get the user's full name.""" return f"{self.first_name} {self.last_name}".strip()
[docs] class MembershipBase(BaseModel): """Base membership schema.""" membership_type: MembershipType = MembershipType.BASIC legal_name: Annotated[str, Field(max_length=255)] = "" preferred_name: Annotated[str, Field(max_length=255)] = "" email_address: EmailStr | str = "" city: Annotated[str, Field(max_length=100)] = "" region: Annotated[str, Field(max_length=100)] = "" country: Annotated[str, Field(max_length=100)] = "" postal_code: Annotated[str, Field(max_length=20)] = "" psf_code_of_conduct: bool = False psf_announcements: bool = False votes: bool = False
[docs] class MembershipCreate(MembershipBase): """Schema for creating a new membership.""" user_id: UUID
[docs] class MembershipUpdate(BaseModel): """Schema for updating a membership.""" membership_type: MembershipType | None = None legal_name: Annotated[str, Field(max_length=255)] | None = None preferred_name: Annotated[str, Field(max_length=255)] | None = None email_address: EmailStr | str | None = None city: Annotated[str, Field(max_length=100)] | None = None region: Annotated[str, Field(max_length=100)] | None = None country: Annotated[str, Field(max_length=100)] | None = None postal_code: Annotated[str, Field(max_length=20)] | None = None psf_code_of_conduct: bool | None = None psf_announcements: bool | None = None votes: bool | None = None
[docs] class MembershipRead(MembershipBase): """Schema for reading membership data.""" id: UUID user_id: UUID last_vote_affirmation: datetime.date | None created_at: datetime.datetime updated_at: datetime.datetime model_config = ConfigDict(from_attributes=True)
[docs] class UserGroupBase(BaseModel): """Base user group schema.""" name: Annotated[str, Field(max_length=255)] location: Annotated[str, Field(max_length=255)] = "" url: Annotated[str, Field(max_length=500)] = "" url_type: UserGroupType = UserGroupType.OTHER approved: bool = False trusted: bool = False
[docs] class UserGroupCreate(UserGroupBase): """Schema for creating a new user group.""" start_date: datetime.date | None = None
[docs] class UserGroupUpdate(BaseModel): """Schema for updating a user group.""" name: Annotated[str, Field(max_length=255)] | None = None location: Annotated[str, Field(max_length=255)] | None = None url: Annotated[str, Field(max_length=500)] | None = None url_type: UserGroupType | None = None start_date: datetime.date | None = None approved: bool | None = None trusted: bool | None = None
[docs] class UserGroupRead(UserGroupBase): """Schema for reading user group data.""" id: UUID start_date: datetime.date | None created_at: datetime.datetime updated_at: datetime.datetime model_config = ConfigDict(from_attributes=True)
[docs] class APIKeyCreate(BaseModel): """Schema for creating a new API key.""" model_config = ConfigDict( json_schema_extra={ "example": { "name": "CI/CD Pipeline Key", "description": "API key for automated deployments", "expires_in_days": 365, } } ) name: Annotated[str, Field(min_length=1, max_length=100)] description: str = "" expires_in_days: Annotated[int, Field(ge=1, le=3650)] | None = None
[docs] class APIKeyRead(BaseModel): """Schema for reading API key data (without the actual key).""" model_config = ConfigDict(from_attributes=True) id: UUID user_id: UUID name: str key_prefix: str description: str is_active: bool expires_at: datetime.datetime | None last_used_at: datetime.datetime | None created_at: datetime.datetime updated_at: datetime.datetime
[docs] class APIKeyCreated(APIKeyRead): """Schema returned when creating a new API key (includes the raw key).""" model_config = ConfigDict( json_schema_extra={ "example": { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "550e8400-e29b-41d4-a716-446655440001", "name": "CI/CD Pipeline Key", "key": "pyorg_abc123xyz789...", "key_prefix": "pyorg_abc123", "description": "API key for automated deployments", "is_active": True, "expires_at": "2026-12-13T18:00:00Z", "last_used_at": None, "created_at": "2025-12-13T18:00:00Z", "updated_at": "2025-12-13T18:00:00Z", } } ) key: str