Skip to content

Commit f8efeb0

Browse files
authored
Merge branch 'O365:master' into docs_for_query
2 parents a2f466f + 7578e64 commit f8efeb0

25 files changed

+206
-276
lines changed

O365/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
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

78
from .__version__ import __version__
89

910
from .account import Account
10-
from .connection import Connection, Protocol, MSGraphProtocol, MSOffice365Protocol
11+
from .connection import Connection, Protocol, MSGraphProtocol
1112
from .utils import FileSystemTokenBackend, EnvTokenBackend
1213
from .message import Message
1314

1415

1516
if sys.warnoptions:
1617
# allow Deprecation warnings to appear
17-
warnings.simplefilter('always', DeprecationWarning)
18+
warnings.simplefilter("always", DeprecationWarning)

O365/address_book.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

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

2424
_endpoints = {
2525
'contact': '/contacts',

O365/calendar.py

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,8 +1780,9 @@ def delete(self):
17801780

17811781
return True
17821782

1783-
def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
1784-
download_attachments=False, include_recurring=True):
1783+
def get_events(self, limit: int = 25, *, query=None, order_by=None, batch=None,
1784+
download_attachments=False, include_recurring=True,
1785+
start_recurring=None, end_recurring=None):
17851786
""" Get events from this Calendar
17861787
17871788
:param int limit: max no. of events to get. Over 999 uses batch.
@@ -1793,6 +1794,8 @@ def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
17931794
batches allowing to retrieve more items than the limit.
17941795
:param download_attachments: downloads event attachments
17951796
:param bool include_recurring: whether to include recurring events or not
1797+
:param start_recurring: a string datetime or a Query object with just a start condition
1798+
:param end_recurring: a string datetime or a Query object with just an end condition
17961799
:return: list of events in this calendar
17971800
:rtype: list[Event] or Pagination
17981801
"""
@@ -1822,22 +1825,29 @@ def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
18221825
if include_recurring:
18231826
start = None
18241827
end = None
1825-
if query and not isinstance(query, str):
1826-
# extract start and end from query because
1827-
# those are required by a calendarView
1828-
start = query.get_filter_by_attribute('start/')
1829-
end = query.get_filter_by_attribute('start/')
1830-
1831-
if start:
1832-
start = start.replace("'", '') # remove the quotes
1833-
query.remove_filter('start')
1834-
if end:
1835-
end = end.replace("'", '') # remove the quotes
1836-
query.remove_filter('end')
1837-
1828+
if start_recurring is None:
1829+
pass
1830+
elif isinstance(start_recurring, str):
1831+
start = start_recurring
1832+
elif isinstance(start_recurring, dt.datetime):
1833+
start = start_recurring.isoformat()
1834+
else:
1835+
# it's a Query Object
1836+
start = start_recurring.get_filter_by_attribute('start/')
1837+
if end_recurring is None:
1838+
pass
1839+
elif isinstance(end_recurring, str):
1840+
end = end_recurring
1841+
elif isinstance(end_recurring, dt.datetime):
1842+
end = end_recurring.isoformat()
1843+
else:
1844+
# it's a Query Object
1845+
end = end_recurring.get_filter_by_attribute('end/')
18381846
if start is None or end is None:
18391847
raise ValueError("When 'include_recurring' is True you must provide "
1840-
"a 'start' and 'end' datetime inside a 'Query' instance.")
1848+
"a 'start_recurring' and 'end_recurring' with a datetime string.")
1849+
start = start.replace("'", '') # remove the quotes
1850+
end = end.replace("'", '') # remove the quotes
18411851

18421852
params[self._cc('startDateTime')] = start
18431853
params[self._cc('endDateTime')] = end
@@ -2088,9 +2098,19 @@ def get_default_calendar(self):
20882098
return self.calendar_constructor(parent=self,
20892099
**{self._cloud_data_key: data})
20902100

