Skip to content

Commit b14e9c6

Browse files
authored
Merge branch 'O365:master' into master
2 parents 67af091 + 32c13e5 commit b14e9c6

277 files changed

Lines changed: 46090 additions & 26292 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build_pages.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Pages Build
2+
3+
on:
4+
push:
5+
branches: [ "master" ]
6+
jobs:
7+
pages_build:
8+
name: Build Pages
9+
runs-on: "ubuntu-latest"
10+
steps:
11+
- name: "Checkout the repository"
12+
uses: actions/checkout@v4
13+
14+
- name: "Set up Python"
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version-file: "pyproject.toml"
18+
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v1
21+
with:
22+
version: "latest"
23+
24+
- name: Install dependencies
25+
run: uv sync
26+
27+
- name: "Build pages"
28+
run: uv run sphinx-build -b html -c ./docs/source/ ./docs/source/ ./docs/latest/
29+
30+
- name: "Pull any updates"
31+
shell: bash
32+
run: git pull
33+
34+
- name: "Check for changes"
35+
shell: bash
36+
run: git status
37+
38+
- name: "Stage changed files"
39+
shell: bash
40+
run: git add ./docs/latest
41+
42+
- name: "Commit changed files"
43+
shell: bash
44+
run: |
45+
git config --local user.email "action@github.com"
46+
git config --local user.name "GitHub Action"
47+
git commit -m "Update the docs" || true
48+
49+
- name: Push changes
50+
uses: ad-m/github-push-action@master
51+
with:
52+
github_token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ coverage.xml
4949

5050
# Sphinx documentation
5151
docs/_build/
52-
doctrees/
52+
.doctrees/
5353

5454
# PyBuilder
5555
target/
@@ -78,4 +78,7 @@ venv/
7878

7979
# O365 specific
8080
o365_token\.txt
81-
local_tests/
81+
local_tests/
82+
83+
# Mac Specifoc
84+
.DS_Store

CHANGES.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,45 @@
22

33
Almost every release features a lot of bugfixes but those are not listed here.
44

5+
## Version 2.1.7 (2025-09-05)
6+
7+
> [!IMPORTANT]
8+
> **Breaking Change:** Removed support for Python 3.9
9+
> **Breaking Change:** Removed Old Query in favour of the new QueryBuilder (both are interchangeable for other methods to use it, but are build differently)
10+
11+
- Tasks: Add population of checklist_items on Task (Thanks @RogerSelwyn)
12+
- Excel: Added `append_rows` and `update_cells` to `WorkSheet` (Thanks @luissantosHCIT)
13+
14+
## Version 2.1.6 (2025-09-05)
15+
16+
- Version Yanked
17+
18+
## Version 2.1.5 (2025-08-04)
19+
- Bug fixing release
20+
21+
## Version 2.1.4 (2025-06-03)
22+
- Calendar: Schedule.get_calendar method can now use query objects with select, expand and order by (Thanks @RogerSelwyn)
23+
24+
## Version 2.1.3 (2025-06-03)
25+
- Calendar: Added the recurrence type (Thanks @RogerSelwyn)
26+
- Calendar: Added the transaction id (Thanks @RogerSelwyn)
27+
- Calendar: Breaking change! Calendar and Schedule get_events method now requires params start_recurring and end_recurring when include_recurring is True.
28+
- Calendar: list_calendars method can now use query objects with select, expand and order by.
29+
- Groups: Added pagination to get_user_groups (Thanks @RogerSelwyn)
30+
- Tasks: Added support for check list items (Thanks @RogerSelwyn)
31+
- Removed Office365 protocol
32+
33+
## Version 2.1.2 (2025-04-08)
34+
- Calendar: list_calendars now allows pagination (Thanks @RogerSelwyn)
35+
- Query: added new experimental Query object that will replace the current Query object in the future. Available in utils.query.
36+
- Message: non-draft messages can be saved. This allows to edit non-draft messages.
37+
- Connection: proxies, verify_ssl and timeout are now honored in the msal http client.
38+
- Message: new method `get_eml_as_object` to retrieve attached eml as Message objects.
39+
40+
## Version 2.1.1 (2025-03-20)
41+
- Tasks: support unsetting tasks due and reminder (Thanks @RogerSelwyn)
42+
- Removed Office 365 tasks file (api was deprecated on november 2024)
43+
544
## Version 2.1.0 (2025-02-11)
645

