Skip to content

Home

PymstodoError

Bases: Exception

Basic Pymstodo exception

Source code in pymstodo/client.py
15
16
17
18
19
class PymstodoError(Exception):
    '''Basic Pymstodo exception'''

    def __init__(self, status_code: int, reason: str):
        super().__init__(f'Error {status_code}: {reason}')

Task dataclass

To-Do task represents a task, such as a piece of work or personal item, that can be tracked and completed

Source code in pymstodo/client.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@dataclasses.dataclass
class Task:
    '''**To-Do task** represents a task, such as a piece of work or personal item, that can be tracked and completed'''

    task_id: str
    '''Unique identifier for the task. By default, this value changes when the item is moved from one list to another'''

    body: _Body

    categories: list[str]
    '''The categories associated with the task'''

    completedDateTime: _DateTimeTimeZone
    '''The date and time in the specified time zone that the task was finished. Uses ISO 8601 format'''

    createdDateTime: str
    '''The date and time when the task was created. It is in UTC and uses ISO 8601 format'''

    dueDateTime: _DateTimeTimeZone
    '''The date and time in the specified time zone that the task is to be finished. Uses ISO 8601 format'''

    hasAttachments: bool
    '''Indicates whether the task has attachments'''

    title: str
    '''A brief description of the task'''

    importance: Literal['low', 'normal', 'high']
    '''The importance of the task. Possible values are: `low`, `normal`, `high`'''

    isReminderOn: bool
    '''Set to true if an alert is set to remind the user of the task'''

    lastModifiedDateTime: str
    '''The date and time when the task was last modified. It is in UTC and uses ISO 8601 format'''

    reminderDateTime: _DateTimeTimeZone
    '''The date and time in the specified time zone for a reminder alert of the task to occur. Uses ISO 8601 format'''

    startDateTime: _DateTimeTimeZone
    '''The date and time in the specified time zone at which the task is scheduled to start. Uses ISO 8601 format'''

    status: str

    def __init__(self, **kwargs: Any) -> None:
        for f in dataclasses.fields(self):
            setattr(self, f.name, kwargs.get('id' if f.name == 'task_id' else f.name))

    def __str__(self) -> str:
        title = self.title.replace('|', '—').strip()
        if self.due_date:
            title += f' • Due {self.due_date.strftime("%x")}'

        return title

    @property
    def body_text(self) -> str | None:
        '''The task body that typically contains information about the task'''
        if self.body:
            return self.body['content']
        return None

    @property
    def completed_date(self) -> datetime | None:
        '''The date and time in the specified time zone that the task was finished'''
        if self.completedDateTime:
            return datetime.fromisoformat(self.completedDateTime['dateTime']).astimezone(ZoneInfo(get_zoneinfo_name_by_windows_zone(self.completedDateTime['timeZone'])))
        return None

    @property
    def created_date(self) -> datetime | None:
        '''The date and time when the task was created. It is in UTC'''
        if self.createdDateTime:
            return datetime.fromisoformat(self.createdDateTime).astimezone(timezone.utc)
        return None

    @property
    def due_date(self) -> datetime | None:
        '''The date and time in the specified time zone that the task is to be finished'''
        if self.dueDateTime:
            return datetime.fromisoformat(self.dueDateTime['dateTime']).astimezone(ZoneInfo(get_zoneinfo_name_by_windows_zone(self.dueDateTime['timeZone'])))
        return None

    @property
    def last_mod_date(self) -> datetime | None:
        '''The date and time when the task was last modified. It is in UTC'''
        if self.lastModifiedDateTime:
            return datetime.fromisoformat(self.lastModifiedDateTime).astimezone(timezone.utc)
        return None

    @property
    def reminder_date(self) -> datetime | None:
        '''The date and time in the specified time zone for a reminder alert of the task to occur'''
        if self.reminderDateTime:
            return datetime.fromisoformat(self.reminderDateTime['dateTime']).astimezone(ZoneInfo(get_zoneinfo_name_by_windows_zone(self.reminderDateTime['timeZone'])))
        return None

    @property
    def start_date(self) -> datetime | None:
        '''The date and time in the specified time zone at which the task is scheduled to start'''
        if self.startDateTime:
            return datetime.fromisoformat(self.startDateTime['dateTime']).astimezone(ZoneInfo(get_zoneinfo_name_by_windows_zone(self.startDateTime['timeZone'])))
        return None

    @property
    def task_status(self) -> TaskStatus:
        '''Indicates the state or progress of the task'''
        return TaskStatus(self.status)

