Files
pragma-stack/backend/app/schemas/organizations.py
Felipe Cardoso a8aa416ecb refactor(backend): migrate type checking from mypy to pyright
Replace mypy>=1.8.0 with pyright>=1.1.390. Remove all [tool.mypy] and
[tool.pydantic-mypy] sections from pyproject.toml and add
pyrightconfig.json (standard mode, SQLAlchemy false-positive rules
suppressed globally).

Fixes surfaced by pyright:
- Remove unreachable except AuthError clauses in login/login_oauth (same class as AuthenticationError)
- Fix Pydantic v2 list Field: min_items/max_items → min_length/max_length
- Split OAuthProviderConfig TypedDict into required + optional(email_url) inheritance
- Move JWTError/ExpiredSignatureError from lazy try-block imports to module level
- Add timezone-aware guard to UserSession.is_expired to match sibling models
- Fix is_active: bool → bool | None in three organization repo signatures
- Initialize search_filter = None before conditional block (possibly unbound fix)
- Add bool() casts to model is_expired and repo is_active/is_superuser returns
- Restructure except (JWTError, Exception) into separate except clauses
2026-02-28 19:12:40 +01:00

170 lines
4.9 KiB
Python

# app/schemas/organizations.py
import re
from datetime import datetime
from typing import Any
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.models.user_organization import OrganizationRole
# Organization Schemas
class OrganizationBase(BaseModel):
"""Base organization schema with common fields."""
name: str = Field(..., min_length=1, max_length=255)
slug: str | None = Field(None, min_length=1, max_length=255)
description: str | None = None
is_active: bool = True
settings: dict[str, Any] | None = {}
@field_validator("slug")
@classmethod
def validate_slug(cls, v: str | None) -> str | None:
"""Validate slug format: lowercase, alphanumeric, hyphens only."""
if v is None:
return v
if not re.match(r"^[a-z0-9-]+$", v):
raise ValueError(
"Slug must contain only lowercase letters, numbers, and hyphens"
)
if v.startswith("-") or v.endswith("-"):
raise ValueError("Slug cannot start or end with a hyphen")
if "--" in v:
raise ValueError("Slug cannot contain consecutive hyphens")
return v
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
"""Validate organization name."""
if not v or v.strip() == "":
raise ValueError("Organization name cannot be empty")
return v.strip()
class OrganizationCreate(OrganizationBase):
"""Schema for creating a new organization."""
name: str = Field(..., min_length=1, max_length=255)
slug: str = Field(..., min_length=1, max_length=255) # pyright: ignore[reportIncompatibleVariableOverride]
class OrganizationUpdate(BaseModel):
"""Schema for updating an organization."""
name: str | None = Field(None, min_length=1, max_length=255)
slug: str | None = Field(None, min_length=1, max_length=255)
description: str | None = None
is_active: bool | None = None
settings: dict[str, Any] | None = None
@field_validator("slug")
@classmethod
def validate_slug(cls, v: str | None) -> str | None:
"""Validate slug format."""
if v is None:
return v
if not re.match(r"^[a-z0-9-]+$", v):
raise ValueError(
"Slug must contain only lowercase letters, numbers, and hyphens"
)
if v.startswith("-") or v.endswith("-"):
raise ValueError("Slug cannot start or end with a hyphen")
if "--" in v:
raise ValueError("Slug cannot contain consecutive hyphens")
return v
@field_validator("name")
@classmethod
def validate_name(cls, v: str | None) -> str | None:
"""Validate organization name."""
if v is not None and (not v or v.strip() == ""):
raise ValueError("Organization name cannot be empty")
return v.strip() if v else v
class OrganizationResponse(OrganizationBase):
"""Schema for organization API responses."""
id: UUID
created_at: datetime
updated_at: datetime | None = None
member_count: int | None = 0
model_config = ConfigDict(from_attributes=True)
class OrganizationListResponse(BaseModel):
"""Schema for paginated organization list responses."""
organizations: list[OrganizationResponse]
total: int
page: int
page_size: int
pages: int
# User-Organization Relationship Schemas
class UserOrganizationBase(BaseModel):
"""Base schema for user-organization relationship."""
role: OrganizationRole = OrganizationRole.MEMBER
is_active: bool = True
custom_permissions: str | None = None
class UserOrganizationCreate(BaseModel):
"""Schema for adding a user to an organization."""
user_id: UUID
role: OrganizationRole = OrganizationRole.MEMBER
custom_permissions: str | None = None
class UserOrganizationUpdate(BaseModel):
"""Schema for updating user's role in an organization."""
role: OrganizationRole | None = None
is_active: bool | None = None
custom_permissions: str | None = None
class UserOrganizationResponse(BaseModel):
"""Schema for user-organization relationship responses."""
user_id: UUID
organization_id: UUID
role: OrganizationRole
is_active: bool
custom_permissions: str | None = None
created_at: datetime
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
class OrganizationMemberResponse(BaseModel):
"""Schema for organization member information."""
user_id: UUID
email: str
first_name: str
last_name: str | None = None
role: OrganizationRole
is_active: bool
joined_at: datetime
model_config = ConfigDict(from_attributes=True)
class OrganizationMemberListResponse(BaseModel):
"""Schema for paginated organization member list."""
members: list[OrganizationMemberResponse]
total: int
page: int
page_size: int
pages: int