746
> [!IMPORTANT]

O365/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
"""
2-
A simple python library to interact with Microsoft Graph and Office 365 API
2+
A simple python library to interact with Microsoft Graph and other MS api
33
"""
4+
45
import warnings
56
import sys
67

7-
from .__version__ import __version__
8-
98
from .account import Account
10-
from .connection import Connection, Protocol, MSGraphProtocol, MSOffice365Protocol
9+
from .connection import Connection, Protocol, MSGraphProtocol
1110
from .utils import FileSystemTokenBackend, EnvTokenBackend
1211
from .message import Message
1312

1413

1514
if sys.warnoptions:
1615
# allow Deprecation warnings to appear
17-
warnings.simplefilter('always', DeprecationWarning)
16+
warnings.simplefilter("always", DeprecationWarning)

O365/__version__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

O365/account.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
from typing import Type, Tuple, Optional, Callable, List
21
import warnings
2+
from typing import Callable, List, Optional, Tuple, Type
33

4-
from .connection import Connection, Protocol, MSGraphProtocol, MSOffice365Protocol
4+
from .connection import Connection, MSGraphProtocol, Protocol
55
from .utils import ME_RESOURCE, consent_input_token
66

77

88
class Account:
9-
connection_constructor: Type = Connection
9+
connection_constructor: Type = Connection #: :meta private:
1010