2091-
def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
2092-
download_attachments=False, include_recurring=True):
2093-
""" Get events from the default Calendar
2101+
def get_events(
2102+
self,
2103+
limit=25,
2104+
*,
2105+
query=None,
2106+
order_by=None,
2107+
batch=None,
2108+
download_attachments=False,
2109+
include_recurring=True,
2110+
start_recurring=None,
2111+
end_recurring=None,
2112+
):
2113+
"""Get events from the default Calendar
20942114
20952115
:param int limit: max no. of events to get. Over 999 uses batch.
20962116
:param query: applies a OData filter to the request
@@ -2101,16 +2121,24 @@ def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
21012121
batches allowing to retrieve more items than the limit.
21022122
:param bool download_attachments: downloads event attachments
21032123
:param bool include_recurring: whether to include recurring events or not
2124+
:param start_recurring: a string datetime or a Query object with just a start condition
2125+
:param end_recurring: a string datetime or a Query object with just an end condition
21042126
:return: list of items in this folder
21052127
:rtype: list[Event] or Pagination
21062128
"""
21072129

21082130
default_calendar = self.calendar_constructor(parent=self)
21092131

2110-
return default_calendar.get_events(limit=limit, query=query,
2111-
order_by=order_by, batch=batch,
2112-
download_attachments=download_attachments,
2113-
include_recurring=include_recurring)
2132+
return default_calendar.get_events(
2133+
limit=limit,
2134+
query=query,
2135+
order_by=order_by,
2136+
batch=batch,
2137+
download_attachments=download_attachments,
2138+
include_recurring=include_recurring,
2139+
start_recurring=start_recurring,
2140+
end_recurring=end_recurring,
2141+
)
21142142

21152143
def new_event(self, subject=None):
21162144
""" Returns a new (unsaved) Event object in the default calendar

O365/connection.py

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
log = logging.getLogger(__name__)
3636

37-
O365_API_VERSION: str = "v2.0"
3837
GRAPH_API_VERSION: str = "v1.0"
3938
OAUTH_REDIRECT_URL: str = "https://login.microsoftonline.com/common/oauth2/nativeclient"
4039

@@ -193,8 +192,6 @@ def convert_case(self, key: str) -> str:
193192
When using Microsoft Graph API, the keywords of the API use
194193
lowerCamelCase Casing
195194
196-
When using Office 365 API, the keywords of the API use PascalCase Casing
197-
198195
Default case in this API is lowerCamelCase
199196
200197
:param key: a dictionary key to convert
@@ -298,62 +295,9 @@ def timezone(self, timezone: Union[str, ZoneInfo]) -> None:
298295
)
299296

300297

301-
class MSOffice365Protocol(Protocol):
302-
"""A Microsoft Office 365 Protocol Implementation
303-
https://docs.microsoft.com/en-us/outlook/rest/compare-graph-outlook
304-
"""
305-
306-
_protocol_url = "https://outlook.office.com/api/"
307-
_oauth_scope_prefix = "https://outlook.office.com/"
308-
_oauth_scopes = DEFAULT_SCOPES
309-
310-
def __init__(self, api_version: str = "v2.0", default_resource: Optional[str] = None, **kwargs):
311-
"""Create a new Office 365 protocol object
312-
313-
_protocol_url = 'https://outlook.office.com/api/'
314-
315-
_oauth_scope_prefix = 'https://outlook.office.com/'
316-
317-
:param str api_version: api version to use
318-
:param str default_resource: the default resource to use when there is
319-
nothing explicitly specified during the requests
320-
"""
321-
super().__init__(
322-
protocol_url=self._protocol_url,
323-
api_version=api_version,
324-
default_resource=default_resource,
325-
casing_function=to_pascal_case,
326-
protocol_scope_prefix=self._oauth_scope_prefix,
327-
**kwargs,
328-
)
329-
330-
self.keyword_data_store["message_type"] = "Microsoft.OutlookServices.Message"
331-
self.keyword_data_store["event_message_type"] = (
332-
"Microsoft.OutlookServices.EventMessage"
333-
)
334-
self.keyword_data_store["file_attachment_type"] = (
335-
"#Microsoft.OutlookServices.FileAttachment"
336-
)
337-
self.keyword_data_store["item_attachment_type"] = (
338-
"#Microsoft.OutlookServices.ItemAttachment"
339-
)
340-
self.keyword_data_store["prefer_timezone_header"] = (
341-
f'outlook.timezone="{get_windows_tz(self.timezone)}"'
342-
)
343-
#: The max value for 'top' (999). |br| **Type:** str
344-
self.max_top_value = 999 # Max $top parameter value
345-
346-
@Protocol.timezone.setter
347-
def timezone(self, timezone: Union[str, ZoneInfo]) -> None:
348-
super()._update_timezone(timezone)
349-
self.keyword_data_store["prefer_timezone_header"] = (
350-
f'outlook.timezone="{get_windows_tz(self._timezone)}"'
351-
)
352-
353-
354298
class MSBusinessCentral365Protocol(Protocol):
355299
"""A Microsoft Business Central Protocol Implementation
356-
https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/endpoints-apis-for-dynamics
300+
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v1.0/
357301
"""
358302

