google-api-client-wrapper 1.0.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.
Files changed (39) hide show
  1. google_api_client_wrapper-1.0.0.dist-info/METADATA +103 -0
  2. google_api_client_wrapper-1.0.0.dist-info/RECORD +39 -0
  3. google_api_client_wrapper-1.0.0.dist-info/WHEEL +5 -0
  4. google_api_client_wrapper-1.0.0.dist-info/licenses/LICENSE +21 -0
  5. google_api_client_wrapper-1.0.0.dist-info/top_level.txt +1 -0
  6. google_client/__init__.py +6 -0
  7. google_client/services/__init__.py +13 -0
  8. google_client/services/calendar/__init__.py +14 -0
  9. google_client/services/calendar/api_service.py +454 -0
  10. google_client/services/calendar/constants.py +48 -0
  11. google_client/services/calendar/exceptions.py +35 -0
  12. google_client/services/calendar/query_builder.py +314 -0
  13. google_client/services/calendar/types.py +403 -0
  14. google_client/services/calendar/utils.py +338 -0
  15. google_client/services/drive/__init__.py +13 -0
  16. google_client/services/drive/api_service.py +1133 -0
  17. google_client/services/drive/constants.py +37 -0
  18. google_client/services/drive/exceptions.py +60 -0
  19. google_client/services/drive/query_builder.py +385 -0
  20. google_client/services/drive/types.py +242 -0
  21. google_client/services/drive/utils.py +392 -0
  22. google_client/services/gmail/__init__.py +16 -0
  23. google_client/services/gmail/api_service.py +715 -0
  24. google_client/services/gmail/constants.py +6 -0
  25. google_client/services/gmail/exceptions.py +45 -0
  26. google_client/services/gmail/query_builder.py +408 -0
  27. google_client/services/gmail/types.py +285 -0
  28. google_client/services/gmail/utils.py +426 -0
  29. google_client/services/tasks/__init__.py +12 -0
  30. google_client/services/tasks/api_service.py +561 -0
  31. google_client/services/tasks/constants.py +32 -0
  32. google_client/services/tasks/exceptions.py +35 -0
  33. google_client/services/tasks/query_builder.py +324 -0
  34. google_client/services/tasks/types.py +156 -0
  35. google_client/services/tasks/utils.py +224 -0
  36. google_client/user_client.py +208 -0
  37. google_client/utils/__init__.py +0 -0
  38. google_client/utils/datetime.py +144 -0
  39. google_client/utils/validation.py +71 -0