body_text: str | None property

The task body that typically contains information about the task

categories: list[str] instance-attribute

The categories associated with the task

completedDateTime: _DateTimeTimeZone instance-attribute

The date and time in the specified time zone that the task was finished. Uses ISO 8601 format

completed_date: datetime | None property

The date and time in the specified time zone that the task was finished

createdDateTime: str instance-attribute

The date and time when the task was created. It is in UTC and uses ISO 8601 format

created_date: datetime | None property

The date and time when the task was created. It is in UTC

dueDateTime: _DateTimeTimeZone instance-attribute

The date and time in the specified time zone that the task is to be finished. Uses ISO 8601 format

due_date: datetime | None property

The date and time in the specified time zone that the task is to be finished

hasAttachments: bool instance-attribute

Indicates whether the task has attachments

importance: Literal['low', 'normal', 'high'] instance-attribute

The importance of the task. Possible values are: low, normal, high

isReminderOn: bool instance-attribute

Set to true if an alert is set to remind the user of the task

lastModifiedDateTime: str instance-attribute

The date and time when the task was last modified. It is in UTC and uses ISO 8601 format

last_mod_date: datetime | None property

The date and time when the task was last modified. It is in UTC

reminderDateTime: _DateTimeTimeZone instance-attribute

The date and time in the specified time zone for a reminder alert of the task to occur. Uses ISO 8601 format

reminder_date: datetime | None property

The date and time in the specified time zone for a reminder alert of the task to occur

startDateTime: _DateTimeTimeZone instance-attribute

The date and time in the specified time zone at which the task is scheduled to start. Uses ISO 8601 format

start_date: datetime | None property

The date and time in the specified time zone at which the task is scheduled to start

task_id: str instance-attribute

Unique identifier for the task. By default, this value changes when the item is moved from one list to another

task_status: TaskStatus property

Indicates the state or progress of the task

title: str instance-attribute

A brief description of the task

TaskList dataclass

To-Do task list contains one or more task

Source code in pymstodo/client.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@dataclasses.dataclass
class TaskList:
    '''**To-Do task list** contains one or more task'''

    list_id: str
    '''The identifier of the task list, unique in the user's mailbox. Read-only'''

    displayName: str
    '''The name of the task list'''

    isOwner: bool
    '''`True` if the user is owner of the given task list'''

    isShared: bool
    '''`True` if the task list is shared with other users'''

    wellknownListName: str

    def __init__(self, **kwargs: Any) -> None:
        for f in dataclasses.fields(self):
            setattr(self, f.name, kwargs.get('id' if f.name == 'list_id' else f.name))

    def __str__(self) -> str:
        return self.displayName.replace('|', '—').strip()

    @property
    def link(self) -> str:
        '''Link to the task list on web.'''
        return 'href=https://to-do.live.com/tasks/{self.list_id}'

    @property
    def wellknown_list_name(self) -> WellknownListName | None:
        '''Property indicating the list name if the given list is a well-known list'''
        return None if self.wellknownListName == 'none' else WellknownListName(self.wellknownListName)

displayName: str instance-attribute

The name of the task list

isOwner: bool instance-attribute

True if the user is owner of the given task list

isShared: bool instance-attribute

True if the task list is shared with other users

Link to the task list on web.

list_id: str instance-attribute

The identifier of the task list, unique in the user's mailbox. Read-only