359303
_protocol_url = "https://api.businesscentral.dynamics.com/"

O365/excel.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import re
1010
from urllib.parse import quote
1111

12-
from .connection import MSOffice365Protocol
1312
from .drive import File
1413
from .utils import ApiComponent, TrackerSet, to_snake_case
1514

@@ -2066,11 +2065,6 @@ def __init__(self, file_item, *, use_session=True, persist=True):
20662065
):
20672066
raise ValueError("This file is not a valid Excel xlsx file.")
20682067

2069-
if isinstance(file_item.protocol, MSOffice365Protocol):
2070-
raise ValueError(
2071-
"Excel capabilities are only allowed on the MSGraph protocol"
2072-
)
2073-
20742068
# append the workbook path
20752069
main_resource = "{}{}/workbook".format(
20762070
file_item.main_resource,

O365/groups.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class Group(ApiComponent):
10-
""" A Microsoft O365 group """
10+
""" A Microsoft 365 group """
1111

1212
_endpoints = {
1313
'get_group_owners': '/groups/{group_id}/owners',
@@ -17,7 +17,7 @@ class Group(ApiComponent):
1717
member_constructor = User #: :meta private:
1818

1919
def __init__(self, *, parent=None, con=None, **kwargs):
20-
""" A Microsoft O365 group
20+
""" A Microsoft 365 group
2121
2222
:param parent: parent object
2323
:type parent: Teams
@@ -156,7 +156,7 @@ def __repr__(self):
156156
return 'Microsoft O365 Group parent class'
157157

158158
def get_group_by_id(self, group_id = None):
159-
""" Returns Microsoft O365/AD group with given id
159+
""" Returns Microsoft 365/AD group with given id
160160
161161
:param group_id: group id of group
162162
@@ -181,7 +181,7 @@ def get_group_by_id(self, group_id = None):
181181
return self.group_constructor(parent=self, **{self._cloud_data_key: data})
182182

183183
def get_group_by_mail(self, group_mail=None):
184-
"""Returns Microsoft O365/AD group by mail field
184+
"""Returns Microsoft 365/AD group by mail field
185185
186186
:param group_name: mail of group
187187

O365/message.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,16 +1160,8 @@ def save_draft(self, target_folder=OutlookWellKnowFolderNames.DRAFTS):
11601160
self.object_id = message.get(self._cc('id'), None)
11611161
self.folder_id = message.get(self._cc('parentFolderId'), None)
11621162

1163-
# fallback to office365 v1.0
1164-
self.__created = message.get(self._cc('createdDateTime'),
1165-
message.get(
1166-
self._cc('dateTimeCreated'),
1167-
None))
1168-
# fallback to office365 v1.0
1169-
self.__modified = message.get(self._cc('lastModifiedDateTime'),
1170-
message.get(
1171-
self._cc('dateTimeModified'),
1172-
None))
1163+
self.__created = message.get(self._cc('createdDateTime'),None)
1164+
self.__modified = message.get(self._cc('lastModifiedDateTime'),None)
11731165

11741166
self.__created = parse(self.__created).astimezone(
11751167
self.protocol.timezone) if self.__created else None

O365/planner.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class TaskDetails(ApiComponent):
1212
_endpoints = {"task_detail": "/planner/tasks/{id}/details"}
1313

1414
def __init__(self, *, parent=None, con=None, **kwargs):
15-
"""A Microsoft O365 plan details
15+
"""A Microsoft 365 plan details
1616
1717
:param parent: parent object
1818
:type parent: Task
@@ -180,7 +180,7 @@ class PlanDetails(ApiComponent):
180180
_endpoints = {"plan_detail": "/planner/plans/{id}/details"}
181181

182182
def __init__(self, *, parent=None, con=None, **kwargs):
183-
"""A Microsoft O365 plan details
183+
"""A Microsoft 365 plan details
184184
185185
:param parent: parent object
186186
:type parent: Plan
@@ -386,7 +386,7 @@ def __eq__(self, other):
386386
return self.object_id == other.object_id
387387

388388
def get_details(self):
389-
"""Returns Microsoft O365/AD plan with given id
389+
"""Returns Microsoft 365/AD plan with given id
390390
391391
:rtype: PlanDetails
392392
"""
@@ -507,7 +507,7 @@ class Bucket(ApiComponent):
507507
task_constructor = Task #: :meta private:
508508

509509
def __init__(self, *, parent=None, con=None, **kwargs):
510-
"""A Microsoft O365 bucket
510+
"""A Microsoft 365 bucket
511511
512512
:param parent: parent object
513513
:type parent: Planner or Plan
@@ -751,7 +751,7 @@ class Plan(ApiComponent):
751751
plan_details_constructor = PlanDetails #: :meta private:
752752

753753
def __init__(self, *, parent=None, con=None, **kwargs):
754-
"""A Microsoft O365 plan
754+
"""A Microsoft 365 plan
755755
756756
:param parent: parent object
757757
:type parent: Planner
@@ -861,7 +861,7 @@ def list_tasks(self):
861861
return tasks
862862

863863
def get_details(self):
864-
"""Returns Microsoft O365/AD plan with given id
864+
"""Returns Microsoft 365/AD plan with given id
865865
866866
:rtype: PlanDetails
867867
"""
@@ -1040,7 +1040,7 @@ def get_my_tasks(self, *args):
10401040
]
10411041

10421042
def get_plan_by_id(self, plan_id=None):
1043-
"""Returns Microsoft O365/AD plan with given id
1043+
"""Returns Microsoft 365/AD plan with given id
10441044
10451045
:param plan_id: plan id of plan
10461046
@@ -1067,7 +1067,7 @@ def get_plan_by_id(self, plan_id=None):
10671067
)
10681068

10691069
def get_bucket_by_id(self, bucket_id=None):
1070-
"""Returns Microsoft O365/AD plan with given id
1070+
"""Returns Microsoft 365/AD plan with given id
10711071
10721072
:param bucket_id: bucket id of buckets
10731073
@@ -1091,7 +1091,7 @@ def get_bucket_by_id(self, bucket_id=None):
10911091
return self.bucket_constructor(parent=self, **{self._cloud_data_key: data})
10921092

10931093
def get_task_by_id(self, task_id=None):
1094-
"""Returns Microsoft O365/AD plan with given id
1094+
"""Returns Microsoft 365/AD plan with given id
10951095
10961096
:param task_id: task id of tasks
10971097
@@ -1115,7 +1115,7 @@ def get_task_by_id(self, task_id=None):
11151115
return self.task_constructor(parent=self, **{self._cloud_data_key: data})
11161116

11171117
def list_user_tasks(self, user_id=None):
1118-
"""Returns Microsoft O365/AD plan with given id
1118+
"""Returns Microsoft 365/AD plan with given id
11191119
11201120
:param user_id: user id
11211121

0 commit comments

Comments
 (0)