@@ -0,0 +1,561 @@
1
+ from datetime import datetime, date
2
+ from typing import Optional, List, Any, Dict
3
+
4
+ from googleapiclient.errors import HttpError
5
+
6
+ from .types import Task, TaskList
7
+ from . import utils
8
+ from .constants import (
9
+ DEFAULT_MAX_RESULTS, MAX_RESULTS_LIMIT, DEFAULT_TASK_LIST_ID,
10
+ TASK_STATUS_COMPLETED, TASK_STATUS_NEEDS_ACTION
11
+ )
12
+ from .exceptions import (
13
+ TasksError, TasksPermissionError, TasksNotFoundError,
14
+ InvalidTaskDataError, TaskMoveError
15
+ )
16
+
17
+
18
+ class TasksApiService:
19
+ """
20
+ Service layer for Tasks API operations.
21
+ Contains all Tasks API functionality that was removed from dataclasses.
22
+ """
23
+
24
+ def __init__(self, service: Any):
25
+ """
26
+ Initialize Tasks service.
27
+
28
+ Args:
29
+ service: The Tasks API service instance
30
+ """
31
+ self._service = service
32
+
33
+ def query(self):
34
+ """
35
+ Create a new TaskQueryBuilder for building complex task queries with a fluent API.
36
+
37
+ Returns:
38
+ TaskQueryBuilder instance for method chaining
39
+
40
+ Example:
41
+ tasks = (user.tasks.query()
42
+ .limit(50)
43
+ .due_today()
44
+ .show_completed(False)
45
+ .in_task_list("my_list_id")
46
+ .execute())
47
+ """
48
+ from .query_builder import TaskQueryBuilder
49
+ return TaskQueryBuilder(self)
50
+
51
+ # Task Operations
52
+ def list_tasks(
53
+ self,
54
+ task_list_id: str = DEFAULT_TASK_LIST_ID,
55
+ max_results: Optional[int] = DEFAULT_MAX_RESULTS,
56
+ completed_min: Optional[datetime] = None,
57
+ completed_max: Optional[datetime] = None,
58
+ due_min: Optional[datetime] = None,
59
+ due_max: Optional[datetime] = None,
60
+ show_completed: Optional[bool] = None,
61
+ show_hidden: Optional[bool] = None
62
+ ) -> List[Task]:
63
+ """
64
+ Fetches a list of tasks from Google Tasks with optional filtering.
65
+
66
+ Args:
67
+ task_list_id: Task list identifier (default: '@default').
68
+ max_results: Maximum number of tasks to retrieve.
69
+ completed_min: Lower bound for a task's completion date (RFC 3339).
70
+ completed_max: Upper bound for a task's completion date (RFC 3339).
71
+ due_min: Lower bound for a task's due date (RFC 3339).
72
+ due_max: Upper bound for a task's due date (RFC 3339).
73
+ show_completed: Flag indicating whether completed tasks are returned.
74
+ show_hidden: Flag indicating whether hidden tasks are returned.
75
+
76
+ Returns:
77
+ A list of Task objects representing the tasks found.
78
+ """
79
+ # Input validation
80
+ if max_results and (max_results < 1 or max_results > MAX_RESULTS_LIMIT):
81
+ raise ValueError(f"max_results must be between 1 and {MAX_RESULTS_LIMIT}")
82
+
83
+
84
+ try:
85
+ # Build request parameters
86
+ request_params = {
87
+ 'tasklist': task_list_id,
88
+ 'maxResults': max_results
89
+ }
90
+
91
+ # Add optional filters
92
+ if completed_min:
93
+ request_params['completedMin'] = completed_min.isoformat() + 'Z'
94
+ if completed_max:
95
+ request_params['completedMax'] = completed_max.isoformat() + 'Z'
96
+ if due_min:
97
+ request_params['dueMin'] = due_min.isoformat() + 'Z'
98
+ if due_max:
99
+ request_params['dueMax'] = due_max.isoformat() + 'Z'
100
+ if show_completed is not None:
101
+ request_params['showCompleted'] = show_completed
102
+ if show_hidden is not None:
103
+ request_params['showHidden'] = show_hidden
104
+
105
+ # Make API call
106
+ result = self._service.tasks().list(**request_params).execute()
107
+ tasks_data = result.get('items', [])
108
+
109
+
110
+ # Parse tasks
111
+ tasks = []
112
+ for task_data in tasks_data:
113
+ try:
114
+ tasks.append(utils.from_google_task(task_data, task_list_id))
115
+ except Exception as e:
116
+ pass
117
+
118
+ return tasks
119
+
120
+ except HttpError as e:
121
+ if e.resp.status == 403:
122
+ raise TasksPermissionError(f"Permission denied: {e}")
123
+ elif e.resp.status == 404:
124
+ raise TasksNotFoundError(f"Task list not found: {task_list_id}")
125
+ else:
126
+ raise TasksError(f"Tasks API error listing tasks: {e}")
127
+ except Exception as e:
128
+ raise TasksError(f"Unexpected error listing tasks: {e}")
129
+
130
+ def get_task(self, task_id: str, task_list_id: str = DEFAULT_TASK_LIST_ID) -> Task:
131
+ """
132
+ Retrieves a specific task from Google Tasks using its unique identifier.
133
+
134
+ Args:
135
+ task_list_id: The task list identifier containing the task.
136
+ task_id: The unique identifier of the task to be retrieved.
137
+
138
+ Returns:
139
+ A Task object representing the task with the specified ID.
140
+ """
141
+
142
+ try:
143
+ task_data = self._service.tasks().get(
144
+ tasklist=task_list_id,
145
+ task=task_id
146
+ ).execute()
147
+
148
+ return utils.from_google_task(task_data, task_list_id)
149
+
150
+ except HttpError as e:
151
+ if e.resp.status == 404:
152
+ raise TasksNotFoundError(f"Task not found: {task_id}")
153
+ elif e.resp.status == 403:
154
+ raise TasksPermissionError(f"Permission denied accessing task: {e}")
155
+ else:
156
+ raise TasksError(f"Tasks API error getting task {task_id}: {e}")
157
+ except Exception as e:
158
+ raise TasksError(f"Unexpected error getting task: {e}")
159
+
160
+ def create_task(
161
+ self,
162
+ title: str,
163
+ task_list_id: str = DEFAULT_TASK_LIST_ID,
164
+ notes: Optional[str] = None,
165
+ due: Optional[date] = None,
166
+ parent: Optional[str] = None,
167
+ position: Optional[str] = None
168
+ ) -> Task:
169
+ """
170
+ Creates a new task.
171
+
172
+ Args:
173
+ title: The title of the task.
174
+ task_list_id: Task list identifier (default: '@default').
175
+ notes: Notes describing the task.
176
+ due: Due date of the task.
177
+ parent: Parent task identifier.
178
+ position: Position in the task list.
179
+
180
+ Returns:
181
+ A Task object representing the created task.
182
+ """
183
+
184
+ try:
185
+ # Create task body using utils
186
+ task_body = utils.create_task_body(
187
+ title=title,
188
+ notes=notes,
189
+ due=due,
190
+ parent=parent,
191
+ position=position
192
+ )
193
+
194
+ # Make API call
195
+ created_task = self._service.tasks().insert(
196
+ tasklist=task_list_id,
197
+ body=task_body
198
+ ).execute()
199
+
200
+ task = utils.from_google_task(created_task, task_list_id)
201
+ return task
202
+
203
+ except HttpError as e:
204
+ if e.resp.status == 403:
205
+ raise TasksPermissionError(f"Permission denied creating task: {e}")
206
+ elif e.resp.status == 404:
207
+ raise TasksNotFoundError(f"Task list not found: {task_list_id}")
208
+ else:
209
+ raise TasksError(f"Tasks API error creating task: {e}")
210
+ except ValueError as e:
211
+ raise InvalidTaskDataError(f"Invalid task data: {e}")
212
+ except Exception as e:
213
+ raise TasksError(f"Unexpected error creating task: {e}")
214
+
215
+ def update_task(self, task: Task, task_list_id: str = DEFAULT_TASK_LIST_ID) -> Task:
216
+ """
217
+ Updates an existing task.
218
+
219
+ Args:
220
+ task: The task to update.
221
+ task_list_id: Task list identifier containing the task.
222
+
223
+ Returns:
224
+ A Task object representing the updated task.
225
+ """
226
+
227
+ try:
228
+ # Build update body
229
+ task_body = task.to_dict()
230
+
231
+ # Make API call
232
+ updated_task = self._service.tasks().update(
233
+ tasklist=task_list_id,
234
+ task=task.task_id,
235
+ body=task_body
236
+ ).execute()
237
+
238
+ task = utils.from_google_task(updated_task, task_list_id)
239
+ return task
240
+
241
+ except HttpError as e:
242
+ if e.resp.status == 404:
243
+ raise TasksNotFoundError(f"Task not found: {task.task_id}")
244
+ elif e.resp.status == 403:
245
+ raise TasksPermissionError(f"Permission denied updating task: {e}")
246
+ else:
247
+ raise TasksError(f"Tasks API error updating task {task.task_id}: {e}")
248
+ except ValueError as e:
249
+ raise InvalidTaskDataError(f"Invalid task data: {e}")
250
+ except Exception as e:
251
+ raise TasksError(f"Unexpected error updating task: {e}")
252
+
253
+ def delete_task(self, task: Task, task_list_id: str = DEFAULT_TASK_LIST_ID) -> bool:
254
+ """
255
+ Deletes a task.
256
+
257
+ Args:
258
+ task: The task to delete.
259
+ task_list_id: Task list identifier containing the task.
260
+
261
+ Returns:
262
+ True if the operation was successful.
263
+ """
264
+
265
+ try:
266
+ self._service.tasks().delete(
267
+ tasklist=task_list_id,
268
+ task=task.task_id
269
+ ).execute()
270
+
271
+ return True
272
+
273
+ except HttpError as e:
274
+ if e.resp.status == 404:
275
+ raise TasksNotFoundError(f"Task not found: {task.task_id}")
276
+ elif e.resp.status == 403:
277
+ raise TasksPermissionError(f"Permission denied deleting task: {e}")
278
+ else:
279
+ raise TasksError(f"Tasks API error deleting task {task.task_id}: {e}")
280
+ except Exception as e:
281
+ raise TasksError(f"Unexpected error deleting task: {e}")
282
+
283
+ def move_task(
284
+ self,
285
+ task: Task,
286
+ task_list_id: str = DEFAULT_TASK_LIST_ID,
287
+ parent: Optional[str] = None,
288
+ previous: Optional[str] = None
289
+ ) -> Task:
290
+ """
291
+ Moves a task to a different position in the task list.
292
+
293
+ Args:
294
+ task: The task to move.
295
+ task_list_id: Task list identifier containing the task.
296
+ parent: Parent task identifier (optional).
297
+ previous: Previous sibling task identifier (optional).
298
+
299
+ Returns:
300
+ A Task object representing the moved task.
301
+ """
302
+
303
+ try:
304
+ request_params = {
305
+ 'tasklist': task_list_id,
306
+ 'task': task.task_id
307
+ }
308
+ if parent:
309
+ request_params['parent'] = parent
310
+ if previous:
311
+ request_params['previous'] = previous
312
+
313
+ moved_task = self._service.tasks().move(**request_params).execute()
314
+
315
+ task = utils.from_google_task(moved_task, task_list_id)
316
+ return task
317
+
318
+ except HttpError as e:
319
+ if e.resp.status == 404:
320
+ raise TasksNotFoundError(f"Task not found: {task.task_id}")
321
+ elif e.resp.status == 403:
322
+ raise TasksPermissionError(f"Permission denied moving task: {e}")
323
+ else:
324
+ raise TaskMoveError(f"Tasks API error moving task {task.task_id}: {e}")
325
+ except Exception as e:
326
+ raise TaskMoveError(f"Unexpected error moving task: {e}")
327
+
328
+ def mark_completed(self, task: Task, task_list_id: str = DEFAULT_TASK_LIST_ID) -> Task:
329
+ """
330
+ Marks a task as completed.
331
+
332
+ Args:
333
+ task: The task to mark as completed.
334
+ task_list_id: Task list identifier containing the task.
335
+
336
+ Returns:
337
+ A Task object representing the updated task.
338
+ """
339
+ task.status = TASK_STATUS_COMPLETED
340
+ task.completed = date.today()
341
+ return self.update_task(task=task, task_list_id=task_list_id)
342
+
343
+ def mark_incomplete(self, task: Task, task_list_id: str = DEFAULT_TASK_LIST_ID) -> Task:
344
+ """
345
+ Marks a task as needing action (incomplete).
346
+
347
+ Args:
348
+ task: The task to mark as incomplete.
349
+ task_list_id: Task list identifier containing the task.
350
+
351
+ Returns:
352
+ A Task object representing the updated task.
353
+ """
354
+ task.completed = None
355
+ task.status = TASK_STATUS_NEEDS_ACTION
356
+ return self.update_task(task=task, task_list_id=task_list_id)
357
+
358
+ # Task List Operations
359
+ def list_task_lists(self) -> List[TaskList]:
360
+ """
361
+ Fetches a list of task lists from Google Tasks.
362
+
363
+ Returns:
364
+ A list of TaskList objects representing the task lists found.
365
+ """
366
+
367
+ try:
368
+ result = self._service.tasklists().list().execute()
369
+ task_lists_data = result.get('items', [])
370
+
371
+
372
+ # Parse task lists
373
+ task_lists = []
374
+ for task_list_data in task_lists_data:
375
+ try:
376
+ task_lists.append(utils.from_google_task_list(task_list_data))
377
+ except Exception as e:
378
+ pass
379
+
380
+ return task_lists
381
+
382
+ except HttpError as e:
383
+ if e.resp.status == 403:
384
+ raise TasksPermissionError(f"Permission denied: {e}")
385
+ else:
386
+ raise TasksError(f"Tasks API error listing task lists: {e}")
387
+ except Exception as e:
388
+ raise TasksError(f"Unexpected error listing task lists: {e}")
389
+
390
+ def get_task_list(self, task_list_id: str) -> TaskList:
391
+ """
392
+ Retrieves a specific task list from Google Tasks.
393
+
394
+ Args:
395
+ task_list_id: The unique identifier of the task list.
396
+
397
+ Returns:
398
+ A TaskList object representing the task list with the specified ID.
399
+ """
400
+
401
+ try:
402
+ task_list_data = self._service.tasklists().get(
403
+ tasklist=task_list_id
404
+ ).execute()
405
+
406
+ return utils.from_google_task_list(task_list_data)
407
+
408
+ except HttpError as e:
409
+ if e.resp.status == 404:
410
+ raise TasksNotFoundError(f"Task list not found: {task_list_id}")
411
+ elif e.resp.status == 403:
412
+ raise TasksPermissionError(f"Permission denied accessing task list: {e}")
413
+ else:
414
+ raise TasksError(f"Tasks API error getting task list {task_list_id}: {e}")
415
+ except Exception as e:
416
+ raise TasksError(f"Unexpected error getting task list: {e}")
417
+
418
+ def create_task_list(self, title: str) -> TaskList:
419
+ """
420
+ Creates a new task list.
421
+
422
+ Args:
423
+ title: The title of the task list.
424
+
425
+ Returns:
426
+ A TaskList object representing the created task list.
427
+ """
428
+
429
+ try:
430
+ # Create task list body using utils
431
+ task_list_body = utils.create_task_list_body(title)
432
+
433
+ # Make API call
434
+ created_task_list = self._service.tasklists().insert(
435
+ body=task_list_body
436
+ ).execute()
437
+
438
+ task_list = utils.from_google_task_list(created_task_list)
439
+ return task_list
440
+
441
+ except HttpError as e:
442
+ if e.resp.status == 403:
443
+ raise TasksPermissionError(f"Permission denied creating task list: {e}")
444
+ else:
445
+ raise TasksError(f"Tasks API error creating task list: {e}")
446
+ except ValueError as e:
447
+ raise InvalidTaskDataError(f"Invalid task list data: {e}")
448
+ except Exception as e:
449
+ raise TasksError(f"Unexpected error creating task list: {e}")
450
+
451
+ def update_task_list(self, task_list: TaskList, title: str) -> TaskList:
452
+ """
453
+ Updates an existing task list.
454
+
455
+ Args:
456
+ task_list: The task list to update.
457
+ title: New title for the task list.
458
+
459
+ Returns:
460
+ A TaskList object representing the updated task list.
461
+ """
462
+
463
+ try:
464
+ # Create update body
465
+ task_list_body = utils.create_task_list_body(title)
466
+ task_list_body['id'] = task_list.task_list_id
467
+
468
+ # Make API call
469
+ updated_task_list = self._service.tasklists().update(
470
+ tasklist=task_list.task_list_id,
471
+ body=task_list_body
472
+ ).execute()
473
+
474
+ task_list.title = title
475
+ task_list = utils.from_google_task_list(updated_task_list)
476
+ return task_list
477
+
478
+ except HttpError as e:
479
+ if e.resp.status == 404:
480
+ raise TasksNotFoundError(f"Task list not found: {task_list.task_list_id}")
481
+ elif e.resp.status == 403:
482
+ raise TasksPermissionError(f"Permission denied updating task list: {e}")
483
+ else:
484
+ raise TasksError(f"Tasks API error updating task list {task_list.task_list_id}: {e}")
485
+ except ValueError as e:
486
+ raise InvalidTaskDataError(f"Invalid task list data: {e}")
487
+ except Exception as e:
488
+ raise TasksError(f"Unexpected error updating task list: {e}")
489
+
490
+ def delete_task_list(self, task_list: TaskList) -> bool:
491
+ """
492
+ Deletes a task list.
493
+
494
+ Args:
495
+ task_list: The task list to delete.
496
+
497
+ Returns:
498
+ True if the operation was successful.
499
+ """
500
+
501
+ try:
502
+ self._service.tasklists().delete(
503
+ tasklist=task_list.task_list_id
504
+ ).execute()
505
+
506
+ return True
507
+
508
+ except HttpError as e:
509
+ if e.resp.status == 404:
510
+ raise TasksNotFoundError(f"Task list not found: {task_list.task_list_id}")
511
+ elif e.resp.status == 403:
512
+ raise TasksPermissionError(f"Permission denied deleting task list: {e}")
513
+ elif e.resp.status == 400:
514
+ raise TasksError(f"Cannot delete default task list: {task_list.task_list_id}")
515
+ else:
516
+ raise TasksError(f"Tasks API error deleting task list {task_list.task_list_id}: {e}")
517
+ except Exception as e:
518
+ raise TasksError(f"Unexpected error deleting task list: {e}")
519
+
520
+ # Batch Operations
521
+ def batch_get_tasks(self, task_list_id: str, task_ids: List[str]) -> List[Task]:
522
+ """
523
+ Retrieves multiple tasks by their IDs.
524
+
525
+ Args:
526
+ task_list_id: Task list identifier containing the tasks.
527
+ task_ids: List of task IDs to retrieve.
528
+
529
+ Returns:
530
+ List of Task objects.
531
+ """
532
+
533
+ tasks = []
534
+ for task_id in task_ids:
535
+ try:
536
+ tasks.append(self.get_task(task_list_id, task_id))
537
+ except Exception as e:
538
+ pass
539
+
540
+ return tasks
541
+
542
+ def batch_create_tasks(self, tasks_data: List[Dict[str, Any]], task_list_id: str = DEFAULT_TASK_LIST_ID) -> List[Task]:
543
+ """
544
+ Creates multiple tasks.
545
+
546
+ Args:
547
+ task_list_id: Task list identifier to create tasks in.
548
+ tasks_data: List of dictionaries containing task parameters.
549
+
550
+ Returns:
551
+ List of created Task objects.
552
+ """
553
+
554
+ created_tasks = []
555
+ for task_data in tasks_data:
556
+ try:
557
+ created_tasks.append(self.create_task(task_list_id=task_list_id, **task_data))
558
+ except Exception as e:
559
+ pass
560
+
561
+ return created_tasks
@@ -0,0 +1,32 @@
1
+ # API Limits and Defaults
2
+ MAX_RESULTS_LIMIT = 100
3
+ DEFAULT_MAX_RESULTS = 100
4
+
5
+ # Field Length Limits
6
+ MAX_TITLE_LENGTH = 1024
7
+ MAX_NOTES_LENGTH = 8192
8
+
9
+ # Task Status Options
10
+ TASK_STATUS_NEEDS_ACTION = "needsAction"
11
+ TASK_STATUS_COMPLETED = "completed"
12
+
13
+ VALID_TASK_STATUSES = [
14
+ TASK_STATUS_NEEDS_ACTION,
15
+ TASK_STATUS_COMPLETED
16
+ ]
17
+
18
+ # Default Task List
19
+ DEFAULT_TASK_LIST_ID = "@default"
20
+
21
+ # API Parameter Names
22
+ API_PARAM_COMPLETED_MIN = "completedMin"
23
+ API_PARAM_COMPLETED_MAX = "completedMax"
24
+ API_PARAM_DUE_MIN = "dueMin"
25
+ API_PARAM_DUE_MAX = "dueMax"
26
+ API_PARAM_SHOW_COMPLETED = "showCompleted"
27
+ API_PARAM_SHOW_HIDDEN = "showHidden"
28
+ API_PARAM_MAX_RESULTS = "maxResults"
29
+
30
+ # DateTime Formats
31
+ ISO_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
32
+ RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
@@ -0,0 +1,35 @@
1
+
2
+
3
+ class TasksError(Exception):
4
+ """Base exception for Tasks API errors."""
5
+ pass
6
+
7
+
8
+ class TasksNotFoundError(TasksError):
9
+ """Raised when a task or task list is not found."""
10
+ pass
11
+
12
+
13
+ class TasksPermissionError(TasksError):
14
+ """Raised when the user lacks permission for a tasks operation."""
15
+ pass
16
+
17
+
18
+ class TaskConflictError(TasksError):
19
+ """Raised when there is a conflict with task operations."""
20
+ pass
21
+
22
+
23
+ class InvalidTaskDataError(TasksError):
24
+ """Raised when task data is invalid or malformed."""
25
+ pass
26
+
27
+
28
+ class TaskListConflictError(TasksError):
29
+ """Raised when there is a conflict with task list operations."""
30
+ pass
31
+
32
+
33
+ class TaskMoveError(TasksError):
34
+ """Raised when there are issues with moving tasks."""
35
+ pass