wellknown_list_name: WellknownListName | None property

Property indicating the list name if the given list is a well-known list

TaskStatus

Bases: Enum

The state or progress of the task

Attributes:

Name Type Description
notStarted

not started

inProgress

in progress

completed

completed

waitingOnOthers

waiting on others

deferred

deferred

Source code in pymstodo/client.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class TaskStatus(Enum):
    '''
    The state or progress of the task

    Attributes:
        notStarted: not started
        inProgress: in progress
        completed: completed
        waitingOnOthers: waiting on others
        deferred: deferred
    '''

    NOT_STARTED = 'notStarted'
    IN_PROGRESS = 'inProgress'
    COMPLETED = 'completed'
    WAITING_ON_OTHERS = 'waitingOnOthers'
    DEFERRED = 'deferred'

TaskStatusFilter

Bases: Enum

Tasks status filter

Attributes:

Name Type Description
completed

only completed tasks

notCompleted

only non-completed tasks

all

all tasks

Source code in pymstodo/client.py
77
78
79
80
81
82
83
84
85
86
87
88
89
class TaskStatusFilter(Enum):
    '''
    Tasks status filter

    Attributes:
        completed: only completed tasks
        notCompleted: only non-completed tasks
        all: all tasks
    '''

    COMPLETED = 'completed'
    NOT_COMPLETED = 'notCompleted'
    ALL = 'all'

ToDoConnection

To-Do connection is your entry point to the To-Do API

Parameters:

Name Type Description Default
client_id str

API client ID

required
client_secret str

API client secret

required
token Token

Token obtained by method get_token

