Skip to content

Commit

Permalink
For #5, adds support for upload sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
dpfens committed Feb 24, 2021
1 parent 9e213d2 commit 7b72235
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
53 changes: 53 additions & 0 deletions examples/files_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os
from msgraph import api, user, files

if __name__ == '__main__':
import argparse
authority_host_uri = 'https://login.microsoftonline.com'
tenant = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
resource_uri = 'https://graph.microsoft.com'
client_id = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
client_thumbprint = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

parser = argparse.ArgumentParser(description='Microsoft Graph Files Test')
parser.add_argument('-t', '--tenant', dest='tenant', type=str, help='Microsoft Graph Tenant', default=os.getenv('DATACENTER'))
parser.add_argument('-c', '--clientid', dest='client_id', type=str, help='Microsoft Graph Client ID', default=os.getenv('CLIENTID'))
parser.add_argument('--thumbprint', dest='thumbprint', type=str, help='Microsoft Graph Secret', default=os.getenv('CLIENTSECRET'))
parser.add_argument('--certificate', dest='certificate', type=str, help='Microsoft Graph Certificate', default=os.getenv('CERTIFICATE'))
parser.add_argument('--user', dest='user', type=str, required=True, help='Microsoft Graph Test User')
parser.add_argument('--source', dest='src', type=str, required=True, help='Source file to upload')
parser.add_argument('--filename', dest='filename', type=str, required=True, help='File name to save as')

args = parser.parse_args()

if not os.path.exists(args.src):
raise ValueErr('%r does not exist' % args.src)

if os.path.exists(args.certificate):
with open(args.certificate, 'rb') as input_file:
client_certificate = input_file.read()
else:
client_certificate = args.certificate
api_instance = api.GraphAPI.from_certificate(authority_host_uri, args.tenant, resource_uri, args.client_id, client_certificate, args.thumbprint)

test_user = user.User.get(api_instance, args.user)
# fetch a OneDrive of a given user
drive = files.Drive.get(api_instance, user=test_user)
# fetch the OneDrive for a given site

# fetch drives accessible by a given user
accessible_drives = files.Drive.accessible(api_instance, user=args.user)
print(accessible_drives)

# fetch the root folder of the drive
drive_root_folder = files.DriveItem.root_folder(api_instance, drive=drive)

# create a new folder in the root folder
new_folder = files.DriveItem.create_folder(api_instance, "Test", drive_root_folder, drive=drive)

session = files.Session.create(api, new_folder, file_name=args.filename)
with open(args.src, 'rb') as input_file:
session.upload(api, input_file)

# delete newly created folder
new_folder.delete(api_instance, drive=drive)
101 changes: 101 additions & 0 deletions msgraph/files.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from msgraph import base
from mspgraph import exception

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -507,3 +508,103 @@ def root_folder(cls, api, **kwargs):
uri += '/root'
data = api.request(uri)
return cls.from_api(data)


class Session(base.Base):
__slots__ = ('upload_url', 'expiration_datetime', 'file_size')
FAIL = 'fail'
REPLACE = 'replace'
RENAME = 'rename'

def __init__(self, upload_url, expiration_datetime, file_size):
self.upload_url = upload_url
self.expiration_datetime = expiration_datetime
self.file_size = file_size
self.next_expected_ranges = None
self.is_complete = False

def upload(self, api, file, chunk_size=327680):
"""
Uploads a file to Microsoft drive in chunks
"""
while self.is_incomplete:
chunk = file.read(chunk_size)
output = self.upload_part(api, chunk)
return output

def upload_part(self, api, content):
content_size = len(content)
if self.next_expected_ranges:
lower_bound, upper_bound = self.next_expected_ranges
if content_size < lower_bound or content_size > upper_bound:
raise exception.MicrosoftException('UPLOAD', 'content size (%r) not in expected range (%r-%r)' % (content_size, lower_bound, upper_bound))

headers = {
'Content-Length': content_size,
}
response = api.request(self.upload_url, data=content, headers=headers, method='PUT')

return self._update_state(response)

def _update_state(self, data):
is_incomplete = 'expirationDateTime' in data and 'nextExpectedRanges' in data
if is_incomplete:
raw_expiration_datetime = data['expirationDateTime']
self.expiration_datetime = self.parse_date_time(raw_expiration_datetime)
raw_next_expected_ranges = data['nextExpectedRanges'][0].split('-')

raw_lower_bound = raw_next_expected_ranges[0]
lower_bound = int(raw_lower_bound)

raw_upper_bound = raw_next_expected_ranges[1]
upper_bound = int(raw_upper_bound) if raw_upper_bound else float('inf')
self.next_expected_ranges = (lower_bound, upper_bound)
else:
self.is_complete = True
return data

@classmethod
def from_api(cls, data):
upload_url = data['uploadUrl']
raw_expiration_datetime = data['expirationDateTime']
file_size = data['fileSize']
expiration_datetime = cls.parse_date_time(raw_expiration_datetime)
return cls(upload_url, expiration_datetime, file_size)

@classmethod
def create(cls, api, item, **kwargs):
group = kwargs.get('group')
site = kwargs.get('site')
drive = kwargs.get('drive')
user = kwargs.get('user')
if drive:
uri = 'drives/%s/items/%s/createUploadSession' % (drive, item)
elif group:
uri = 'groups/%s/drive/items/%s/createUploadSession' % (group, item)
elif site:
uri = 'sites/%s/drive/items/%s/createUploadSession' % (site, item)
elif user:
uri = 'users/%s/drive/items/%s/createUploadSession' % (user, item)
else:
uri = 'me/drive/items/%s/createUploadSession' % item

item_data = {
"@odata.type": "microsoft.graph.driveItemUploadableProperties",
}
item_parameters = {
'conflict_behavior': 'conflictBehavior',
'file_size': 'fileSize',
'file_name': 'name',
'description': 'description'
}
for key in item_parameters:
value = kwargs.get(key)
if value:
parameter = item_parameters[key]
item_data[parameter] = value

defer_commit = kwargs.get('defer_commit', False)
data = dict(item=item_data, defer_commit=defer_commit)
response = api.request(uri, json=data, method='POST')
response['fileSize'] = item_data.get('fileSize')
return cls.from_api(response)

0 comments on commit 7b72235

Please sign in to comment.