diff --git a/examples/__init__.py b/examples/__init__.py index ff62e0649..e69de29bb 100644 --- a/examples/__init__.py +++ b/examples/__init__.py @@ -1,9 +0,0 @@ -from office365.onedrive.drives.drive import Drive - - -def upload_sample_files(drive): - # type: (Drive) -> None - local_paths = ["../../data/Financial Sample.xlsx"] - for local_path in local_paths: - file = drive.root.resumable_upload(local_path).get().execute_query() - print("File {0} has been uploaded", file.web_url) diff --git a/examples/onedrive/files/share_invitation.py b/examples/onedrive/files/share_invitation.py index 6c6ffe7cb..f4f8bd2ad 100644 --- a/examples/onedrive/files/share_invitation.py +++ b/examples/onedrive/files/share_invitation.py @@ -8,13 +8,11 @@ """ from datetime import datetime, timedelta -from examples import upload_sample_files from office365.graph_client import GraphClient from tests.graph_case import acquire_token_by_username_password file_name = "Financial Sample.xlsx" client = GraphClient(acquire_token_by_username_password) -# upload_sample_files(client.me.drive) file_item = client.me.drive.root.get_by_path(file_name) expired = datetime.utcnow() + timedelta(days=1) permissions = file_item.invite( diff --git a/examples/onedrive/files/upload_large.py b/examples/onedrive/files/upload_large.py index e89617805..054df8337 100644 --- a/examples/onedrive/files/upload_large.py +++ b/examples/onedrive/files/upload_large.py @@ -7,15 +7,14 @@ from office365.graph_client import GraphClient from tests.graph_case import acquire_token_by_username_password -client = GraphClient(acquire_token_by_username_password) - -chunk_size = 3 * 1024 * 1024 - def print_progress(range_pos): + # type: (int) -> None print("{0} bytes uploaded".format(range_pos)) +client = GraphClient(acquire_token_by_username_password) +chunk_size = 1 * 1024 * 1024 local_path = "../../../tests/data/big_buck_bunny.mp4" remote_folder = client.me.drive.root.get_by_path("archive") remote_file = ( diff --git a/examples/outlook/messages/search.py b/examples/outlook/messages/search.py index d2215fbd2..2c0f4caa7 100644 --- a/examples/outlook/messages/search.py +++ b/examples/outlook/messages/search.py @@ -4,11 +4,11 @@ https://learn.microsoft.com/en-us/graph/search-concept-messages """ -import json - from office365.graph_client import GraphClient from tests.graph_case import acquire_token_by_username_password client = GraphClient(acquire_token_by_username_password) result = client.search.query_messages("Let's go for lunch").execute_query() -print(json.dumps(result.value.to_json(), indent=4)) +for item in result.value: + for hit in item.hitsContainers[0].hits: + print(hit.resource.properties.get("webLink")) diff --git a/examples/outlook/messages/send_with_large_attachment.py b/examples/outlook/messages/send_with_large_attachment.py index e731b0743..219d81949 100644 --- a/examples/outlook/messages/send_with_large_attachment.py +++ b/examples/outlook/messages/send_with_large_attachment.py @@ -1,28 +1,31 @@ """ -Demonstrates how to upload large attachment to Outlook message +Demonstrates how send e message with large attachment to Outlook message https://learn.microsoft.com/en-us/graph/api/attachment-createuploadsession?view=graph-rest-1.0 """ from office365.graph_client import GraphClient +from tests import test_user_principal_name_alt from tests.graph_case import acquire_token_by_username_password -client = GraphClient(acquire_token_by_username_password) - -local_path = "../../../tests/data/big_buck_bunny.mp4" - def print_progress(range_pos): print("{0} bytes uploaded".format(range_pos)) +client = GraphClient(acquire_token_by_username_password) +local_path = "../../../tests/data/big_buck_bunny.mp4" message = ( - client.me.messages.add( - subject="Meet for lunch?", - body="The new cafeteria is open.", - to_recipients=["fannyd@contoso.onmicrosoft.com"], + ( + client.me.messages.add( + subject="Meet for lunch?", + body="The new cafeteria is open.", + to_recipients=[ + "fannyd@contoso.onmicrosoft.com", + test_user_principal_name_alt, + ], + ).upload_attachment(local_path, print_progress) ) - .upload_attachment(local_path, print_progress) + .send() .execute_query() ) -message.send().execute_query() diff --git a/generator/metadata/MicrosoftGraph.xml b/generator/metadata/MicrosoftGraph.xml index 9b7c115ae..4fc9a8cf2 100644 --- a/generator/metadata/MicrosoftGraph.xml +++ b/generator/metadata/MicrosoftGraph.xml @@ -3871,6 +3871,11 @@ + + + + + @@ -5242,6 +5247,11 @@ + + + + + @@ -5298,11 +5308,18 @@ - - - - - + + + + + + + + + + + + @@ -5311,6 +5328,19 @@ + + + + + + + + + + + + + @@ -5407,13 +5437,6 @@ - - - - - - - @@ -20612,6 +20635,7 @@ + @@ -22504,6 +22528,7 @@ + @@ -22905,6 +22930,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -23111,14 +23184,6 @@ - - - - - - - - @@ -23126,10 +23191,6 @@ - - - - @@ -23170,17 +23231,6 @@ - - - - - - - - - - - @@ -23225,18 +23275,6 @@ - - - - - - - - - - - - @@ -31068,6 +31106,12 @@ + + + + + + diff --git a/office365/onedrive/driveitems/driveItem.py b/office365/onedrive/driveitems/driveItem.py index 9f4e7609b..ab0702a24 100644 --- a/office365/onedrive/driveitems/driveItem.py +++ b/office365/onedrive/driveitems/driveItem.py @@ -31,9 +31,6 @@ from office365.onedrive.folders.folder import Folder from office365.onedrive.internal.paths.children import ChildrenPath from office365.onedrive.internal.paths.url import UrlPath -from office365.onedrive.internal.queries.resumable_file_upload import ( - create_resumable_file_upload_query, -) from office365.onedrive.listitems.item_reference import ItemReference from office365.onedrive.listitems.list_item import ListItem from office365.onedrive.operations.pending import PendingOperations @@ -50,6 +47,7 @@ from office365.runtime.http.http_method import HttpMethod from office365.runtime.http.request_options import RequestOptions from office365.runtime.odata.v4.upload_session import UploadSession +from office365.runtime.odata.v4.upload_session_request import UploadSessionRequest from office365.runtime.paths.resource_path import ResourcePath from office365.runtime.queries.create_entity import CreateEntityQuery from office365.runtime.queries.function import FunctionQuery @@ -198,12 +196,21 @@ def resumable_upload(self, source_path, chunk_size=2000000, chunk_uploaded=None) :param str source_path: File path :param int chunk_size: chunk size """ + + def _start_upload(): + with open(source_path, "rb") as local_file: + session_request = UploadSessionRequest( + local_file, chunk_size, chunk_uploaded + ) + session_request.execute_query(qry) + file_name = os.path.basename(source_path) return_type = DriveItem(self.context, UrlPath(file_name, self.resource_path)) - qry = create_resumable_file_upload_query( - return_type, source_path, chunk_size, chunk_uploaded + + qry = UploadSessionQuery( + return_type, {"item": DriveItemUploadableProperties(name=file_name)} ) - self.context.add_query(qry) + self.context.add_query(qry).after_query_execute(_start_upload) return return_type def create_upload_session(self, item): diff --git a/office365/onedrive/internal/queries/resumable_file_upload.py b/office365/onedrive/internal/queries/resumable_file_upload.py deleted file mode 100644 index 87f6607e0..000000000 --- a/office365/onedrive/internal/queries/resumable_file_upload.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -from office365.onedrive.driveitems.uploadable_properties import ( - DriveItemUploadableProperties, -) -from office365.runtime.odata.v4.upload_session_request import UploadSessionRequest -from office365.runtime.queries.upload_session import UploadSessionQuery - - -def create_resumable_file_upload_query( - return_type, local_path, chunk_size, chunk_uploaded -): - """ - :type return_type: office365.onedrive.driveitems.driveItem.DriveItem - :type local_path: str - :type chunk_size: int - :type chunk_uploaded: (int)->None - """ - item = DriveItemUploadableProperties() - item.name = os.path.basename(local_path) - qry = UploadSessionQuery(return_type, {"item": item}) - context = return_type.context - - def _start_upload(resp): - """ - :type resp: requests.Response - """ - resp.raise_for_status() - with open(local_path, "rb") as local_file: - session_request = UploadSessionRequest( - local_file, chunk_size, chunk_uploaded - ) - session_request.execute_query(qry) - - context.after_execute(_start_upload) - return qry diff --git a/office365/outlook/internal/queries/attachment_upload.py b/office365/outlook/internal/queries/attachment_upload.py deleted file mode 100644 index c9a32e125..000000000 --- a/office365/outlook/internal/queries/attachment_upload.py +++ /dev/null @@ -1,53 +0,0 @@ -from office365.outlook.mail.attachments.attachment_item import AttachmentItem -from office365.runtime.compat import parse_query_string -from office365.runtime.odata.v4.upload_session_request import UploadSessionRequest -from office365.runtime.queries.upload_session import UploadSessionQuery - - -def create_attachment_upload_query( - binding_type, return_type, source_path, chunk_size=1000000, chunk_uploaded=None -): - """ - :type binding_type: office365.outlook.mail.attachments.collection.AttachmentCollection - :type return_type: FileAttachment - :type source_path: str - :type chunk_size: int - :type chunk_uploaded: (int)->None - """ - qry = UploadSessionQuery( - binding_type, {"AttachmentItem": AttachmentItem.create_file(source_path)} - ) - context = binding_type.context - - def _start_upload(resp): - """ - :type resp: requests.Response - """ - resp.raise_for_status() - with open(source_path, "rb") as local_file: - session_request = UploadSessionRequest( - local_file, chunk_size, chunk_uploaded - ) - - def _construct_request(request): - auth_token = parse_query_string(request.url, "authtoken") - request.set_header("Authorization", "Bearer {0}".format(auth_token)) - - session_request.beforeExecute += _construct_request - - def _process_response(response): - """ - :type response: requests.Response - """ - location = response.headers.get("Location", None) - if location is None: - return - attachment_id = location[location.find("Attachments(") + 13 : -2] - return_type.set_property("id", attachment_id) - - session_request.afterExecute += _process_response - - session_request.execute_query(qry) - - context.after_execute(_start_upload) - return qry diff --git a/office365/outlook/mail/attachments/collection.py b/office365/outlook/mail/attachments/collection.py index b1c133779..1b95e9d24 100644 --- a/office365/outlook/mail/attachments/collection.py +++ b/office365/outlook/mail/attachments/collection.py @@ -1,10 +1,11 @@ import base64 +import requests + from office365.entity_collection import EntityCollection -from office365.outlook.internal.queries.attachment_upload import ( - create_attachment_upload_query, -) from office365.outlook.mail.attachments.attachment import Attachment +from office365.runtime.compat import parse_query_string +from office365.runtime.odata.v4.upload_session_request import UploadSessionRequest from office365.runtime.queries.upload_session import UploadSessionQuery @@ -49,14 +50,41 @@ def resumable_upload(self, source_path, chunk_size=1000000, chunk_uploaded=None) :param int chunk_size: File chunk size :param (int)->None chunk_uploaded: Upload action """ + from office365.outlook.mail.attachments.attachment_item import AttachmentItem from office365.outlook.mail.attachments.file import FileAttachment return_type = FileAttachment(self.context) self.add_child(return_type) - qry = create_attachment_upload_query( - self, return_type, source_path, chunk_size, chunk_uploaded + + qry = UploadSessionQuery( + self, {"AttachmentItem": AttachmentItem.create_file(source_path)} + ) + + def _start_upload(): + with open(source_path, "rb") as local_file: + session_request = UploadSessionRequest( + local_file, chunk_size, chunk_uploaded + ) + + def _construct_request(request): + auth_token = parse_query_string(request.url, "authtoken") + request.set_header("Authorization", "Bearer {0}".format(auth_token)) + + def _process_response(response): + # type: (requests.Response) -> None + location = response.headers.get("Location", None) + if location is None: + return + attachment_id = location[location.find("Attachments(") + 13 : -2] + return_type.set_property("id", attachment_id) + + session_request.beforeExecute += _construct_request + session_request.afterExecute += _process_response + session_request.execute_query(qry) + + self.context.add_query(qry).after_query_execute( + _start_upload, execute_first=True ) - self.context.add_query(qry) return self def create_upload_session(self, attachment_item): diff --git a/office365/outlook/mail/messages/collection.py b/office365/outlook/mail/messages/collection.py index 43027ec7c..5983c7bad 100644 --- a/office365/outlook/mail/messages/collection.py +++ b/office365/outlook/mail/messages/collection.py @@ -16,7 +16,6 @@ def add(self, subject=None, body=None, to_recipients=None, **kwargs): :param str subject: The subject of the message. :param str or ItemBody body: The body of the message. It can be in HTML or text format :param list[str] to_recipients: - :rtype: Message """ if to_recipients is not None: kwargs["toRecipients"] = ClientValueCollection( diff --git a/office365/runtime/odata/v4/upload_session_request.py b/office365/runtime/odata/v4/upload_session_request.py index 3427e2837..05f489ae3 100644 --- a/office365/runtime/odata/v4/upload_session_request.py +++ b/office365/runtime/odata/v4/upload_session_request.py @@ -1,62 +1,57 @@ import os +import typing +from typing import Callable + +import requests +from typing_extensions import Self from office365.runtime.client_request import ClientRequest from office365.runtime.http.http_method import HttpMethod from office365.runtime.http.request_options import RequestOptions +from office365.runtime.queries.upload_session import UploadSessionQuery class UploadSessionRequest(ClientRequest): def __init__(self, file_object, chunk_size, chunk_uploaded=None): - """ - :type file_object: typing.IO - :type chunk_size: int - :type chunk_uploaded: (int) -> None - """ + # type: (typing.IO, int, Callable[[int], None]) -> None super(UploadSessionRequest, self).__init__() self._file_object = file_object self._chunk_size = chunk_size self._chunk_uploaded = chunk_uploaded - self._range_start = 0 - self._range_end = 0 + self._range_data = None def build_request(self, query): - """ - :type query: office365.runtime.queries.upload_session.UploadSessionQuery - """ - range_data = self._read_next() + # type: (UploadSessionQuery) -> Self request = RequestOptions(query.upload_session_url) request.method = HttpMethod.Put - request.set_header("Content-Length", str(len(range_data))) + request.set_header("Content-Length", str(len(self._range_data))) request.set_header( "Content-Range", "bytes {0}-{1}/{2}".format( - self._range_start, self._range_end - 1, self.file_size + self.range_start, self.range_end - 1, self.file_size ), ) request.set_header("Accept", "*/*") - request.data = range_data + request.data = self._range_data return request def process_response(self, response, query): - """ - :type response: requests.Response - :type query: office365.runtime.queries.upload_session.UploadSessionQuery - """ + # type: (requests.Response, UploadSessionQuery) -> None response.raise_for_status() if callable(self._chunk_uploaded): self._chunk_uploaded(self.range_end) - if self.has_pending_read: - self.execute_query(query) - def _read_next(self): - self._range_start = self._file_object.tell() - content = self._file_object.read(self._chunk_size) - self._range_end = self._file_object.tell() - return content + def execute_query(self, query): + # type: (UploadSessionQuery) -> None + for self._range_data in self._read_next(): + super(UploadSessionRequest, self).execute_query(query) - @property - def has_pending_read(self): - return self._range_end < self.file_size + def _read_next(self): + while True: + content = self._file_object.read(self._chunk_size) + if not content: + break + yield content @property def file_size(self): @@ -64,8 +59,10 @@ def file_size(self): @property def range_start(self): - return self._range_start + if self.range_end == 0: + return 0 + return self.range_end - len(self._range_data) @property def range_end(self): - return self._range_end + return self._file_object.tell() diff --git a/office365/search/entity.py b/office365/search/entity.py index c7ffd3322..8aae49baa 100644 --- a/office365/search/entity.py +++ b/office365/search/entity.py @@ -31,7 +31,17 @@ def query(self, query_string, entity_types=None): payload = {"requests": ClientValueCollection(SearchRequest, [search_request])} return_type = ClientResult(self.context, ClientValueCollection(SearchResponse)) qry = ServiceOperationQuery(self, "query", None, payload, None, return_type) - self.context.add_query(qry) + + def _process_response(): + # patch 'resource' nav property + for item in return_type.value: + for hitCs in item.hitsContainers: + for hit in hitCs.hits: + json = hit.resource + hit.resource = Entity(self.context) + self.context.pending_request().map_json(json, hit.resource) + + self.context.add_query(qry).after_query_execute(_process_response) return return_type def query_messages(self, query_string):