required
Source code in pymstodo/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
class ToDoConnection:
    '''**To-Do connection** is your entry point to the To-Do API

    Args:
        client_id: API client ID
        client_secret: API client secret
        token: Token obtained by method `get_token`
    '''
    _redirect: str = 'https://localhost/login/authorized'
    _scope: str = 'openid offline_access Tasks.ReadWrite'
    _authority: str = 'https://login.microsoftonline.com/common'
    _authorize_endpoint: str = '/oauth2/v2.0/authorize'
    _token_endpoint: str = '/oauth2/v2.0/token'
    _base_api_url: str = 'https://graph.microsoft.com/v1.0/me/todo/'

    def __init__(self, client_id: str, client_secret: str, token: Token) -> None:
        self.client_id: str = client_id
        self.client_secret: str = client_secret
        self.token: Token = token

    @staticmethod
    def get_auth_url(client_id: str) -> Any:
        '''Get the authorization_url

        Args:
            client_id: API client ID

        Returns:
            Authorization URL to show to the user
        '''
        oa_sess = OAuth2Session(client_id, scope=ToDoConnection._scope, redirect_uri=ToDoConnection._redirect)

        authorize_url = f'{ToDoConnection._authority}{ToDoConnection._authorize_endpoint}'
        authorization_url, _ = oa_sess.authorization_url(authorize_url)

        return authorization_url

    @staticmethod
    def get_token(client_id: str, client_secret: str, redirect_resp: str) -> Any:
        '''Fetch the access token'''
        oa_sess = OAuth2Session(client_id, scope=ToDoConnection._scope, redirect_uri=ToDoConnection._redirect)
        token_url = f'{ToDoConnection._authority}{ToDoConnection._token_endpoint}'
        return oa_sess.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_resp)

    def _refresh_token(self) -> None:
        now = time.time()
        expire_time = self.token['expires_at'] - 300
        if now >= expire_time:
            token_url = f'{ToDoConnection._authority}{ToDoConnection._token_endpoint}'
            oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope,
                                    token=self.token, redirect_uri=ToDoConnection._redirect)
            new_token = oa_sess.refresh_token(token_url, client_id=self.client_id, client_secret=self.client_secret)
            self.token = new_token

    def get_lists(self, limit: int | None = 99) -> list[TaskList]:
        '''Get a list of the task lists

        Args:
            limit: The limit size of the response

        Returns:
            A list of the task lists

        Raises:
            PymstodoError: An error occurred accessing the API
        '''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists?$top={limit}')
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())['value']
        return [TaskList(**list_data) for list_data in contents]

    def create_list(self, name: str) -> TaskList:
        '''Create a new task list

        Args:
            name: Title of the new task list

        Returns:
            A created task list

        Raises:
            PymstodoError: An error occurred accessing the API
        '''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.post(f'{ToDoConnection._base_api_url}lists', json={'displayName': name})
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return TaskList(**contents)

    def get_list(self, list_id: str) -> TaskList:
        '''Read the properties of a task list

        Args:
            list_id: Unique identifier for the task list

        Returns:
            A task list object

        Raises:
            PymstodoError: An error occurred accessing the API
        '''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists/{list_id}')
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return TaskList(**contents)

    def update_list(self, list_id: str, **list_data: str | bool) -> TaskList:
        '''Update the properties of a task list

        Args:
            list_id: Unique identifier for the task list
            list_data: Task properties from `TaskList` object

        Returns:
            An updated task list.

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.patch(f'{ToDoConnection._base_api_url}lists/{list_id}', json=list_data)
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return TaskList(**contents)

    def delete_list(self, list_id: str) -> bool:
        '''Delete a task list

        Args:
            list_id: Unique identifier for the task list

        Returns:
            `True` if success

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.delete(f'{ToDoConnection._base_api_url}lists/{list_id}')
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        return True

    def get_tasks(self, list_id: str, limit: int | None = 1000, status: TaskStatusFilter | None = TaskStatusFilter.NOT_COMPLETED) -> list[Task]:
        '''Get tasks by a specified task list

        Args:
            list_id: Unique identifier for the task list
            limit: The limit size of the response
            status: The state or progress of the task

        Returns:
            Tasks of a specified task list

        Raises:
            PymstodoError: An error occurred accessing the API
        '''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        filters = {
            TaskStatusFilter.COMPLETED: "filter=status eq 'completed'",
            TaskStatusFilter.NOT_COMPLETED: "filter=status ne 'completed'",
            TaskStatusFilter.ALL: None
        }
        eff_limit = limit or 1000
        params = (
            filters.get(status or TaskStatusFilter.NOT_COMPLETED, filters[TaskStatusFilter.NOT_COMPLETED]),
            f'top={eff_limit}'
        )
        params_str = '&$'.join(filter(None, params))
        url = f'{ToDoConnection._base_api_url}lists/{list_id}/tasks?${params_str}'
        contents: list[dict[str, Any]] = []
        while (len(contents) < eff_limit or eff_limit <= 0) and url:
            resp = oa_sess.get(url)
            if not resp.ok:
                raise PymstodoError(resp.status_code, resp.reason)
            resp_content = json.loads(resp.content.decode())
            url = resp_content.get('@odata.nextLink')
            contents.extend(resp_content['value'])
        if limit:
            contents = contents[:limit]
        return [Task(**task_data) for task_data in contents]

    def create_task(self, title: str, list_id: str, due_date: datetime | None = None, body_text: str | None = None) -> Task:
        '''Create a new task in a specified task list

        Args:
            title: A brief description of the task
            list_id: Unique identifier for the task list
            due_date: The date and time that the task is to be finished
            body_text: Information about the task

        Returns:
            A created task

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        task_data: dict[str, Any] = {'title': title}
        if due_date:
            task_data['dueDateTime'] = {'dateTime': due_date.strftime('%Y-%m-%dT%H:%M:%S.0000000'), 'timeZone': 'UTC'}
        if body_text:
            task_data['body'] = {'content': body_text, 'contentType': 'text'}
        resp = oa_sess.post(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks', json=task_data)
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return Task(**contents)

    def get_task(self, task_id: str, list_id: str) -> Task:
        '''Read the properties of a task

        Args:
            task_id: Unique identifier for the task
            list_id: Unique identifier for the task list

        Returns:
            A task object

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}')
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return Task(**contents)

    def update_task(self, task_id: str, list_id: str, **task_data: str | int | bool) -> Task:
        '''Update the properties of a task

        Args:
            task_id: Unique identifier for the task
            list_id: Unique identifier for the task list
            task_data: Task properties from `Task` object

        Returns:
            An updated task

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.patch(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}', json=task_data)
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        contents = json.loads(resp.content.decode())

        return Task(**contents)

    def delete_task(self, task_id: str, list_id: str) -> bool:
        '''Delete a task

        Args:
            task_id: Unique identifier for the task
            list_id: Unique identifier for the task list

        Returns:
            `True` if success

        Raises:
            PymstodoError: An error occurred accessing the API'''
        self._refresh_token()
        oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
        resp = oa_sess.delete(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}')
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)

        return True

    def complete_task(self, task_id: str, list_id: str) -> Task:
        '''Complete a task

        Args:
            task_id: Unique identifier for the task
            list_id: Unique identifier for the task list

        Returns:
            A completed task

        Raises:
            PymstodoError: An error occurred accessing the API'''
        return self.update_task(task_id, list_id, status='completed')

complete_task(task_id, list_id)

Complete a task

Parameters:

Name Type Description Default
task_id str

Unique identifier for the task

required
list_id str

Unique identifier for the task list

required

Returns:

Type Description
Task

A completed task

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
532
533
534
535
536
537
538
539
540
541
542
543
544
def complete_task(self, task_id: str, list_id: str) -> Task:
    '''Complete a task

    Args:
        task_id: Unique identifier for the task
        list_id: Unique identifier for the task list

    Returns:
        A completed task

    Raises:
        PymstodoError: An error occurred accessing the API'''
    return self.update_task(task_id, list_id, status='completed')

create_list(name)

Create a new task list

Parameters:

Name Type Description Default
name str

Title of the new task list

required

Returns:

Type Description
TaskList

A created task list

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def create_list(self, name: str) -> TaskList:
    '''Create a new task list

    Args:
        name: Title of the new task list

    Returns:
        A created task list

    Raises:
        PymstodoError: An error occurred accessing the API
    '''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.post(f'{ToDoConnection._base_api_url}lists', json={'displayName': name})
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return TaskList(**contents)

create_task(title, list_id, due_date=None, body_text=None)

Create a new task in a specified task list

Parameters:

Name Type Description Default
title str

A brief description of the task

required
list_id str

Unique identifier for the task list

required
due_date datetime | None

The date and time that the task is to be finished

None
body_text str | None

Information about the task

None

Returns:

Type Description
Task

A created task

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
def create_task(self, title: str, list_id: str, due_date: datetime | None = None, body_text: str | None = None) -> Task:
    '''Create a new task in a specified task list

    Args:
        title: A brief description of the task
        list_id: Unique identifier for the task list
        due_date: The date and time that the task is to be finished
        body_text: Information about the task

    Returns:
        A created task

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    task_data: dict[str, Any] = {'title': title}
    if due_date:
        task_data['dueDateTime'] = {'dateTime': due_date.strftime('%Y-%m-%dT%H:%M:%S.0000000'), 'timeZone': 'UTC'}
    if body_text:
        task_data['body'] = {'content': body_text, 'contentType': 'text'}
    resp = oa_sess.post(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks', json=task_data)
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return Task(**contents)

delete_list(list_id)

Delete a task list

Parameters:

Name Type Description Default
list_id str

Unique identifier for the task list

required

Returns:

Type Description
bool

True if success

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
def delete_list(self, list_id: str) -> bool:
    '''Delete a task list

    Args:
        list_id: Unique identifier for the task list

    Returns:
        `True` if success

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.delete(f'{ToDoConnection._base_api_url}lists/{list_id}')
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    return True

delete_task(task_id, list_id)

Delete a task

Parameters:

Name Type Description Default
task_id str

Unique identifier for the task

required
list_id str

Unique identifier for the task list

required

Returns:

Type Description
bool

True if success

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
def delete_task(self, task_id: str, list_id: str) -> bool:
    '''Delete a task

    Args:
        task_id: Unique identifier for the task
        list_id: Unique identifier for the task list

    Returns:
        `True` if success

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.delete(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}')
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    return True

get_auth_url(client_id) staticmethod

Get the authorization_url

Parameters:

Name Type Description Default
client_id str

API client ID

required

Returns:

Type Description
Any

Authorization URL to show to the user

Source code in pymstodo/client.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
@staticmethod
def get_auth_url(client_id: str) -> Any:
    '''Get the authorization_url

    Args:
        client_id: API client ID

    Returns:
        Authorization URL to show to the user
    '''
    oa_sess = OAuth2Session(client_id, scope=ToDoConnection._scope, redirect_uri=ToDoConnection._redirect)

    authorize_url = f'{ToDoConnection._authority}{ToDoConnection._authorize_endpoint}'
    authorization_url, _ = oa_sess.authorization_url(authorize_url)

    return authorization_url

get_list(list_id)

Read the properties of a task list

Parameters:

Name Type Description Default
list_id str

Unique identifier for the task list

required

Returns:

Type Description
TaskList

A task list object

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def get_list(self, list_id: str) -> TaskList:
    '''Read the properties of a task list

    Args:
        list_id: Unique identifier for the task list

    Returns:
        A task list object

    Raises:
        PymstodoError: An error occurred accessing the API
    '''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists/{list_id}')
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return TaskList(**contents)

get_lists(limit=99)

Get a list of the task lists

Parameters:

Name Type Description Default
limit int | None

The limit size of the response

99

Returns:

Type Description
list[TaskList]

A list of the task lists

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def get_lists(self, limit: int | None = 99) -> list[TaskList]:
    '''Get a list of the task lists

    Args:
        limit: The limit size of the response

    Returns:
        A list of the task lists

    Raises:
        PymstodoError: An error occurred accessing the API
    '''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists?$top={limit}')
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())['value']
    return [TaskList(**list_data) for list_data in contents]

