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,138 @@
1
+ """
2
+ Link Sharing Models
3
+
4
+ Models for project link sharing functionality.
5
+ Based on Vikunja API v2.3.0 specification.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from datetime import datetime
11
+ from enum import IntEnum
12
+ from typing import Optional
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+
17
+ class SharingType(IntEnum):
18
+ """Types of link sharing for projects."""
19
+
20
+ UNDEFINED = 0
21
+ """Undefined sharing type"""
22
+
23
+ WITHOUT_PASSWORD = 1
24
+ """Shared without password protection"""
25
+
26
+ WITH_PASSWORD = 2
27
+ """Shared with password protection"""
28
+
29
+
30
+ class Permission(IntEnum):
31
+ """Permission levels for project sharing."""
32
+
33
+ READ_ONLY = 0
34
+ """Read-only access - can view but not modify"""
35
+
36
+ READ_WRITE = 1
37
+ """Write access - can create, update, delete own items"""
38
+
39
+ ADMIN = 2
40
+ """Admin access - full control including member management"""
41
+
42
+
43
+ class User(BaseModel):
44
+ """User representation for link sharing."""
45
+
46
+ model_config = ConfigDict(extra='ignore')
47
+
48
+ id: int = Field(..., description="The unique user ID")
49
+ username: str = Field(..., description="The username")
50
+ name: str = Field(..., description="The full name of the user")
51
+ email: Optional[str] = Field(None, description="The email address of the user")
52
+
53
+
54
+ class LinkSharing(BaseModel):
55
+ """Link sharing configuration for a project.
56
+
57
+ Represents a public share link that allows access to a project without
58
+ requiring user authentication. Can be password-protected.
59
+ """
60
+
61
+ model_config = ConfigDict(extra='ignore')
62
+
63
+ id: int = Field(..., description="The ID of the shared thing")
64
+ hash: str = Field(..., description="The public id to get this shared project")
65
+ name: str = Field(..., description="The name of this link share. All actions someone takes while being authenticated with that link will be attributed to this name.")
66
+ permission: Permission = Field(
67
+ Permission.READ_ONLY,
68
+ description="The permission this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin."
69
+ )
70
+ sharing_type: SharingType = Field(
71
+ SharingType.UNDEFINED,
72
+ description="The kind of this link. 0 = undefined, 1 = without password, 2 = with password."
73
+ )
74
+ shared_by: Optional[User] = Field(None, description="The user who shared this project")
75
+ created: datetime = Field(..., description="A timestamp when this project was shared. You cannot change this value.")
76
+ updated: datetime = Field(..., description="A timestamp when this share was last updated. You cannot change this value.")
77
+
78
+ # Password is write-only - can be set but not retrieved after creation
79
+ password: Optional[str] = Field(None, description="The password of this link share. You can only set it, not retrieve it after the link share has been created.")
80
+
81
+
82
+ class LinkSharingCreateRequest(BaseModel):
83
+ """Request to create a new link share for a project."""
84
+
85
+ model_config = ConfigDict(extra='ignore')
86
+
87
+ name: str = Field(..., max_length=255, description="The name of this link share")
88
+ password: Optional[str] = Field(None, description="Optional password for the link share. If provided, sharing_type will be set to WITH_PASSWORD.")
89
+ permission: Optional[Permission] = Field(
90
+ Permission.READ_ONLY,
91
+ description="The permission level for this share. 0 = Read only, 1 = Read & Write, 2 = Admin."
92
+ )
93
+
94
+
95
+ class LinkSharingUpdateRequest(BaseModel):
96
+ """Request to update an existing link share."""
97
+
98
+ model_config = ConfigDict(extra='ignore')
99
+
100
+ name: Optional[str] = Field(None, max_length=255, description="The name of this link share")
101
+ password: Optional[str] = Field(None, description="New password for the link share. Set to empty string to remove password protection.")
102
+ permission: Optional[Permission] = Field(
103
+ None,
104
+ description="The permission level for this share. 0 = Read only, 1 = Read & Write, 2 = Admin."
105
+ )
106
+
107
+
108
+ class LinkSharingListResponse(BaseModel):
109
+ """Response containing a list of link shares."""
110
+
111
+ model_config = ConfigDict(extra='ignore')
112
+
113
+ shares: list[LinkSharing] = Field(default_factory=list, description="List of link shares")
114
+
115
+
116
+ class LinkSharingGetResponse(BaseModel):
117
+ """Response containing a single link share."""
118
+
119
+ model_config = ConfigDict(extra='ignore')
120
+
121
+ share: LinkSharing = Field(..., description="The link share details (password not included)")
122
+
123
+
124
+ class LinkSharingCreateResponse(BaseModel):
125
+ """Response from creating a link share."""
126
+
127
+ model_config = ConfigDict(extra='ignore')
128
+
129
+ share: LinkSharing = Field(..., description="The created link share")
130
+ public_url: str = Field(..., description="The public URL for accessing this shared project")
131
+
132
+
133
+ class LinkSharingDeleteResponse(BaseModel):
134
+ """Response from deleting a link share."""
135
+
136
+ model_config = ConfigDict(extra='ignore')
137
+
138
+ success: bool = Field(..., description="Whether the deletion was successful")
@@ -0,0 +1,404 @@
1
+ """
2
+ CSV Import and Migration Models for Vikunja API
3
+
4
+ Covers: csv.* (CSV import), migration.Status, microsofttodo.*, todoist.*, trello.*
5
+
6
+ CSV Import Workflow:
7
+ 1. Upload CSV file → GET /import/csv/preview
8
+ 2. Get DetectionResult with suggested column mappings
9
+ 3. Submit ColumnMapping to configure import
10
+ 4. POST /import/csv to complete import
11
+
12
+ Migration Workflow:
13
+ 1. Authenticate with external service (Microsoft Todo, Todoist, Trello)
14
+ 2. POST /migration/{service} with auth code
15
+ 3. Monitor migration status via GET /migration/status
16
+
17
+ Supported Migration Sources:
18
+ - Microsoft Todo
19
+ - Todoist
20
+ - Trello
21
+ """
22
+
23
+ from pydantic import Field
24
+ from datetime import datetime
25
+ from typing import Optional, List, Any
26
+ from .base import VikunjaBaseModel
27
+
28
+
29
+ # ============================================================================
30
+ # CSV Task Attribute Enum (11 values)
31
+ # ============================================================================
32
+
33
+ TaskAttribute = str
34
+ """CSV column attribute mapping.
35
+
36
+ API spec: csv.TaskAttribute
37
+
38
+ Enum Values:
39
+ "title" - Task title (AttrTitle)
40
+ "description" - Task description (AttrDescription)
41
+ "due_date" - Due date field (AttrDueDate)
42
+ "start_date" - Start date field (AttrStartDate)
43
+ "end_date" - End date field (AttrEndDate)
44
+ "done" - Completion status (AttrDone)
45
+ "priority" - Priority level (AttrPriority)
46
+ "labels" - Labels/Tags (AttrLabels)
47
+ "project" - Project assignment (AttrProject)
48
+ "reminder" - Reminder date/time (AttrReminder)
49
+ "ignore" - Skip this column (AttrIgnore)
50
+
51
+ Usage:
52
+ from models.migration import TaskAttribute
53
+
54
+ # Map CSV columns to task attributes
55
+ mapping = ColumnMapping(
56
+ attribute=TaskAttribute("title"),
57
+ column_name="Task Name",
58
+ column_index=0
59
+ )
60
+ """
61
+
62
+ # Named constants for clarity
63
+ AttrTitle = "title"
64
+ AttrDescription = "description"
65
+ AttrDueDate = "due_date"
66
+ AttrStartDate = "start_date"
67
+ AttrEndDate = "end_date"
68
+ AttrDone = "done"
69
+ AttrPriority = "priority"
70
+ AttrLabels = "labels"
71
+ AttrProject = "project"
72
+ AttrReminder = "reminder"
73
+ AttrIgnore = "ignore"
74
+
75
+
76
+ # ============================================================================
77
+ # CSV Import Models
78
+ # ============================================================================
79
+
80
+ class ColumnMapping(VikunjaBaseModel):
81
+ """
82
+ Mapping between a CSV column and a task attribute.
83
+
84
+ API endpoint: Part of CSV import configuration
85
+
86
+ Maps a specific column from the uploaded CSV file to a Vikunja task attribute.
87
+
88
+ Fields from API spec (csv.ColumnMapping):
89
+ - attribute: The task attribute to map to
90
+ - column_index: Zero-based index of the column in CSV
91
+ - column_name: Human-readable name of the column
92
+
93
+ Example from API response:
94
+ {
95
+ "attribute": "title",
96
+ "column_index": 0,
97
+ "column_name": "Task Name"
98
+ }
99
+
100
+ Usage:
101
+ # Map first column to task title
102
+ mapping = ColumnMapping(
103
+ attribute=AttrTitle,
104
+ column_index=0,
105
+ column_name="Task Name"
106
+ )
107
+
108
+ # Ignore a column (e.g., notes that don't map to any field)
109
+ ignore_mapping = ColumnMapping(
110
+ attribute=AttrIgnore,
111
+ column_index=5,
112
+ column_name="Internal Notes"
113
+ )
114
+ """
115
+
116
+ # Core Mapping (3 fields)
117
+ attribute: TaskAttribute = Field(
118
+ ...,
119
+ description="Task attribute to map this column to"
120
+ )
121
+ column_index: int = Field(..., ge=0, description="Zero-based column index in CSV")
122
+ column_name: str = Field(..., description="Human-readable column name")
123
+
124
+
125
+ class DetectionResult(VikunjaBaseModel):
126
+ """
127
+ CSV file detection and analysis result.
128
+
129
+ API endpoint: GET /import/csv/preview
130
+
131
+ Returned when uploading a CSV file for import. Contains detected format
132
+ information and suggested column mappings.
133
+
134
+ Fields from API spec (csv.DetectionResult):
135
+ - columns: List of column names from first row
136
+ - delimiter: Detected delimiter (comma, semicolon, tab, etc.)
137
+ - quote_char: Detected quote character
138
+ - date_format: Detected date format string
139
+ - preview_rows: Sample data rows (typically 5-10 rows)
140
+ - suggested_mapping: Auto-detected column-to-attribute mappings
141
+
142
+ Example from API response:
143
+ {
144
+ "columns": ["Task Name", "Description", "Due Date", "Done"],
145
+ "delimiter": ",",
146
+ "quote_char": "\"",
147
+ "date_format": "%Y-%m-%d",
148
+ "preview_rows": [
149
+ ["Buy milk", "Groceries", "2024-01-20", "false"],
150
+ ["Write report", "Work", "2024-01-25", "true"]
151
+ ],
152
+ "suggested_mapping": [
153
+ {"attribute": "title", "column_index": 0, "column_name": "Task Name"},
154
+ {"attribute": "description", "column_index": 1, "column_name": "Description"},
155
+ {"attribute": "due_date", "column_index": 2, "column_name": "Due Date"},
156
+ {"attribute": "done", "column_index": 3, "column_name": "Done"}
157
+ ]
158
+ }
159
+
160
+ Usage:
161
+ # Get detection result after uploading CSV
162
+ detection = DetectionResult.model_validate(api_response)
163
+
164
+ # Use suggested mappings or customize
165
+ for mapping in detection.suggested_mapping:
166
+ print(f"Column '{mapping.column_name}' → {mapping.attribute}")
167
+ """
168
+
169
+ # Format Detection (4 fields)
170
+ columns: Optional[List[str]] = Field(
171
+ None,
172
+ description="Column names from first row of CSV"
173
+ )
174
+ delimiter: Optional[str] = Field(None, description="Detected field delimiter")
175
+ quote_char: Optional[str] = Field(None, description="Detected quote character")
176
+ date_format: Optional[str] = Field(None, description="Detected date format string")
177
+
178
+ # Preview Data (1 field)
179
+ preview_rows: Optional[List[List[str]]] = Field(
180
+ None,
181
+ description="Sample data rows from CSV"
182
+ )
183
+
184
+ # Suggested Mappings (1 field)
185
+ suggested_mapping: Optional[List[ColumnMapping]] = Field(
186
+ None,
187
+ description="Auto-detected column-to-attribute mappings"
188
+ )
189
+
190
+
191
+ class PreviewTask(VikunjaBaseModel):
192
+ """
193
+ A single task preview from CSV import.
194
+
195
+ API endpoint: Part of PreviewResult
196
+
197
+ Represents how a row from the CSV file will be imported as a task.
198
+
199
+ Fields from API spec (csv.PreviewTask):
200
+ - title: Task title
201
+ - description: Task description
202
+ - due_date: Due date string
203
+ - start_date: Start date string
204
+ - end_date: End date string
205
+ - done: Completion status
206
+ - priority: Priority level
207
+ - labels: List of label names
208
+ - project: Project name
209
+
210
+ Example from API response:
211
+ {
212
+ "title": "Buy milk",
213
+ "description": "Groceries",
214
+ "due_date": "2024-01-20",
215
+ "start_date": null,
216
+ "end_date": null,
217
+ "done": false,
218
+ "priority": 0,
219
+ "labels": ["shopping"],
220
+ "project": "Personal"
221
+ }
222
+ """
223
+
224
+ # Core Task Fields (3 fields)
225
+ title: Optional[str] = Field(None, description="Task title")
226
+ description: Optional[str] = Field(None, description="Task description")
227
+ done: Optional[bool] = Field(None, description="Completion status")
228
+
229
+ # Date Fields (3 fields)
230
+ due_date: Optional[str] = Field(None, description="Due date string")
231
+ start_date: Optional[str] = Field(None, description="Start date string")
232
+ end_date: Optional[str] = Field(None, description="End date string")
233
+
234
+ # Additional Fields (3 fields)
235
+ priority: Optional[int] = Field(None, ge=0, description="Priority level")
236
+ labels: Optional[List[str]] = Field(None, description="List of label names")
237
+ project: Optional[str] = Field(None, description="Project name")
238
+
239
+
240
+ class PreviewResult(VikunjaBaseModel):
241
+ """
242
+ CSV import preview result.
243
+
244
+ API endpoint: GET /import/csv/preview
245
+
246
+ Shows how the uploaded CSV file will be imported, with all tasks parsed.
247
+
248
+ Fields from API spec (csv.PreviewResult):
249
+ - tasks: List of parsed tasks from CSV rows
250
+ - total_rows: Total number of rows in CSV
251
+
252
+ Example from API response:
253
+ {
254
+ "tasks": [
255
+ {"title": "Buy milk", "done": false, ...},
256
+ {"title": "Write report", "done": true, ...}
257
+ ],
258
+ "total_rows": 25
259
+ }
260
+
261
+ Usage:
262
+ # Get preview after uploading CSV and configuring mappings
263
+ preview = PreviewResult.model_validate(api_response)
264
+
265
+ print(f"Importing {preview.total_rows} tasks:")
266
+ for task in preview.tasks:
267
+ status = "✓" if task.done else "○"
268
+ print(f" {status} {task.title}")
269
+ """
270
+
271
+ # Preview Data (2 fields)
272
+ tasks: Optional[List[PreviewTask]] = Field(
273
+ None,
274
+ description="List of parsed tasks from CSV rows"
275
+ )
276
+ total_rows: Optional[int] = Field(None, ge=0, description="Total number of rows in CSV")
277
+
278
+
279
+ # ============================================================================
280
+ # Migration Status Model
281
+ # ============================================================================
282
+
283
+ class MigrationStatus(VikunjaBaseModel):
284
+ """
285
+ Status of an ongoing migration from external service.
286
+
287
+ API endpoint: GET /migration/status
288
+
289
+ Tracks the progress of a migration job (Microsoft Todo, Todoist, Trello).
290
+
291
+ Fields from API spec (migration.Status):
292
+ - id: Unique migration job identifier
293
+ - migrator_name: Name of the migration source (e.g., "todoist")
294
+ - started_at: When migration began
295
+ - finished_at: When migration completed (null if still running)
296
+
297
+ Example from API response:
298
+ {
299
+ "id": 42,
300
+ "migrator_name": "todoist",
301
+ "started_at": "2024-01-15T10:30:00Z",
302
+ "finished_at": "2024-01-15T10:32:15Z"
303
+ }
304
+
305
+ Usage:
306
+ # Check migration status
307
+ status = MigrationStatus.model_validate(api_response)
308
+
309
+ if status.finished_at:
310
+ print(f"Migration completed at {status.finished_at}")
311
+ else:
312
+ print(f"Migration in progress since {status.started_at}")
313
+ """
314
+
315
+ # Core Identification (2 fields)
316
+ id: Optional[int] = Field(None, description="Unique migration job ID")
317
+ migrator_name: Optional[str] = Field(
318
+ None,
319
+ description="Name of migration source (e.g., 'todoist', 'microsofttodo', 'trello')"
320
+ )
321
+
322
+ # Timing (2 fields)
323
+ started_at: Optional[datetime] = Field(None, description="Migration start timestamp")
324
+ finished_at: Optional[datetime] = Field(None, description="Migration completion timestamp")
325
+
326
+
327
+ # ============================================================================
328
+ # External Service Migration Models
329
+ # ============================================================================
330
+
331
+ class MicrosoftTodoMigrationRequest(VikunjaBaseModel):
332
+ """
333
+ Request body for Microsoft Todo migration.
334
+
335
+ API endpoint: POST /migration/microsofttodo
336
+
337
+ Required fields: code (OAuth authorization code)
338
+
339
+ UNKNOWN: Full request structure requires API testing - only 'code' field confirmed
340
+
341
+ Example:
342
+ req = MicrosoftTodoMigrationRequest(code="auth_code_from_oauth")
343
+ """
344
+ # Confirmed field from API spec
345
+ code: Optional[str] = Field(None, description="OAuth authorization code from Microsoft Todo")
346
+
347
+
348
+ class TodoistMigrationRequest(VikunjaBaseModel):
349
+ """
350
+ Request body for Todoist migration.
351
+
352
+ API endpoint: POST /migration/todoist
353
+
354
+ Required fields: code (OAuth authorization code)
355
+
356
+ UNKNOWN: Full request structure requires API testing - only 'code' field confirmed
357
+
358
+ Example:
359
+ req = TodoistMigrationRequest(code="auth_code_from_oauth")
360
+ """
361
+ # Confirmed field from API spec
362
+ code: Optional[str] = Field(None, description="OAuth authorization code from Todoist")
363
+
364
+
365
+ class TrelloMigrationRequest(VikunjaBaseModel):
366
+ """
367
+ Request body for Trello migration.
368
+
369
+ API endpoint: POST /migration/trello
370
+
371
+ Required fields: code (OAuth authorization code)
372
+
373
+ UNKNOWN: Full request structure requires API testing - only 'code' field confirmed
374
+
375
+ Example:
376
+ req = TrelloMigrationRequest(code="auth_code_from_oauth")
377
+ """
378
+ # Confirmed field from API spec
379
+ code: Optional[str] = Field(None, description="OAuth authorization code from Trello")
380
+
381
+
382
+ # ============================================================================
383
+ # Response Models
384
+ # ============================================================================
385
+
386
+ class CSVImportResponse(VikunjaBaseModel):
387
+ """Response for CSV import operations."""
388
+ success: bool = Field(True, description="Request succeeded")
389
+ imported_tasks: Optional[int] = Field(None, description="Number of tasks imported")
390
+ error: Optional[str] = Field(None, description="Error message if failed")
391
+
392
+
393
+ class MigrationResponse(VikunjaBaseModel):
394
+ """Response for migration initiation."""
395
+ success: bool = Field(True, description="Request succeeded")
396
+ migration_id: Optional[int] = Field(None, description="Migration job ID")
397
+ error: Optional[str] = Field(None, description="Error message if failed")
398
+
399
+
400
+ class MigrationStatusResponse(VikunjaBaseModel):
401
+ """Response for migration status check."""
402
+ success: bool = Field(True, description="Request succeeded")
403
+ status: Optional[MigrationStatus] = Field(None, description="Migration status")
404
+ error: Optional[str] = Field(None, description="Error message if failed")
@@ -0,0 +1,74 @@
1
+ """Phase 6: Medium Priority Models (Bucket, Reaction, Subscription)"""
2
+ from pydantic import BaseModel, Field, ConfigDict
3
+ from enum import IntEnum
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ from vikunja_python.core.models.base import User
7
+
8
+ class ReactionKind(IntEnum):
9
+ """Reaction kinds as defined by Vikunja API."""
10
+ HEART = 1
11
+ THUMBS_UP = 2
12
+ FACE_WITH_TEARS_OF_JOY = 3
13
+
14
+ class Reaction(BaseModel):
15
+ """Reaction on a task or comment."""
16
+ model_config = ConfigDict(extra='ignore')
17
+ value: str = Field(..., description="The emoji reaction value (e.g., Unicode emoji)")
18
+ user: Optional[User] = Field(None, description="The user who reacted")
19
+ created: datetime = Field(..., description="Timestamp of the reaction")
20
+
21
+ class ReactionCreateRequest(BaseModel):
22
+ """Request to create a new reaction."""
23
+ model_config = ConfigDict(extra='ignore')
24
+ value: str = Field(..., description="The emoji reaction value")
25
+
26
+ class ReactionMapEntry(BaseModel):
27
+ """A single entry in a reaction map."""
28
+ value: str
29
+ count: int
30
+
31
+ class SubscriptionType(IntEnum):
32
+ """Types of subscriptions available."""
33
+ TASK = 1
34
+ PROJECT = 2
35
+
36
+ class Subscription(BaseModel):
37
+ """A subscription to a task or project."""
38
+ model_config = ConfigDict(extra='ignore')
39
+ id: int
40
+ type: SubscriptionType = Field(..., description="The type of the subscription")
41
+ entity_id: int = Field(..., description="The ID of the subscribed task or project")
42
+ created: datetime = Field(..., description="Timestamp of creation")
43
+
44
+ class SubscriptionCreateRequest(BaseModel):
45
+ """Request to create a new subscription."""
46
+ model_config = ConfigDict(extra='ignore')
47
+ type: SubscriptionType = Field(..., description="The type of the subscription")
48
+ entity_id: int = Field(..., description="The ID of the subscribed task or project")
49
+
50
+ class Bucket(BaseModel):
51
+ """A bucket (column) in a Kanban board."""
52
+ model_config = ConfigDict(extra='ignore')
53
+ id: int
54
+ title: str = Field(..., description="The title of the bucket")
55
+ color: str | None = Field(None, description="Hex color of the bucket")
56
+ position: int = Field(..., description="Position in the board")
57
+
58
+ class BucketCreateRequest(BaseModel):
59
+ """Request to create a new bucket."""
60
+ model_config = ConfigDict(extra='ignore')
61
+ title: str = Field(..., description="The title of the bucket")
62
+ color: str | None = Field(None, description="Hex color of the bucket")
63
+
64
+ class BucketUpdateRequest(BaseModel):
65
+ """Request to update an existing bucket."""
66
+ model_config = ConfigDict(extra='ignore')
67
+ title: str | None = Field(None, description="The updated title")
68
+ color: str | None = Field(None, description="The updated color")
69
+ position: int | None = Field(None, description="The updated position")
70
+
71
+ class BucketMoveRequest(BaseModel):
72
+ """Request to move a bucket."""
73
+ model_config = ConfigDict(extra='ignore')
74
+ new_position: int = Field(..., description="The new position of the bucket")