1111
def __init__(self, credentials: Tuple[str, str], *,
1212
username: Optional[str] = None,
@@ -25,6 +25,7 @@ def __init__(self, credentials: Tuple[str, str], *,
2525
protocol = protocol or MSGraphProtocol # Defaults to Graph protocol
2626
if isinstance(protocol, type):
2727
protocol = protocol(default_resource=main_resource, **kwargs)
28+
# The protocol to use for the account. Defaults ot MSGraphProtocol. |br| **Type:** Protocol
2829
self.protocol: Protocol = protocol
2930

3031
if not isinstance(self.protocol, Protocol):
@@ -57,6 +58,7 @@ def __init__(self, credentials: Tuple[str, str], *,
5758
kwargs['username'] = username
5859

5960
self.con = self.connection_constructor(credentials, **kwargs)
61+
#: The resource in use for the account. |br| **Type:** str
6062
self.main_resource: str = main_resource or self.protocol.default_resource
6163

6264
def __repr__(self):
@@ -77,7 +79,10 @@ def is_authenticated(self) -> bool:
7779
if self.con.load_token_from_backend() is False:
7880
return False
7981

80-
return not self.con.token_backend.token_is_expired(username=self.con.username, refresh_token=True)
82+
return (
83+
self.con.token_backend.token_is_long_lived(username=self.con.username)
84+
or not self.con.token_backend.token_is_expired(username=self.con.username)
85+
)
8186

8287
def authenticate(self, *, requested_scopes: Optional[list] = None, redirect_uri: Optional[str] = None,
8388
handle_consent: Callable = consent_input_token, **kwargs) -> bool:
@@ -182,11 +187,12 @@ def username(self) -> Optional[str]:
182187
return self.con.username
183188

184189
def get_authenticated_usernames(self) -> list[str]:
185-
""" Returns a list of usernames that are authenticated and have a valid access or refresh token. """
190+
""" Returns a list of usernames that are authenticated and have a valid access token or a refresh token."""
186191
usernames = []
192+
tb = self.con.token_backend
187193
for account in self.con.token_backend.get_all_accounts():
188194
username = account.get('username')
189-
if username and not self.con.token_backend.token_is_expired(username=username, refresh_token=True):
195+
if username and (tb.token_is_long_lived(username=username) or not tb.token_is_expired(username=username)):
190196
usernames.append(username)
191197

192198
return usernames
@@ -265,7 +271,7 @@ def address_book(self, *, resource: Optional[str] = None, address_book: str = 'p
265271

266272
def directory(self, resource: Optional[str] = None):
267273
""" Returns the active directory instance"""
268-
from .directory import Directory, USERS_RESOURCE
274+
from .directory import USERS_RESOURCE, Directory
269275

270276
return Directory(parent=self, main_resource=resource or USERS_RESOURCE)
271277

@@ -331,10 +337,7 @@ def planner(self, *, resource: str = ''):
331337
def tasks(self, *, resource: str = ''):
332338
""" Get an instance to read information from Microsoft ToDo """
333339

334-
if isinstance(self.protocol, MSOffice365Protocol):
335-
from .tasks import ToDo
336-
else:
337-
from .tasks_graph import ToDo as ToDo
340+
from .tasks import ToDo
338341

339342
return ToDo(parent=self, main_resource=resource)
340343

O365/address_book.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
from dateutil.parser import parse
55
from requests.exceptions import HTTPError
66

7-
from .utils import Recipients
8-
from .utils import AttachableMixin, TrackerSet
9-
from .utils import Pagination, NEXT_LINK_KEYWORD, ApiComponent
10-
from .message import Message, RecipientType
117
from .category import Category
12-
8+
from .message import Message, RecipientType
9+
from .utils import (
10+
NEXT_LINK_KEYWORD,
11+
ApiComponent,
12+
AttachableMixin,
13+
Pagination,
14+
Recipients,
15+
TrackerSet,
16+
)
1317

1418
log = logging.getLogger(__name__)
1519

1620

1721
class Contact(ApiComponent, AttachableMixin):
18-
""" Contact manages lists of events on associated contact on office365. """
22+
""" Contact manages lists of events on associated contact on Microsoft 365. """
1923

2024
_endpoints = {
2125
'contact': '/contacts',
@@ -25,7 +29,7 @@ class Contact(ApiComponent, AttachableMixin):
2529
'photo_size': '/contacts/{id}/photos/{size}/$value',
2630
}
2731

28-
message_constructor = Message
32+
message_constructor = Message #: :meta private:
2933

3034
def __init__(self, *, parent=None, con=None, **kwargs):
3135
""" Create a contact API component
@@ -56,6 +60,7 @@ def __init__(self, *, parent=None, con=None, **kwargs):
5660
# internal to know which properties need to be updated on the server
5761
self._track_changes = TrackerSet(casing=cc)
5862

63+
#: The contact's unique identifier. |br| **Type:** str
5964
self.object_id = cloud_data.get(cc('id'), None)
6065
self.__created = cloud_data.get(cc('createdDateTime'), None)
6166
self.__modified = cloud_data.get(cc('lastModifiedDateTime'), None)
@@ -457,7 +462,7 @@ def personal_notes(self, value):
457462

458463
@property
459464
def folder_id(self):
460-
""" ID of the folder
465+
"""ID of the containing folder
461466
462467
:rtype: str
463468
"""
@@ -594,7 +599,8 @@ def new_message(self, recipient=None, *, recipient_type=RecipientType.TO):
594599
return new_message
595600

596601
def get_profile_photo(self, size=None):
597-
""" Returns this contact profile photo
602+
"""Returns this contact profile photo
603+
598604
:param str size: 48x48, 64x64, 96x96, 120x120, 240x240,
599605
360x360, 432x432, 504x504, and 648x648
600606
"""
@@ -636,8 +642,8 @@ class BaseContactFolder(ApiComponent):
636642
'child_folders': '/contactFolders/{id}/childFolders'
637643
}
638644

639-
contact_constructor = Contact
640-
message_constructor = Message
645+
contact_constructor = Contact #: :meta private:
646+
message_constructor = Message #: :meta private:
641647

642648
def __init__(self, *, parent=None, con=None, **kwargs):
643649
""" Create a contact folder component
@@ -663,17 +669,21 @@ def __init__(self, *, parent=None, con=None, **kwargs):
663669
main_resource=main_resource)
664670

665671
# This folder has no parents if root = True.
672+
#: Indicates if this is the root folder. |br| **Type:** bool
666673
self.root = kwargs.pop('root', False)
667674

668675
cloud_data = kwargs.get(self._cloud_data_key, {})
669676

670677
# Fallback to manual folder if nothing available on cloud data
678+
#: The folder's display name. |br| **Type:** str
671679
self.name = cloud_data.get(self._cc('displayName'),
672680
kwargs.get('name',
673681
''))
674682
# TODO: Most of above code is same as mailbox.Folder __init__
675683

684+
#: Unique identifier of the contact folder. |br| **Type:** str
676685
self.folder_id = cloud_data.get(self._cc('id'), None)
686+
#: The ID of the folder's parent folder. |br| **Type:** str
677687
self.parent_id = cloud_data.get(self._cc('parentFolderId'), None)
678688

679689
def __str__(self):

0 commit comments

Comments
 (0)