get_task(task_id, list_id)

Read the properties of a task

Parameters:

Name Type Description Default
task_id str

Unique identifier for the task

required
list_id str

Unique identifier for the task list

required

Returns:

Type Description
Task

A task object

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
def get_task(self, task_id: str, list_id: str) -> Task:
    '''Read the properties of a task

    Args:
        task_id: Unique identifier for the task
        list_id: Unique identifier for the task list

    Returns:
        A task object

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.get(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}')
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return Task(**contents)

get_tasks(list_id, limit=1000, status=TaskStatusFilter.NOT_COMPLETED)

Get tasks by a specified task list

Parameters:

Name Type Description Default
list_id str

Unique identifier for the task list

required
limit int | None

The limit size of the response

1000
status TaskStatusFilter | None

The state or progress of the task

TaskStatusFilter.NOT_COMPLETED

Returns:

Type Description
list[Task]

Tasks of a specified task list

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
def get_tasks(self, list_id: str, limit: int | None = 1000, status: TaskStatusFilter | None = TaskStatusFilter.NOT_COMPLETED) -> list[Task]:
    '''Get tasks by a specified task list

    Args:
        list_id: Unique identifier for the task list
        limit: The limit size of the response
        status: The state or progress of the task

    Returns:
        Tasks of a specified task list

    Raises:
        PymstodoError: An error occurred accessing the API
    '''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    filters = {
        TaskStatusFilter.COMPLETED: "filter=status eq 'completed'",
        TaskStatusFilter.NOT_COMPLETED: "filter=status ne 'completed'",
        TaskStatusFilter.ALL: None
    }
    eff_limit = limit or 1000
    params = (
        filters.get(status or TaskStatusFilter.NOT_COMPLETED, filters[TaskStatusFilter.NOT_COMPLETED]),
        f'top={eff_limit}'
    )
    params_str = '&$'.join(filter(None, params))
    url = f'{ToDoConnection._base_api_url}lists/{list_id}/tasks?${params_str}'
    contents: list[dict[str, Any]] = []
    while (len(contents) < eff_limit or eff_limit <= 0) and url:
        resp = oa_sess.get(url)
        if not resp.ok:
            raise PymstodoError(resp.status_code, resp.reason)
        resp_content = json.loads(resp.content.decode())
        url = resp_content.get('@odata.nextLink')
        contents.extend(resp_content['value'])
    if limit:
        contents = contents[:limit]
    return [Task(**task_data) for task_data in contents]

get_token(client_id, client_secret, redirect_resp) staticmethod

Fetch the access token

Source code in pymstodo/client.py
275
276
277
278
279
280
@staticmethod
def get_token(client_id: str, client_secret: str, redirect_resp: str) -> Any:
    '''Fetch the access token'''
    oa_sess = OAuth2Session(client_id, scope=ToDoConnection._scope, redirect_uri=ToDoConnection._redirect)
    token_url = f'{ToDoConnection._authority}{ToDoConnection._token_endpoint}'
    return oa_sess.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_resp)

update_list(list_id, **list_data)

Update the properties of a task list

Parameters:

Name Type Description Default
list_id str

Unique identifier for the task list

required
list_data str | bool

Task properties from TaskList object

{}

Returns:

Type Description
TaskList

An updated task list.

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
def update_list(self, list_id: str, **list_data: str | bool) -> TaskList:
    '''Update the properties of a task list

    Args:
        list_id: Unique identifier for the task list
        list_data: Task properties from `TaskList` object

    Returns:
        An updated task list.

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.patch(f'{ToDoConnection._base_api_url}lists/{list_id}', json=list_data)
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return TaskList(**contents)

