vikunja-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ """
2
+ Task Expansion Models for Vikunja API
3
+
4
+ Covers: models.TaskComment, models.TaskAttachment, models.TaskReminder
5
+ API endpoints: Part of task CRUD with expand=[comments,attachments,reminders]
6
+
7
+ These models represent the expanded data that can be fetched alongside tasks.
8
+ Use expand query parameter to include these in task responses.
9
+
10
+ Usage Examples:
11
+ # Fetch task with all expansions
12
+ task = client.get_task(2, expand=["comments", "attachments", "reminders"])
13
+
14
+ # Access expanded data
15
+ for comment in task.comments:
16
+ print(f"{comment.author.username}: {comment.comment}")
17
+ """
18
+
19
+ from pydantic import Field, RootModel
20
+ from datetime import datetime
21
+ from typing import Optional, Dict, List, Literal
22
+ from .base import VikunjaBaseModel, User
23
+
24
+
25
+ # ============================================================================
26
+ # Task Comment Model
27
+ # ============================================================================
28
+
29
+ class TaskComment(VikunjaBaseModel):
30
+ """
31
+ A comment on a task.
32
+
33
+ API endpoint: Part of task response with expand=comments
34
+
35
+ Comments support markdown formatting and reactions. Each comment has:
36
+ - id: Unique identifier
37
+ - comment: The comment text (markdown)
38
+ - author: User who wrote the comment
39
+ - created/updated: Timestamps
40
+ - reactions: Map of emoji reactions to users
41
+
42
+ Fields from API spec (models.TaskComment):
43
+ - id: Comment ID
44
+ - comment: Comment text content
45
+ - author: User object for comment author
46
+ - created: Creation timestamp
47
+ - updated: Last update timestamp
48
+ - reactions: Reaction map (emoji -> list of users)
49
+
50
+ Example from API response:
51
+ {
52
+ "id": 1,
53
+ "comment": "This needs fixing",
54
+ "author": {"id": 1, "username": "alice"},
55
+ "created": "2024-01-15T10:30:00Z",
56
+ "updated": "2024-01-15T10:30:00Z",
57
+ "reactions": {"👍": [{"id": 2, "username": "bob"}]}
58
+ }
59
+ """
60
+
61
+ # Core Identification (2 fields)
62
+ id: int = Field(..., description="Unique comment ID")
63
+ comment: str = Field(..., description="Comment text content (markdown)")
64
+
65
+ # Author and Timestamps (3 fields)
66
+ author: Optional[User] = Field(None, description="User who wrote this comment")
67
+ created: Optional[datetime] = Field(None, description="Creation timestamp")
68
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
69
+
70
+ # Reactions (1 field)
71
+ reactions: Optional[Dict[str, List[User]]] = Field(
72
+ None,
73
+ description="Map of emoji to list of users who reacted"
74
+ )
75
+
76
+
77
+ # ============================================================================
78
+ # Task Attachment Model
79
+ # ============================================================================
80
+
81
+ class TaskAttachment(VikunjaBaseModel):
82
+ """
83
+ A file attachment on a task.
84
+
85
+ API endpoint: Part of task response with expand=attachments
86
+
87
+ Attachments reference files stored in the Vikunja file system. Each has:
88
+ - id: Unique identifier
89
+ - file: File metadata (name, size, mime type)
90
+ - created: Upload timestamp
91
+ - created_by: User who uploaded it
92
+ - task_id: Parent task ID
93
+
94
+ Fields from API spec (models.TaskAttachment):
95
+ - id: Attachment ID
96
+ - file: File object with name, size, mime
97
+ - created: Upload timestamp
98
+ - created_by: User who uploaded
99
+ - task_id: Parent task ID
100
+
101
+ Example from API response:
102
+ {
103
+ "id": 1,
104
+ "file": {
105
+ "id": 100,
106
+ "name": "screenshot.png",
107
+ "size": 245760,
108
+ "mime": "image/png"
109
+ },
110
+ "created": "2024-01-15T10:30:00Z",
111
+ "created_by": {"id": 1, "username": "alice"},
112
+ "task_id": 5
113
+ }
114
+ """
115
+
116
+ # Core Identification (3 fields)
117
+ id: int = Field(..., description="Unique attachment ID")
118
+ task_id: int = Field(..., description="Parent task ID")
119
+ file: dict = Field(..., description="File metadata (name, size, mime)")
120
+
121
+ # Metadata (2 fields)
122
+ created: Optional[datetime] = Field(None, description="Upload timestamp")
123
+ created_by: Optional[User] = Field(None, description="User who uploaded this attachment")
124
+
125
+
126
+ # ============================================================================
127
+ # Task Reminder Model
128
+ # ============================================================================
129
+
130
+ # ReminderRelation is a RootModel[str] for the date field reference
131
+ ReminderRelation = RootModel[str]
132
+ """The date field a reminder is relative to.
133
+
134
+ API spec: models.ReminderRelation
135
+
136
+ Values:
137
+ - due_date: Relative to task's due date
138
+ - start_date: Relative to task's start date
139
+ - end_date: Relative to task's end date
140
+
141
+ Usage:
142
+ from models.task_expansion import ReminderRelation
143
+
144
+ # Create a reminder relative to due date
145
+ rel = ReminderRelation("due_date")
146
+ """
147
+
148
+ ReminderRelation.DUE_DATE = "due_date"
149
+ ReminderRelation.START_DATE = "start_date"
150
+ ReminderRelation.END_DATE = "end_date"
151
+
152
+
153
+ class TaskReminder(VikunjaBaseModel):
154
+ """
155
+ A reminder for a task.
156
+
157
+ API endpoint: Part of task response with expand=reminders
158
+
159
+ Reminders can be absolute (specific datetime) or relative to task dates.
160
+
161
+ Fields from API spec (models.TaskReminder):
162
+ - reminder: Absolute datetime string (ISO 8601)
163
+ - relative_to: Date field reference (due_date, start_date, end_date)
164
+ - relative_period: Seconds offset from relative_to date
165
+
166
+ Examples:
167
+ # Absolute reminder at specific time
168
+ {
169
+ "reminder": "2024-01-20T09:00:00Z",
170
+ "relative_to": null,
171
+ "relative_period": 0
172
+ }
173
+
174
+ # Relative reminder (30 minutes before due date)
175
+ {
176
+ "reminder": null,
177
+ "relative_to": "due_date",
178
+ "relative_period": -1800 # -30 minutes in seconds
179
+ }
180
+
181
+ Notes:
182
+ - If `reminder` is set, it's an absolute reminder (ignores relative fields)
183
+ - If `relative_to` is set, it's a relative reminder (uses relative_period offset)
184
+ - Negative relative_period means before the reference date
185
+ - Positive relative_period means after the reference date
186
+ """
187
+
188
+ # Core Reminder Definition (3 fields - one mode active at a time)
189
+ reminder: Optional[str] = Field(
190
+ None,
191
+ description="Absolute datetime (ISO 8601). If set, overrides relative fields."
192
+ )
193
+ relative_to: Optional[ReminderRelation] = Field(
194
+ None,
195
+ description="Date field reference (due_date, start_date, end_date)"
196
+ )
197
+ relative_period: Optional[int] = Field(
198
+ 0,
199
+ description="Seconds offset from relative_to date. Negative=before, positive=after"
200
+ )
201
+
202
+ @property
203
+ def is_absolute(self) -> bool:
204
+ """Check if this is an absolute reminder."""
205
+ return self.reminder is not None
206
+
207
+ @property
208
+ def is_relative(self) -> bool:
209
+ """Check if this is a relative reminder."""
210
+ return self.relative_to is not None
211
+
212
+ def get_description(self) -> str:
213
+ """Human-readable description of this reminder."""
214
+ if self.is_absolute:
215
+ return f"Absolute reminder at {self.reminder}"
216
+ elif self.is_relative:
217
+ offset = self.relative_period or 0
218
+ direction = "before" if offset <= 0 else "after"
219
+ abs_offset = abs(offset)
220
+ if abs_offset >= 3600:
221
+ time_str = f"{abs_offset // 3600} hours"
222
+ elif abs_offset >= 60:
223
+ time_str = f"{abs_offset // 60} minutes"
224
+ else:
225
+ time_str = f"{abs_offset} seconds"
226
+ return f"Relative reminder {time_str} {direction} {self.relative_to}"
227
+ return "Reminder with no timing specified"
228
+
229
+
230
+ # ============================================================================
231
+ # Response Models for Task Expansions
232
+ # ============================================================================
233
+
234
+ class TaskCommentsResponse(VikunjaBaseModel):
235
+ """Response containing task comments."""
236
+ success: bool = Field(True, description="Request succeeded")
237
+ comments: List[TaskComment] = Field(default_factory=list, description="List of comments")
238
+ error: Optional[str] = Field(None, description="Error message if failed")
239
+
240
+
241
+ class TaskAttachmentsResponse(VikunjaBaseModel):
242
+ """Response containing task attachments."""
243
+ success: bool = Field(True, description="Request succeeded")
244
+ attachments: List[TaskAttachment] = Field(default_factory=list, description="List of attachments")
245
+ error: Optional[str] = Field(None, description="Error message if failed")
246
+
247
+
248
+ class TaskRemindersResponse(VikunjaBaseModel):
249
+ """Response containing task reminders."""
250
+ success: bool = Field(True, description="Request succeeded")
251
+ reminders: List[TaskReminder] = Field(default_factory=list, description="List of reminders")
252
+ error: Optional[str] = Field(None, description="Error message if failed")