Skip to main content
Glama
models.py8.21 kB
"""Pydantic models for ClickUp entities.""" from datetime import datetime from enum import Enum from typing import Any, Dict, Optional, Union from typing import List as ListType from pydantic import BaseModel, field_validator class TaskPriority(Enum): """Task priority levels.""" URGENT = 1 HIGH = 2 NORMAL = 3 LOW = 4 class TaskStatus(BaseModel): """Task status model.""" id: str status: str color: str orderindex: int type: str class User(BaseModel): """User model.""" id: int username: Optional[str] = None email: Optional[str] = None color: Optional[str] = None initials: Optional[str] = None profile_picture: Optional[str] = None class WorkspaceMember(BaseModel): """Workspace member model.""" user: User status: str = "active" class CustomField(BaseModel): """Custom field model.""" id: str name: str type: str type_config: Dict[str, Any] = {} value: Optional[Union[str, int, float, bool, ListType[Any], Dict[str, Any]]] = None class Task(BaseModel): """Task model.""" id: str custom_id: Optional[str] = None name: str description: Optional[str] = None status: TaskStatus orderindex: Optional[float] = None date_created: Optional[datetime] = None date_updated: Optional[datetime] = None date_closed: Optional[datetime] = None archived: bool = False creator: User assignees: ListType[User] = [] tags: ListType[str] = [] parent: Optional[str] = None priority: Optional[TaskPriority] = None due_date: Optional[datetime] = None start_date: Optional[datetime] = None time_estimate: Optional[int] = None time_spent: Optional[int] = None custom_fields: ListType[CustomField] = [] list: Dict[str, Any] folder: Dict[str, Any] space: Dict[str, Any] url: str @field_validator("tags", mode="before") @classmethod def handle_tags_format(cls, v: Any) -> ListType[str]: """Handle both list and dict formats for tags from the API.""" if v is None: return [] if isinstance(v, list): # If it's already a list of strings, return as is if all(isinstance(tag, str) for tag in v): return v # If it's a list of dicts, extract the tag names return [tag.get("name", str(tag)) if isinstance(tag, dict) else str(tag) for tag in v] # If it's a dict (single tag), convert to list if isinstance(v, dict): return [v.get("name", str(v))] # Fallback: convert to string and wrap in list return [str(v)] @field_validator("priority", mode="before") @classmethod def handle_priority_format(cls, v: Any) -> Optional[TaskPriority]: """Handle both integer and dict formats for priority from the API.""" if v is None: return None if isinstance(v, int): # Direct integer value return TaskPriority(v) if isinstance(v, dict): # Try to map priority string to enum first if "priority" in v: priority_str = str(v["priority"]).lower() priority_map = { "urgent": TaskPriority.URGENT, "high": TaskPriority.HIGH, "normal": TaskPriority.NORMAL, "low": TaskPriority.LOW, } if priority_str in priority_map: return priority_map[priority_str] # Try 'id' field as fallback priority_id = v.get("id") if priority_id is not None and isinstance(priority_id, (int, str)): try: return TaskPriority(int(priority_id)) except (ValueError, KeyError): pass # Try to convert directly if it's a string number if isinstance(v, str) and v.isdigit(): return TaskPriority(int(v)) return None class List(BaseModel): """List model.""" id: str name: str orderindex: int content: Optional[str] = None # Added missing field status: Optional[Dict[str, Any]] = None priority: Optional[Dict[str, Any]] = None assignee: Optional[Dict[str, Any]] = None task_count: Optional[Union[str, int]] = None due_date: Optional[str] = None # API returns string timestamp start_date: Optional[str] = None # API returns string timestamp folder: Optional[Dict[str, Any]] = None space: Dict[str, Any] archived: bool = False override_statuses: Optional[bool] = False # Added missing field statuses: ListType[Dict[str, Any]] = [] # Added missing field permission_level: Optional[str] = None # Added missing field class Folder(BaseModel): """Folder model.""" id: str name: str orderindex: int override_statuses: bool = False hidden: bool = False space: Dict[str, Any] task_count: Optional[Union[str, int]] = None # API returns string, not int lists: ListType["List"] = [] archived: bool = False statuses: ListType[Dict[str, Any]] = [] # Added missing field permission_level: Optional[str] = None # Added missing field class Space(BaseModel): """Space model.""" id: str name: str private: bool = False color: Optional[str] = None avatar: Optional[str] = None admin_can_manage: Optional[bool] = False statuses: ListType[TaskStatus] = [] multiple_assignees: bool = True features: Dict[str, Any] = {} archived: bool = False class Comment(BaseModel): """Comment model.""" id: str comment_text: str user: User date: datetime resolved: bool = False assignee: Optional[User] = None class Workspace(BaseModel): """Workspace/Team model.""" id: str name: str color: str avatar: Optional[str] = None members: ListType[WorkspaceMember] = [] @field_validator("members", mode="before") @classmethod def handle_members_format(cls, v: Any) -> Any: """Handle both old and new member formats from the API.""" if not isinstance(v, list): return v processed_members = [] for member in v: if isinstance(member, dict): # If member already has the correct structure if "user" in member and isinstance(member["user"], dict): processed_members.append(member) # If member has the old flat structure elif "id" in member and "username" in member: processed_members.append( {"user": member, "status": member.get("status", "active")} ) else: # Unknown format, skip continue return processed_members class CreateTaskRequest(BaseModel): """Request model for creating a task.""" name: str description: Optional[str] = None assignees: Optional[ListType[int]] = None tags: Optional[ListType[str]] = None status: Optional[str] = None priority: Optional[int] = None due_date: Optional[int] = None # Unix timestamp in milliseconds due_date_time: Optional[bool] = False time_estimate: Optional[int] = None # Time in milliseconds start_date: Optional[int] = None # Unix timestamp in milliseconds start_date_time: Optional[bool] = False notify_all: Optional[bool] = True parent: Optional[str] = None links_to: Optional[str] = None custom_fields: Optional[ListType[Dict[str, Any]]] = None class UpdateTaskRequest(BaseModel): """Request model for updating a task.""" name: Optional[str] = None description: Optional[str] = None assignees: Optional[Dict[str, ListType[int]]] = None # {"add": [123], "rem": [456]} status: Optional[str] = None priority: Optional[int] = None due_date: Optional[int] = None due_date_time: Optional[bool] = None time_estimate: Optional[int] = None start_date: Optional[int] = None start_date_time: Optional[bool] = None archived: Optional[bool] = None parent: Optional[str] = None

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DiversioTeam/clickup-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server