update_task(task_id, list_id, **task_data)

Update the properties of a task

Parameters:

Name Type Description Default
task_id str

Unique identifier for the task

required
list_id str

Unique identifier for the task list

required
task_data str | int | bool

Task properties from Task object

{}

Returns:

Type Description
Task

An updated task

Raises:

Type Description
PymstodoError

An error occurred accessing the API

Source code in pymstodo/client.py
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def update_task(self, task_id: str, list_id: str, **task_data: str | int | bool) -> Task:
    '''Update the properties of a task

    Args:
        task_id: Unique identifier for the task
        list_id: Unique identifier for the task list
        task_data: Task properties from `Task` object

    Returns:
        An updated task

    Raises:
        PymstodoError: An error occurred accessing the API'''
    self._refresh_token()
    oa_sess = OAuth2Session(self.client_id, scope=ToDoConnection._scope, token=self.token)
    resp = oa_sess.patch(f'{ToDoConnection._base_api_url}lists/{list_id}/tasks/{task_id}', json=task_data)
    if not resp.ok:
        raise PymstodoError(resp.status_code, resp.reason)

    contents = json.loads(resp.content.decode())

    return Task(**contents)

WellknownListName

Bases: Enum

Well-known list name

Attributes:

Name Type Description
DEFAULT_LIST

Default list

FLAGGED_EMAILS

Flagged emails

UNKNOWN_FUTURE_VALUE

Unknown future value

Source code in pymstodo/client.py
43
44
45
46
47
48
49
50
51
52
53
54
55
class WellknownListName(Enum):
    '''
    Well-known list name

    Attributes:
        DEFAULT_LIST: Default list
        FLAGGED_EMAILS: Flagged emails
        UNKNOWN_FUTURE_VALUE: Unknown future value
    '''

    DEFAULT_LIST = 'defaultList'
    FLAGGED_EMAILS = 'flaggedEmails'
    UNKNOWN_FUTURE_VALUE = 'unknownFutureValue'