Skip to content

Commit cd369c6

Browse files
2.1.9 version, type hints, unit tests for Teams API
1 parent 59fd59c commit cd369c6

40 files changed

+346
-118
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ python:
66
- "3.7"
77
- "3.8"
88

9+
matrix:
10+
allow_failures:
11+
- python: "2.7"
12+
913
install:
1014
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install requests adal setuptools requests_ntlm; fi
1115
- pip install -r requirements.txt

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Office 365 & Microsoft Graph library for Python
66
1. [Installation](#Installation)
77
1. [Working with SharePoint API](#Working-with-SharePoint-API)
88
2. [Working with Outlook API](#Working-with-Outlook-API)
9-
3. [Working with OneDrive API](#Working-with-OneDrive-API)
9+
3. [Working with OneDrive API](#Working-with-OneDrive-API)
10+
4. [Working with Microsoft Teams API](#Working-with-Microsoft-Teams-API)
1011

1112

1213
## Status
@@ -214,6 +215,44 @@ def download_files(remote_folder, local_path):
214215
Refer [OneDrive examples section](examples/onedrive) for a more examples.
215216

216217

218+
# Working with Microsoft Teams API
219+
220+
#### Authentication
221+
222+
[ADAL Python](https://adal-python.readthedocs.io/en/latest/#)
223+
library is utilized to authenticate users to Active Directory (AD) and obtain tokens
224+
225+
#### Examples
226+
227+
##### Example: create a new team under a group
228+
229+
The example demonstrates how create a new team under a group
230+
which corresponds to [`Create team` endpoint](https://docs.microsoft.com/en-us/graph/api/team-put-teams?view=graph-rest-1.0&tabs=http)
231+
232+
```
233+
234+
tenant_name = "contoso.onmicrosoft.com"
235+
client = GraphClient(tenant_name, get_token)
236+
new_team = client.groups[group_id].add_team()
237+
client.execute_query()
238+
239+
```
240+
241+
where
242+
243+
```
244+
def get_token(auth_ctx):
245+
"""Acquire token via client credential flow (ADAL Python library is utilized)
246+
:type auth_ctx: adal.AuthenticationContext
247+
"""
248+
token = auth_ctx.acquire_token_with_client_credentials(
249+
"https://graph.microsoft.com",
250+
client_id,
251+
client_secret)
252+
return token
253+
```
254+
255+
217256
# Third Party Libraries and Dependencies
218257
The following libraries will be installed when you install the client library:
219258
* [requests](https://github.com/kennethreitz/requests)

examples/directory/delete_groups.py renamed to examples/directory/delete_deletedObjects.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44

55
def get_token_for_user(auth_ctx):
6+
"""
7+
8+
:type auth_ctx: adal.AuthenticationContext
9+
"""
610
token = auth_ctx.acquire_token_with_username_password(
711
'https://graph.microsoft.com',
812
settings['user_credentials']['username'],
@@ -13,20 +17,15 @@ def get_token_for_user(auth_ctx):
1317

1418
client = GraphClient(settings['tenant'], get_token_for_user)
1519

16-
groups = client.groups
17-
client.load(groups)
20+
deleted_groups = client.directory.deletedGroups
21+
client.load(deleted_groups)
22+
deleted_users = client.directory.deletedUsers
23+
client.load(deleted_users)
1824
client.execute_query()
19-
no = 1
20-
groups_count = len(groups)
21-
for grp in groups:
22-
print("({0} of {1}) Deleting {2} group ...".format(no, groups_count, grp.properties['displayName']))
23-
# 1st step: delete group
24-
grp.delete_object()
25-
client.execute_query()
25+
groups_count = len(deleted_groups)
2626

27-
# 2nd step: permanently delete (deleted) group
28-
deleted_group = client.directory.deletedGroups[grp.id]
29-
deleted_group.delete_object()
27+
for index, deleted_grp in enumerate(deleted_groups):
28+
print("({0} of {1}) Deleting {2} group ...".format(index + 1, groups_count, deleted_grp.properties['displayName']))
29+
deleted_grp.delete_object()
3030
client.execute_query()
3131
print("Group deleted.")
32-
no += 1

examples/onedrive/export_files.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import os
22
import tempfile
3-
43
from office365.graph.graph_client import GraphClient
54
from settings import settings
65

76

87
def get_token(auth_ctx):
9-
"""Acquire token via client credential flow (ADAL Python library is utilized)"""
8+
"""Acquire token via client credential flow (ADAL Python library is utilized)
9+
:type auth_ctx: adal.AuthenticationContext
10+
"""
1011
token = auth_ctx.acquire_token_with_client_credentials(
1112
"https://graph.microsoft.com",
1213
settings['client_credentials']['client_id'],
@@ -37,7 +38,7 @@ def download_files(remote_folder, local_path):
3738
client.load(drive)
3839
client.execute_query()
3940

40-
# download files from onedrive
41+
# download files from OneDrive
4142
with tempfile.TemporaryDirectory() as path:
4243
download_files(drive.root, path)
4344
print("Done")

examples/onedrive/import_files.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
from os.path import isfile, join
33
from settings import settings
44
from office365.graph.graph_client import GraphClient
5+
from office365.graph.onedrive.drive import Drive
56

67

78
def get_token(auth_ctx):
8-
"""Acquire token via client credential flow (ADAL Python library is utilized)"""
9+
"""Acquire token via client credential flow
10+
11+
:type auth_ctx: adal.AuthenticationContext
12+
"""
913
token = auth_ctx.acquire_token_with_client_credentials(
1014
"https://graph.microsoft.com",
1115
settings['client_credentials']['client_id'],
@@ -14,14 +18,20 @@ def get_token(auth_ctx):
1418

1519

1620
def upload_files(remote_drive, local_root_path):
21+
"""
22+
Uploads files from local folder into OneDrive drive
23+
24+
:type remote_drive: Drive
25+
:type local_root_path: str
26+
"""
1727
for name in os.listdir(local_root_path):
1828
path = join(local_root_path, name)
1929
if isfile(path):
2030
with open(path, 'rb') as local_file:
2131
content = local_file.read()
2232
uploaded_drive_item = remote_drive.root.upload(name, content)
2333
remote_drive.context.execute_query()
24-
print("File '{0}' uploaded into {1}".format(path, uploaded_drive_item.webUrl),)
34+
print("File '{0}' uploaded into {1}".format(path, uploaded_drive_item.webUrl), )
2535

2636

2737
# get target drive

examples/onedrive/print_folders_and_files.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44

55
def get_token_for_user(auth_ctx):
6+
"""
7+
Acquire token via user credentials
8+
9+
:type auth_ctx: adal.AuthenticationContext
10+
"""
611
token = auth_ctx.acquire_token_with_username_password(
712
'https://graph.microsoft.com',
813
settings['user_credentials']['username'],

examples/teams/__init__.py

Whitespace-only changes.

office365/graph/directory/directoryObjectCollection.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ def __init__(self, context, resource_path=None):
1313
super(DirectoryObjectCollection, self).__init__(context, DirectoryObject, resource_path)
1414

1515
def __getitem__(self, key):
16+
"""
17+
18+
:param key: key is used to address a DirectoryObject resource by either an index in collection
19+
or by resource id
20+
:type key: int or str
21+
"""
1622
if type(key) == int:
17-
return self._data[key]
18-
return DirectoryObject(self.context,
23+
return super(DirectoryObjectCollection, self).__getitem__(key)
24+
return self._item_type(self.context,
1925
ResourcePath(key, self.resource_path))
2026

2127
def getByIds(self, ids):
@@ -40,5 +46,9 @@ def remove(self, user_id):
4046
self.context.get_pending_request().beforeExecute += self._construct_remove_user_request
4147

4248
def _construct_remove_user_request(self, request):
49+
"""
50+
51+
:type request: RequestOptions
52+
"""
4353
request.method = HttpMethod.Delete
4454
self.context.get_pending_request().beforeExecute -= self._construct_remove_user_request

office365/graph/directory/group.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
from office365.graph.teams.team import Team
1111

1212

13+
def _delete_group_from_directory(target_group):
14+
"""
15+
Deletes the group from directory
16+
17+
:type target_group: Group
18+
"""
19+
deleted_item = target_group.context.directory.deletedGroups[target_group.id]
20+
deleted_item.delete_object()
21+
22+
1323
class Group(DirectoryObject):
1424
"""Represents an Azure Active Directory (Azure AD) group, which can be an Office 365 group, or a security group."""
1525

@@ -22,6 +32,16 @@ def add_team(self):
2232
self.context.get_pending_request().beforeExecute += self._construct_create_team_request
2333
return team
2434

35+
def delete_object(self, permanent_delete=False):
36+
"""
37+
:param permanent_delete: Permanently deletes the group from directory
38+
:type permanent_delete: bool
39+
40+
"""
41+
super(Group, self).delete_object()
42+
if permanent_delete:
43+
self.ensure_property("id", _delete_group_from_directory)
44+
2545
def _construct_create_team_request(self, request):
2646
request.method = HttpMethod.Put
2747
request.set_header('Content-Type', "application/json")

office365/graph/directory/groupCollection.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from office365.graph.directory.directoryObjectCollection import DirectoryObjectCollection
22
from office365.graph.directory.group import Group
33
from office365.runtime.client_query import CreateEntityQuery
4+
from office365.graph.directory.groupProfile import GroupProfile
45

56

67
class GroupCollection(DirectoryObjectCollection):
7-
"""User's collection"""
8+
"""Group's collection"""
89

910
def __init__(self, context, resource_path=None):
1011
super(GroupCollection, self).__init__(context, resource_path)
@@ -13,7 +14,9 @@ def __init__(self, context, resource_path=None):
1314
def add(self, group_properties):
1415
"""Create a Group resource. You can create the following types of groups:
1516
Office 365 group (unified group)
16-
Security group"""
17+
Security group
18+
19+
:type group_properties: GroupProfile"""
1720
grp = Group(self.context)
1821
self.add_child(grp)
1922
qry = CreateEntityQuery(self, group_properties, grp)

office365/graph/directory/groupCreationProperties.py renamed to office365/graph/directory/groupProfile.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from office365.runtime.client_value_object import ClientValueObject
22

33

4-
class GroupCreationProperties(ClientValueObject):
4+
class GroupProfile(ClientValueObject):
55
def __init__(self, name):
6-
super(GroupCreationProperties, self).__init__()
6+
"""
7+
8+
:param str name: Group name
9+
"""
10+
super(GroupProfile, self).__init__()
711
self.mailNickname = name
812
self.displayName = name
913
self.description = None

office365/graph/directory/user.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from office365.graph.directory.directoryObject import DirectoryObject
2+
from office365.graph.directory.directoryObjectCollection import DirectoryObjectCollection
23
from office365.graph.directory.groupCollection import GroupCollection
34
from office365.graph.onedrive.drive import Drive
45
from office365.outlookservices.contact_collection import ContactCollection
@@ -77,6 +78,23 @@ def joinedTeams(self):
7778
else:
7879
return GroupCollection(self.context, ResourcePath("joinedTeams", self.resource_path))
7980

81+
@property
82+
def memberOf(self):
83+
"""Get groups and directory roles that the user is a direct member of."""
84+
if self.is_property_available('memberOf'):
85+
return self.properties['memberOf']
86+
else:
87+
return DirectoryObjectCollection(self.context, ResourcePath("memberOf", self.resource_path))
88+
89+
@property
90+
def transitiveMemberOf(self):
91+
"""Get groups, directory roles that the user is a member of. This API request is transitive, and will also
92+
return all groups the user is a nested member of. """
93+
if self.is_property_available('transitiveMemberOf'):
94+
return self.properties['transitiveMemberOf']
95+
else:
96+
return DirectoryObjectCollection(self.context, ResourcePath("transitiveMemberOf", self.resource_path))
97+
8098
def set_property(self, name, value, persist_changes=True):
8199
super(User, self).set_property(name, value, persist_changes)
82100
# fallback: create a new resource path

office365/graph/directory/userCollection.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from office365.graph.directory.directoryObjectCollection import DirectoryObjectCollection
22
from office365.graph.directory.user import User
33
from office365.runtime.client_query import CreateEntityQuery
4-
from office365.runtime.resource_path import ResourcePath
4+
from office365.graph.directory.userProfile import UserProfile
55

66

77
class UserCollection(DirectoryObjectCollection):
@@ -11,15 +11,10 @@ def __init__(self, context, resource_path=None):
1111
super(UserCollection, self).__init__(context, resource_path)
1212
self._item_type = User
1313

14-
def __getitem__(self, key):
15-
if type(key) == int:
16-
return self._data[key]
17-
return User(self.context, ResourcePath(key, self.resource_path))
18-
1914
def add(self, user_properties):
2015
"""Create a new user.
2116
22-
:type user_properties: UserCreationProperties
17+
:type user_properties: UserProfile
2318
"""
2419
usr = User(self.context)
2520
qry = CreateEntityQuery(self, user_properties, usr)

office365/graph/entity.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ def entity_type_name(self):
1010

1111
@property
1212
def id(self):
13-
"""The unique identifier of the drive."""
13+
"""The unique identifier of the drive.
14+
:rtype: str or None
15+
"""
1416
if self.is_property_available("id"):
1517
return self.properties['id']
1618
return None

0 commit comments

Comments
 (0)