From 6a16c8f096d2070e0849519ca6eda33e5ec950c7 Mon Sep 17 00:00:00 2001 From: vgrem Date: Fri, 16 Feb 2024 23:51:26 +0200 Subject: [PATCH] bug fixes & typings updates --- README.md | 2 +- .../directory/applications/grant_perms.py | 10 +- examples/onedrive/files/get_permissions.py | 16 ++++ examples/sharepoint/connect.ipynb | 95 +++++++++++++++++++ .../import_lib.py} | 36 +------ examples/sharepoint/import/import_list.py | 49 ++++++++++ examples/sharepoint/lists/clear.py | 15 +-- examples/sharepoint/lists/read_paged.py | 19 ++++ examples/sharepoint/requirements.txt | 2 + generator/metadata/MicrosoftGraph.xml | 29 ++++++ .../roles/assignment_collection.py | 2 +- .../applications/roles/collection.py | 4 +- .../authentication/method_mode_detail.py | 8 ++ .../directory/authentication/method_target.py | 5 + .../directory/authentication/strength_root.py | 5 + .../identities/conditional_access_root.py | 11 ++- .../serviceprincipals/service_principal.py | 2 +- office365/onedrive/driveitems/driveItem.py | 3 +- .../onedrive/workbooks/functions/functions.py | 9 +- .../outlook/mail/attachments/collection.py | 7 +- office365/outlook/mail/messages/message.py | 2 +- office365/reports/root.py | 24 +++++ office365/runtime/client_object.py | 19 +++- office365/runtime/client_object_collection.py | 31 ++---- office365/runtime/client_result.py | 8 +- office365/runtime/client_runtime_context.py | 18 ++-- office365/runtime/types/event_handler.py | 2 +- office365/search/entity.py | 5 +- .../contentcenter/machinelearning/enabled.py | 7 +- .../contentcenter/machinelearning/hub.py | 12 +-- .../machinelearning/models/collection.py | 2 +- office365/sharepoint/entity.py | 9 +- office365/sharepoint/files/collection.py | 29 +++--- office365/sharepoint/files/file.py | 35 +++---- office365/sharepoint/folders/folder.py | 40 ++++---- office365/sharepoint/lists/list.py | 9 ++ .../permissions/irm/file_settings.py | 14 +-- .../sharepoint/permissions/irm/settings.py | 22 ++--- .../tenant/administration/tenant.py | 1 + office365/sharepoint/webs/web.py | 21 ++-- tests/directory/test_authentication.py | 11 +++ tests/directory/test_security.py | 4 + tests/onedrive/test_excel_functions.py | 9 ++ tests/outlook/test_messages.py | 7 +- 44 files changed, 473 insertions(+), 197 deletions(-) create mode 100644 examples/onedrive/files/get_permissions.py create mode 100644 examples/sharepoint/connect.ipynb rename examples/sharepoint/{lists/custom/data_generator.py => import/import_lib.py} (52%) create mode 100644 examples/sharepoint/import/import_list.py create mode 100644 examples/sharepoint/lists/read_paged.py create mode 100644 office365/directory/authentication/method_mode_detail.py create mode 100644 office365/directory/authentication/method_target.py create mode 100644 office365/directory/authentication/strength_root.py create mode 100644 tests/directory/test_authentication.py diff --git a/README.md b/README.md index b06bebaea..28ee6176b 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ The list of examples: - Working with lists and list items - [create a list item](examples/sharepoint/lists/data_generator.py) - - [read a list item](examples/sharepoint/lists/read_large.py) + - [read a list item](examples/sharepoint/lists/read_paged.py) - [update a list item](examples/sharepoint/listitems/update_batch.py) - [delete a list item](examples/sharepoint/listitems/delete.py) diff --git a/examples/directory/applications/grant_perms.py b/examples/directory/applications/grant_perms.py index 353e04e6a..2f7f46716 100644 --- a/examples/directory/applications/grant_perms.py +++ b/examples/directory/applications/grant_perms.py @@ -35,12 +35,16 @@ # select specific appRole -names = ["Directory.AccessAsUser.All", "ThreatHunting.Read.All"] -app_role = resource.app_roles["Directory.AccessAsUser.All"] +names = [ + "Directory.AccessAsUser.All", + "ThreatHunting.Read.All", + "UserAuthenticationMethod.Read.All", +] +app_role = resource.app_roles["Policy.Read.All"] # Step 2: Grant an app role to a client app app = client.applications.get_by_app_id(test_client_id) -resource.grant(app, app_role).execute_query() +# resource.grant(app, app_role).execute_query() # Step 3. Print app role assignments diff --git a/examples/onedrive/files/get_permissions.py b/examples/onedrive/files/get_permissions.py new file mode 100644 index 000000000..59e6e92e3 --- /dev/null +++ b/examples/onedrive/files/get_permissions.py @@ -0,0 +1,16 @@ +""" +List sharing permissions on a driveItem + +https://learn.microsoft.com/en-us/graph/api/driveitem-list-permissions?view=graph-rest-1.0 +""" + +from office365.graph_client import GraphClient +from tests import test_client_id, test_password, test_tenant, test_username + +client = GraphClient.with_username_and_password( + test_tenant, test_client_id, test_username, test_password +) +file_path = "Archive/Financial Sample.xlsx" +file_item = client.me.drive.root.get_by_path(file_path) +permissions = file_item.permissions.get().execute_query() +print(permissions) diff --git a/examples/sharepoint/connect.ipynb b/examples/sharepoint/connect.ipynb new file mode 100644 index 000000000..f0201d5c5 --- /dev/null +++ b/examples/sharepoint/connect.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 31, + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-02-11T10:23:26.221156714Z", + "start_time": "2024-02-11T10:23:26.197964378Z" + } + }, + "outputs": [], + "source": [ + "from office365.sharepoint.client_context import ClientContext\n", + "client_id = \"4b7eb3df-afc3-4b7d-ae1d-629f22a3fe42\" \n", + "site_url = \"https://mediadev8.sharepoint.com\"\n", + "tenant_name = \"mediadev8.onmicrosoft.com\"" + ] + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "ctx = ClientContext(site_url).with_interactive(tenant_name, client_id)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-02-11T10:23:27.733534468Z", + "start_time": "2024-02-11T10:23:27.725507872Z" + } + }, + "id": "b5504d6537baaebd", + "execution_count": 32 + }, + { + "cell_type": "code", + "outputs": [ + { + "data": { + "text/plain": "vgrem@mediadev8.onmicrosoft.com" + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "me = ctx.web.current_user.get().execute_query()\n", + "me" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-02-11T10:23:36.770634654Z", + "start_time": "2024-02-11T10:23:36.520379564Z" + } + }, + "id": "e61a0947e8ef00d5", + "execution_count": 34 + }, + { + "cell_type": "code", + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "403e49d95b83b674" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/sharepoint/lists/custom/data_generator.py b/examples/sharepoint/import/import_lib.py similarity index 52% rename from examples/sharepoint/lists/custom/data_generator.py rename to examples/sharepoint/import/import_lib.py index 2de43c3a2..ee88526dc 100644 --- a/examples/sharepoint/lists/custom/data_generator.py +++ b/examples/sharepoint/import/import_lib.py @@ -6,7 +6,7 @@ from tests import test_team_site_url, test_user_credentials -def generate_documents(context, amount): +def run(context, amount): """ :type context: ClientContext :type amount: int @@ -36,38 +36,6 @@ def generate_documents(context, amount): print("File '{0}' has been uploaded".format(target_file.serverRelativeUrl)) -def generate_contacts(context, amount): - """ - :type context: ClientContext - :type amount: int - """ - contacts_list = context.web.lists.get_by_title("Contacts_Large") - - fake = Faker() - for idx in range(0, amount): - contact_properties = { - "Title": fake.name(), - "FullName": fake.name(), - "Email": fake.email(), - "Company": fake.company(), - "WorkPhone": fake.phone_number(), - "WorkAddress": fake.street_address(), - "WorkCity": fake.city(), - "WorkZip": fake.postcode(), - "WorkCountry": fake.country(), - "WebPage": {"Url": fake.url()}, - } - # contact_item = contacts_list.add_item(contact_properties).execute_query() - contact_item = contacts_list.add_item(contact_properties) - print( - "({0} of {1}) Contact '{2}' has been created".format( - idx, amount, contact_item.properties["Title"] - ) - ) - ctx.execute_batch() - - if __name__ == "__main__": ctx = ClientContext(test_team_site_url).with_credentials(test_user_credentials) - # generate_contacts(ctx, 5000) - generate_documents(ctx, 100) + run(ctx, 100) diff --git a/examples/sharepoint/import/import_list.py b/examples/sharepoint/import/import_list.py new file mode 100644 index 000000000..7d6aafcde --- /dev/null +++ b/examples/sharepoint/import/import_list.py @@ -0,0 +1,49 @@ +from faker import Faker + +from office365.sharepoint.client_context import ClientContext +from tests import test_team_site_url, test_user_credentials + + +def print_progress(items_count): + print("{0} list items has been created".format(items_count)) + + +def load_data_source(amount=1000): + fake = Faker() + contacts = [] + for idx in range(0, amount): + contact = { + "Title": fake.name(), + "FullName": fake.name(), + "Email": fake.email(), + "Company": fake.company(), + "WorkPhone": fake.phone_number(), + "WorkAddress": fake.street_address(), + "WorkCity": fake.city(), + "WorkZip": fake.postcode(), + "WorkCountry": fake.country() + # "WebPage": {"Url": fake.url()}, + } + contacts.append(contact) + + return contacts + + +def run(context): + # type: (ClientContext) -> None + contacts_data = load_data_source() + contacts_list = context.web.lists.get_by_title("Contacts_Large") + for idx, contact in enumerate(contacts_data): + # contact_item = contacts_list.add_item(contact).execute_query() + contacts_list.add_item(contact) + # print( + # "({0} of {1}) Contact '{2}' has been created".format( + # idx, len(contacts_data), contact_item.properties["Title"] + # ) + # ) + ctx.execute_batch(items_per_batch=2, success_callback=print_progress) + + +if __name__ == "__main__": + ctx = ClientContext(test_team_site_url).with_credentials(test_user_credentials) + run(ctx) diff --git a/examples/sharepoint/lists/clear.py b/examples/sharepoint/lists/clear.py index 0befa6d6b..00903925b 100644 --- a/examples/sharepoint/lists/clear.py +++ b/examples/sharepoint/lists/clear.py @@ -2,13 +2,14 @@ This example deletes a SharePoint all the list items. """ from office365.sharepoint.client_context import ClientContext -from office365.sharepoint.listitems.listitem import ListItem from tests import test_client_credentials, test_team_site_url + +def print_progress(items_count): + print("List items count: {0}".format(target_list.item_count)) + + ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) -target_list = ctx.web.lists.get_by_title("Tasks") -items = target_list.items.get().execute_query() -for item in items: # type: ListItem - item.delete_object() -ctx.execute_batch() -print("Items deleted count: {0}".format(len(items))) +target_list = ctx.web.lists.get_by_title("Contacts_Large") +target_list.clear().get().execute_batch() +print("List items count: {0}".format(target_list.item_count)) diff --git a/examples/sharepoint/lists/read_paged.py b/examples/sharepoint/lists/read_paged.py new file mode 100644 index 000000000..3948f6d1f --- /dev/null +++ b/examples/sharepoint/lists/read_paged.py @@ -0,0 +1,19 @@ +from office365.sharepoint.client_context import ClientContext +from office365.sharepoint.listitems.collection import ListItemCollection +from office365.sharepoint.listitems.listitem import ListItem +from tests import test_client_credentials, test_team_site_url + + +def print_progress(items): + # type: (ListItemCollection) -> None + print("Items read: {0}".format(len(items))) + + +ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) +large_list = ctx.web.lists.get_by_title("Contacts_Large") +paged_items = ( + large_list.items.paged(5000, page_loaded=print_progress).get().execute_query() +) +for index, item in enumerate(paged_items): # type: int, ListItem + pass + # print("{0}: {1}".format(index, item.id)) diff --git a/examples/sharepoint/requirements.txt b/examples/sharepoint/requirements.txt index e69de29bb..5da331cf6 100644 --- a/examples/sharepoint/requirements.txt +++ b/examples/sharepoint/requirements.txt @@ -0,0 +1,2 @@ +numpy +pandas diff --git a/generator/metadata/MicrosoftGraph.xml b/generator/metadata/MicrosoftGraph.xml index c97733159..17c31b7f8 100644 --- a/generator/metadata/MicrosoftGraph.xml +++ b/generator/metadata/MicrosoftGraph.xml @@ -20587,6 +20587,7 @@ + @@ -20610,6 +20611,16 @@ + + + + + + + + + + @@ -23303,6 +23314,7 @@ + @@ -23338,6 +23350,20 @@ + + + + + + + + + + + + + + @@ -26287,6 +26313,7 @@ + @@ -26305,6 +26332,7 @@ + @@ -26618,6 +26646,7 @@ + diff --git a/office365/directory/applications/roles/assignment_collection.py b/office365/directory/applications/roles/assignment_collection.py index f42b6f723..d3afd452d 100644 --- a/office365/directory/applications/roles/assignment_collection.py +++ b/office365/directory/applications/roles/assignment_collection.py @@ -2,7 +2,7 @@ from office365.entity_collection import EntityCollection -class AppRoleAssignmentCollection(EntityCollection): +class AppRoleAssignmentCollection(EntityCollection[AppRoleAssignment]): def __init__(self, context, resource_path=None): super(AppRoleAssignmentCollection, self).__init__( context, AppRoleAssignment, resource_path diff --git a/office365/directory/applications/roles/collection.py b/office365/directory/applications/roles/collection.py index 97e0cdd54..5f4053841 100644 --- a/office365/directory/applications/roles/collection.py +++ b/office365/directory/applications/roles/collection.py @@ -8,6 +8,6 @@ class AppRoleCollection(ClientValueCollection[AppRole]): def __init__(self, initial_values=None): super(AppRoleCollection, self).__init__(AppRole, initial_values) - def __getitem__(self, name): + def __getitem__(self, key): # type: (str) -> AppRole - return next(iter([item for item in self._data if item.value == name]), None) + return next(iter([item for item in self._data if item.value == key]), None) diff --git a/office365/directory/authentication/method_mode_detail.py b/office365/directory/authentication/method_mode_detail.py new file mode 100644 index 000000000..da04eff74 --- /dev/null +++ b/office365/directory/authentication/method_mode_detail.py @@ -0,0 +1,8 @@ +from office365.entity import Entity + + +class AuthenticationMethodModeDetail(Entity): + """ + The details of the authenticationMethodModes objects that can be defined for the allowedCombinations property + of the authenticationstrengthpolicy. + """ diff --git a/office365/directory/authentication/method_target.py b/office365/directory/authentication/method_target.py new file mode 100644 index 000000000..ee9d3e35e --- /dev/null +++ b/office365/directory/authentication/method_target.py @@ -0,0 +1,5 @@ +from office365.entity import Entity + + +class AuthenticationMethodTarget(Entity): + """""" diff --git a/office365/directory/authentication/strength_root.py b/office365/directory/authentication/strength_root.py new file mode 100644 index 000000000..8a107b790 --- /dev/null +++ b/office365/directory/authentication/strength_root.py @@ -0,0 +1,5 @@ +from office365.entity import Entity + + +class AuthenticationStrengthRoot(Entity): + """The authenticationStrengthRoot resource is the entry point for the authentication strengths object model.""" diff --git a/office365/directory/identities/conditional_access_root.py b/office365/directory/identities/conditional_access_root.py index 28f4d5d0a..fe7f57edd 100644 --- a/office365/directory/identities/conditional_access_root.py +++ b/office365/directory/identities/conditional_access_root.py @@ -1,8 +1,17 @@ from office365.entity import Entity +from office365.runtime.paths.resource_path import ResourcePath class ConditionalAccessRoot(Entity): """The conditionalAccessRoot resource is the entry point for the Conditional Access (CA) object model. It doesn't contain any usable properties.""" - pass + @property + def authentication_strength(self): + """The entry point for the Conditional Access (CA) object model.""" + return self.properties.get( + "authenticationStrength", + ConditionalAccessRoot( + self.context, ResourcePath("authenticationStrength", self.resource_path) + ), + ) diff --git a/office365/directory/serviceprincipals/service_principal.py b/office365/directory/serviceprincipals/service_principal.py index c986ec9c9..87dfcfa7a 100644 --- a/office365/directory/serviceprincipals/service_principal.py +++ b/office365/directory/serviceprincipals/service_principal.py @@ -104,7 +104,7 @@ def add_token_signing_certificate(self, display_name, end_datetime=None): def grant(self, app, app_role): # type: (Application|str, AppRole|str) -> Self """ - Revokes an app role assignment to a client service principal + Grants an app role assignment to a client service principal :param Application or str app: Application object or app identifier :param AppRole or str app_role: AppRole object or name """ diff --git a/office365/onedrive/driveitems/driveItem.py b/office365/onedrive/driveitems/driveItem.py index 850347fad..3d680a23f 100644 --- a/office365/onedrive/driveitems/driveItem.py +++ b/office365/onedrive/driveitems/driveItem.py @@ -209,7 +209,8 @@ def resumable_upload(self, source_path, chunk_size=2000000, chunk_uploaded=None) :param int chunk_size: chunk size """ - def _start_upload(): + def _start_upload(result): + # type: (ClientResult[UploadSession]) -> None with open(source_path, "rb") as local_file: session_request = UploadSessionRequest( local_file, chunk_size, chunk_uploaded diff --git a/office365/onedrive/workbooks/functions/functions.py b/office365/onedrive/workbooks/functions/functions.py index aec5ee671..248b7a860 100644 --- a/office365/onedrive/workbooks/functions/functions.py +++ b/office365/onedrive/workbooks/functions/functions.py @@ -48,11 +48,14 @@ def accr_int(self, issue, first_interest, settlement, rate, par, frequency): def days(self, start_date, end_date): """Returns the number of days between two dates. - :param datetime start_date: Two dates between which you want to know the number of days. - :param datetime end_date: Two dates between which you want to know the number of days. + :param datetime.datetime start_date: Two dates between which you want to know the number of days. + :param datetime.datetime end_date: Two dates between which you want to know the number of days. """ return_type = WorkbookFunctionResult(self.context) - payload = {"startDate": start_date, "endDate": end_date} + payload = { + "startDate": start_date.isoformat() + "Z", + "endDate": end_date.isoformat() + "Z", + } qry = ServiceOperationQuery(self, "days", None, payload, None, return_type) self.context.add_query(qry) return return_type diff --git a/office365/outlook/mail/attachments/collection.py b/office365/outlook/mail/attachments/collection.py index 1b95e9d24..ac40c124e 100644 --- a/office365/outlook/mail/attachments/collection.py +++ b/office365/outlook/mail/attachments/collection.py @@ -4,7 +4,10 @@ from office365.entity_collection import EntityCollection from office365.outlook.mail.attachments.attachment import Attachment +from office365.runtime.client_result import ClientResult from office365.runtime.compat import parse_query_string +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.queries.upload_session import UploadSessionQuery @@ -60,13 +63,15 @@ def resumable_upload(self, source_path, chunk_size=1000000, chunk_uploaded=None) self, {"AttachmentItem": AttachmentItem.create_file(source_path)} ) - def _start_upload(): + def _start_upload(result): + # type: (ClientResult[UploadSession]) -> None with open(source_path, "rb") as local_file: session_request = UploadSessionRequest( local_file, chunk_size, chunk_uploaded ) def _construct_request(request): + # type: (RequestOptions) -> None auth_token = parse_query_string(request.url, "authtoken") request.set_header("Authorization", "Bearer {0}".format(auth_token)) diff --git a/office365/outlook/mail/messages/message.py b/office365/outlook/mail/messages/message.py index 4b9b5d426..362fef300 100644 --- a/office365/outlook/mail/messages/message.py +++ b/office365/outlook/mail/messages/message.py @@ -103,7 +103,7 @@ def upload_attachment(self, file_path, chunk_uploaded=None): if a file that's smaller than 3 MB, then add_file_attachment method is utilized :param str file_path: - :param ()->None chunk_uploaded: Upload action + :param (int)->None chunk_uploaded: Upload action """ max_upload_chunk = 1000000 * 3 file_size = os.stat(file_path).st_size diff --git a/office365/reports/root.py b/office365/reports/root.py index 4adc0b063..7b77715aa 100644 --- a/office365/reports/root.py +++ b/office365/reports/root.py @@ -225,6 +225,30 @@ def get_sharepoint_activity_user_detail(self, period): self.context.add_query(qry) return qry.return_type + def get_sharepoint_site_usage_detail(self, period): + """ + Get details about SharePoint site usage. + + :param str period: Specifies the length of time over which the report is aggregated. + The supported values for {period_value} are: D7, D30, D90, and D180. These values follow the format + Dn where n represents the number of days over which the report is aggregated. Required. + """ + qry = create_report_query(self, "getSharePointSiteUsageDetail", period) + self.context.add_query(qry) + return qry.return_type + + def get_sharepoint_site_usage_site_counts(self, period): + """ + Get the trend of total and active site count during the reporting period. + + :param str period: Specifies the length of time over which the report is aggregated. + The supported values for {period_value} are: D7, D30, D90, and D180. These values follow the format + Dn where n represents the number of days over which the report is aggregated. Required. + """ + qry = create_report_query(self, "getSharePointSiteUsageSiteCounts", period) + self.context.add_query(qry) + return qry.return_type + @property def authentication_methods(self): """Container for navigation properties for Azure AD authentication methods resources.""" diff --git a/office365/runtime/client_object.py b/office365/runtime/client_object.py index 85f819939..1270ca7e4 100644 --- a/office365/runtime/client_object.py +++ b/office365/runtime/client_object.py @@ -7,6 +7,7 @@ from office365.runtime.client_runtime_context import ClientRuntimeContext from office365.runtime.client_value import ClientValue +from office365.runtime.http.request_options import RequestOptions from office365.runtime.odata.json_format import ODataJsonFormat from office365.runtime.odata.query_options import QueryOptions from office365.runtime.odata.type import ODataType @@ -73,10 +74,16 @@ def execute_query_retry( ) return self - def after_execute(self, action): - # type: (Callable[[Self], None]) -> Self + def before_execute(self, action): + # type: (Callable[[RequestOptions], None]) -> Self """Attach an event handler to client object which gets triggered after query is submitted to server""" - self._context.after_query_execute(action, self) + self.context.before_execute(action) + return self + + def after_execute(self, action, execute_first=False): + # type: (Callable[[Self], None], bool) -> Self + """Attach an event handler to client object which gets triggered after query is submitted to server""" + self.context.after_query_execute(action, execute_first) return self def get(self): @@ -180,7 +187,11 @@ def ensure_properties(self, names, action, *args, **kwargs): from office365.runtime.queries.read_entity import ReadEntityQuery qry = ReadEntityQuery(self, names_to_include) - self.context.add_query(qry).after_query_execute(action, *args, **kwargs) + + def _after_loaded(return_type): + action(*args, **kwargs) + + self.context.add_query(qry).after_query_execute(_after_loaded) else: action(*args, **kwargs) return self diff --git a/office365/runtime/client_object_collection.py b/office365/runtime/client_object_collection.py index c7fadcacd..4b4cfa912 100644 --- a/office365/runtime/client_object_collection.py +++ b/office365/runtime/client_object_collection.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Generic, Iterator, List, Optional, Type, TypeVar +from typing import Callable, Generic, Iterator, List, Optional, Type, TypeVar from typing_extensions import Self @@ -147,12 +147,6 @@ def paged(self, page_size=None, page_loaded=None): self.top(page_size) return self - def after_execute(self, action, *args, **kwargs): - # type: (Callable[[Self, Any, Any], None], Any, Any) -> Self - super(ClientObjectCollection, self).after_execute(action, *args, **kwargs) - self._page_loaded += action - return self - def get(self): # type: () -> Self @@ -160,39 +154,30 @@ def _loaded(col): # type: (Self) -> None self._page_loaded.notify(self) - self.context.load(self, after_loaded=_loaded) + self.context.load(self).after_query_execute(_loaded) return self def get_all(self, page_size=None, page_loaded=None): # type: (int, Callable[[Self], None]) -> Self """Gets all the items in a collection, regardless of the size.""" - self.paged(page_size, page_loaded) - def _page_loaded(col): # type: (Self) -> None - self._page_loaded.notify(col) if self.has_next: - self._get_next(after_loaded=_page_loaded) + self._get_next().after_execute(_page_loaded) - self.context.load(self, after_loaded=_page_loaded) + self.paged(page_size, page_loaded).get().after_execute(_page_loaded) return self - def _get_next(self, after_loaded=None): - # type: (Optional[EventHandler]) -> Self - """ - Submit a request to retrieve next collection of items - :param (ClientObjectCollection) -> None after_loaded: Page loaded event - """ + def _get_next(self): + # type: () -> Self + """Submit a request to retrieve next collection of items""" def _construct_request(request): # type: (RequestOptions) -> None request.url = self._next_request_url - self.context.load( - self, before_loaded=_construct_request, after_loaded=after_loaded - ) - return self + return self.get().before_execute(_construct_request) def first(self, expression): # type: (str) -> T diff --git a/office365/runtime/client_result.py b/office365/runtime/client_result.py index 2bab2063e..efbe83e3b 100644 --- a/office365/runtime/client_result.py +++ b/office365/runtime/client_result.py @@ -1,5 +1,5 @@ import copy -from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, TypeVar +from typing import TYPE_CHECKING, Callable, Generic, Optional, TypeVar from typing_extensions import Self @@ -27,10 +27,10 @@ def before_execute(self, action): self._context.before_query_execute(action) return self - def after_execute(self, action): - # type: (Callable[[Self], None]) -> Self + def after_execute(self, action, execute_first=False): + # type: (Callable[[Self], None], bool) -> Self """Attach an event handler which is triggered after query is submitted to server""" - self._context.after_query_execute(action, self) + self._context.after_query_execute(action, execute_first) return self def set_property(self, key, value, persist_changes=False): diff --git a/office365/runtime/client_runtime_context.py b/office365/runtime/client_runtime_context.py index 04422f9ef..77a1a8328 100644 --- a/office365/runtime/client_runtime_context.py +++ b/office365/runtime/client_runtime_context.py @@ -1,8 +1,9 @@ import abc from time import sleep -from typing import TYPE_CHECKING, Any, AnyStr, Callable, List +from typing import TYPE_CHECKING, AnyStr, Callable, List import requests +from requests import Response from typing_extensions import Self from office365.runtime.client_request import ClientRequest @@ -135,34 +136,33 @@ def _process_request(request): self.pending_request().beforeExecute += _process_request return self - def after_query_execute(self, action, *args, **kwargs): - # type: (Callable[..., None], Any, Any) -> Self + def after_query_execute(self, action, execute_first=False): + # type: (Callable[[T], None], bool) -> Self """Attach an event handler which is triggered after query is submitted to server""" if len(self._queries) == 0: return query = self._queries[-1] def _process_response(resp): - # type: (requests.Response) -> None + # type: (Response) -> None resp.raise_for_status() if self.current_query.id == query.id: self.pending_request().afterExecute -= _process_response - action(*args, **kwargs) + action(query.return_type) self.pending_request().afterExecute += _process_response - execute_first = kwargs.pop("execute_first", False) if execute_first and len(self._queries) > 1: self._queries.insert(0, self._queries.pop()) return self def after_execute(self, action, once=True): - # type: (Callable[[requests.Response], None], bool) -> Self + # type: (Callable[[Response], None], bool) -> Self """Attach an event handler which is triggered after request is submitted to server""" def _process_response(response): - # type: (requests.Response) -> None + # type: (Response) -> None if once: self.pending_request().afterExecute -= _process_response action(response) @@ -171,7 +171,7 @@ def _process_response(response): return self def execute_request_direct(self, path): - # type: (str) -> requests.Response + # type: (str) -> Response full_url = "".join([self.service_root_url(), "/", path]) request = RequestOptions(full_url) return self.pending_request().execute_request_direct(request) diff --git a/office365/runtime/types/event_handler.py b/office365/runtime/types/event_handler.py index 908cb67ef..fa43e61fd 100644 --- a/office365/runtime/types/event_handler.py +++ b/office365/runtime/types/event_handler.py @@ -35,7 +35,7 @@ def __len__(self): def notify(self, *args, **kwargs): # type: (Any, Any) -> None - for listener in self._listeners: + for listener in self._listeners[:]: if self._once: self._listeners.remove(listener) listener(*args, **kwargs) diff --git a/office365/search/entity.py b/office365/search/entity.py index 9b03ebb12..8002ba25b 100644 --- a/office365/search/entity.py +++ b/office365/search/entity.py @@ -38,8 +38,9 @@ def _patch_hit(search_hit): # self.context.pending_request().map_json(search_hit.resource, resource) # search_hit.set_property("resource", resource) - def _process_response(): - for item in return_type.value: + def _process_response(result): + # type: (ClientResult[ClientValueCollection[SearchResponse]]) -> None + for item in result.value: for hcs in item.hitsContainers: [_patch_hit(hit) for hit in hcs.hits] diff --git a/office365/sharepoint/contentcenter/machinelearning/enabled.py b/office365/sharepoint/contentcenter/machinelearning/enabled.py index e07dc0258..800707880 100644 --- a/office365/sharepoint/contentcenter/machinelearning/enabled.py +++ b/office365/sharepoint/contentcenter/machinelearning/enabled.py @@ -1,12 +1,13 @@ +from typing import Optional + from office365.sharepoint.entity import Entity class SPMachineLearningEnabled(Entity): @property def is_syntex_payg_enabled(self): - """ - :rtype: bool or None - """ + # type: () -> Optional[bool] + """ """ return self.properties.get("IsSyntexPAYGEnabled", None) @property diff --git a/office365/sharepoint/contentcenter/machinelearning/hub.py b/office365/sharepoint/contentcenter/machinelearning/hub.py index 3af4813cd..205e06256 100644 --- a/office365/sharepoint/contentcenter/machinelearning/hub.py +++ b/office365/sharepoint/contentcenter/machinelearning/hub.py @@ -1,3 +1,5 @@ +from typing import Optional + from office365.runtime.client_result import ClientResult from office365.runtime.client_value_collection import ClientValueCollection from office365.runtime.paths.resource_path import ResourcePath @@ -73,16 +75,14 @@ def get_property(self, name, default_value=None): @property def is_default_content_center(self): - """ - :rtype: bool - """ + # type: () -> Optional[bool] + """ """ return self.properties.get("IsDefaultContentCenter", None) @property def machine_learning_capture_enabled(self): - """ - :rtype: bool - """ + # type: () -> Optional[bool] + """ """ return self.properties.get("MachineLearningCaptureEnabled", None) @property diff --git a/office365/sharepoint/contentcenter/machinelearning/models/collection.py b/office365/sharepoint/contentcenter/machinelearning/models/collection.py index c9b2f8c03..7683508a1 100644 --- a/office365/sharepoint/contentcenter/machinelearning/models/collection.py +++ b/office365/sharepoint/contentcenter/machinelearning/models/collection.py @@ -5,7 +5,7 @@ from office365.sharepoint.entity_collection import EntityCollection -class SPMachineLearningModelCollection(EntityCollection): +class SPMachineLearningModelCollection(EntityCollection[SPMachineLearningModel]): def __init__(self, context, resource_path=None): super(SPMachineLearningModelCollection, self).__init__( context, SPMachineLearningModel, resource_path diff --git a/office365/sharepoint/entity.py b/office365/sharepoint/entity.py index affab7ee3..f0a1615bb 100644 --- a/office365/sharepoint/entity.py +++ b/office365/sharepoint/entity.py @@ -1,4 +1,6 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable + +from typing_extensions import Self from office365.runtime.client_object import ClientObject from office365.runtime.queries.delete_entity import DeleteEntityQuery @@ -11,6 +13,11 @@ class Entity(ClientObject): """SharePoint specific entity""" + def execute_batch(self, items_per_batch=100, success_callback=None): + # type: (int, Callable[[int], None]) -> Self + """Construct and submit to a server a batch request""" + return self.context.execute_batch(items_per_batch, success_callback) + def with_credentials(self, credentials): """ :type self: T diff --git a/office365/sharepoint/files/collection.py b/office365/sharepoint/files/collection.py index b53bb35e8..acc7a92c2 100644 --- a/office365/sharepoint/files/collection.py +++ b/office365/sharepoint/files/collection.py @@ -75,44 +75,43 @@ def create_upload_session( if not hasattr(file, "read"): file = open(file, "rb") auto_close = True - else: - pass file_size = os.fstat(file.fileno()).st_size file_name = file_name if file_name else os.path.basename(file.name) upload_id = str(uuid.uuid4()) - def _upload_session(return_type, return_file): - # type: (File|ClientResult, File) -> None - if return_file is None: - return_file = return_type + def _upload(return_type): + # type: (File) -> None + + def _after_uploaded(result): + # type: (ClientResult) -> None + _upload(return_type) uploaded_bytes = file.tell() if callable(chunk_uploaded): chunk_uploaded(uploaded_bytes, **kwargs) + content = file.read(chunk_size) if uploaded_bytes == file_size: if auto_close and not file.closed: file.close() return - content = file.read(chunk_size) - if uploaded_bytes == 0: - return_file.start_upload(upload_id, content).after_execute( - _upload_session, return_file + return_type.start_upload(upload_id, content).after_execute( + _after_uploaded ) elif uploaded_bytes + len(content) < file_size: - return_file.continue_upload( + return_type.continue_upload( upload_id, uploaded_bytes, content - ).after_execute(_upload_session, return_file) + ).after_execute(_after_uploaded) else: - return_file.finish_upload( + return_type.finish_upload( upload_id, uploaded_bytes, content - ).after_execute(_upload_session, return_file) + ).after_execute(_upload) if file_size > chunk_size: - return self.add(file_name, None, True).after_execute(_upload_session, None) + return self.add(file_name, None, True).after_execute(_upload) else: return self.add(file_name, file.read(), True) diff --git a/office365/sharepoint/files/file.py b/office365/sharepoint/files/file.py index 3892021d6..8b55bf060 100644 --- a/office365/sharepoint/files/file.py +++ b/office365/sharepoint/files/file.py @@ -59,10 +59,7 @@ class File(AbstractFile): or a file in a folder.""" def __repr__(self): - if self.server_relative_path: - return repr(self.server_relative_path) - else: - return self.serverRelativeUrl or self.unique_id or self.entity_type_name + return self.serverRelativeUrl or self.unique_id or self.entity_type_name def __str__(self): return self.name or self.entity_type_name @@ -332,19 +329,18 @@ def moveto(self, destination, flag): :param int flag: Specifies the kind of move operation. """ - def _update_file(return_type, new_file_url): - return_type.set_property("ServerRelativeUrl", new_file_url) - def _moveto(destination_folder): - """ - :type destination_folder: Folder - """ - file_path = "/".join([str(destination_folder.serverRelativeUrl), self.name]) + # type: (Folder) -> None + file_url = "/".join([str(destination_folder.serverRelativeUrl), self.name]) - params = {"newurl": file_path, "flags": flag} + params = {"newurl": file_url, "flags": flag} qry = ServiceOperationQuery(self, "moveto", params) self.context.add_query(qry) - self.context.after_query_execute(_update_file, self, file_path) + + def _update_file(return_type): + self.set_property("ServerRelativeUrl", file_url) + + self.context.after_query_execute(_update_file) def _source_file_resolved(): if isinstance(destination, Folder): @@ -366,10 +362,6 @@ def move_to_using_path(self, destination, flag): :param int flag: Specifies the kind of move operation. """ - def _update_file(return_type, new_file_url): - # type: (File, str) -> None - return_type.set_property("ServerRelativePath", new_file_url) - def _move_to_using_path(destination_folder): # type: (Folder) -> None file_path = "/".join( @@ -377,9 +369,12 @@ def _move_to_using_path(destination_folder): ) params = {"DecodedUrl": file_path, "moveOperations": flag} qry = ServiceOperationQuery(self, "MoveToUsingPath", params) - self.context.add_query(qry).after_query_execute( - _update_file, self, file_path - ) + + def _update_file(return_type): + # type: (File) -> None + self.set_property("ServerRelativePath", file_path) + + self.context.add_query(qry).after_query_execute(_update_file) def _source_file_resolved(): if isinstance(destination, Folder): diff --git a/office365/sharepoint/folders/folder.py b/office365/sharepoint/folders/folder.py index 8b844a1d0..eae90fb58 100644 --- a/office365/sharepoint/folders/folder.py +++ b/office365/sharepoint/folders/folder.py @@ -30,7 +30,7 @@ class Folder(Entity): """Represents a folder in a SharePoint Web site.""" def __str__(self): - return self.name + return self.name or self.entity_type_name def __repr__(self): return self.serverRelativeUrl or self.unique_id or self.entity_type_name @@ -69,14 +69,14 @@ def get_folders(self, recursive=False): return_type = FolderCollection(self.context, self.folders.resource_path, self) - def _loaded(parent): + def _get_folders(parent): # type: ("Folder") -> None [return_type.add_child(f) for f in parent.folders] if recursive: for folder in parent.folders: - folder.ensure_properties(["Folders"], _loaded, parent=folder) + folder.ensure_properties(["Folders"], _get_folders, parent=folder) - self.ensure_properties(["Folders"], _loaded, parent=self) + self.ensure_properties(["Folders"], _get_folders, parent=self) return return_type def get_files(self, recursive=False): @@ -88,16 +88,16 @@ def get_files(self, recursive=False): return_type = FileCollection(self.context, self.files.resource_path, self) - def _loaded(parent): + def _get_files(parent): # type: ("Folder") -> None [return_type.add_child(f) for f in parent.files] if recursive: for folder in parent.folders: folder.ensure_properties( - ["Files", "Folders"], _loaded, parent=folder + ["Files", "Folders"], _get_files, parent=folder ) - self.ensure_properties(["Files", "Folders"], _loaded, parent=self) + self.ensure_properties(["Files", "Folders"], _get_files, parent=self) return return_type def get_sharing_information(self): @@ -115,18 +115,17 @@ def move_to(self, destination): where to move a folder. """ - def _update_folder(url): - self.set_property("ServerRelativeUrl", url) - def _move_to(destination_folder): # type: ("Folder") -> None destination_url = "/".join( [destination_folder.serverRelativeUrl, self.name] ) qry = ServiceOperationQuery(self, "MoveTo", {"newUrl": destination_url}) - self.context.add_query(qry).after_query_execute( - _update_folder, destination_url - ) + + def _update_folder(return_type): + self.set_property("ServerRelativeUrl", destination_url) + + self.context.add_query(qry).after_query_execute(_update_folder) def _source_folder_resolved(): if isinstance(destination, Folder): @@ -146,21 +145,20 @@ def move_to_using_path(self, destination): where to move a folder. """ - def _update_folder(url): - self.set_property("ServerRelativePath", url) - def _move_to_using_path(destination_folder): # type: ("Folder") -> None - destination_url = "/".join( + destination_path = "/".join( [str(destination_folder.server_relative_path), self.name] ) qry = ServiceOperationQuery( - self, "MoveToUsingPath", {"DecodedUrl": destination_url} - ) - self.context.add_query(qry).after_query_execute( - _update_folder, destination_url + self, "MoveToUsingPath", {"DecodedUrl": destination_path} ) + def _update_folder(url): + self.set_property("ServerRelativePath", destination_path) + + self.context.add_query(qry).after_query_execute(_update_folder) + def _source_folder_resolved(): if isinstance(destination, Folder): destination.ensure_property( diff --git a/office365/sharepoint/lists/list.py b/office365/sharepoint/lists/list.py index 4424e2038..04ccb0f93 100644 --- a/office365/sharepoint/lists/list.py +++ b/office365/sharepoint/lists/list.py @@ -77,6 +77,15 @@ def __init__(self, context, resource_path=None): def __repr__(self): return self.id or self.title or self.entity_type_name + def clear(self): + """Clears the list.""" + + def _clear(items): + [item.delete_object() for item in items] + + self.items.get().after_execute(_clear) + return self + def create_document_and_get_edit_link( self, file_name=None, diff --git a/office365/sharepoint/permissions/irm/file_settings.py b/office365/sharepoint/permissions/irm/file_settings.py index c6f2ee3b9..26dea667f 100644 --- a/office365/sharepoint/permissions/irm/file_settings.py +++ b/office365/sharepoint/permissions/irm/file_settings.py @@ -1,3 +1,5 @@ +from typing import Optional + from office365.runtime.queries.service_operation import ServiceOperationQuery from office365.sharepoint.entity import Entity @@ -13,43 +15,43 @@ def reset(self): @property def allow_print(self): + # type: () -> Optional[bool] """ Gets a value indicating whether or not the user can print the downloaded document. True if print is allowed; otherwise, it is false. The default value is false. - :rtype: bool """ return self.properties.get("AllowPrint", None) @allow_print.setter def allow_print(self, value): + # type: (bool) -> None """ - Sets a value indicating whether or not the user can print the downloaded document. - :param bool value: + Sets a value indicating whether or not the user can print the downloaded document. """ self.set_property("AllowPrint", value) @property def allow_script(self): + # type: () -> Optional[bool] """ Gets a value indicating whether or not the user can run a script on the downloaded document. True if the script is allowed to run; otherwise, it is false. The default value is false. - :rtype: bool """ return self.properties.get("AllowScript", None) @allow_script.setter def allow_script(self, value): + # type: (bool) -> None """ Sets a value indicating whether or not the user can run a script on the downloaded document. - :param bool value: """ self.set_property("AllowPrint", value) @property def allow_write_copy(self): + # type: () -> Optional[bool] """ Getsa value indicating whether or not the user can write on a copy of the downloaded document. True if write on a copy is allowed; otherwise, it is false. The default value is false. - :rtype: bool """ return self.properties.get("AllowWriteCopy", None) diff --git a/office365/sharepoint/permissions/irm/settings.py b/office365/sharepoint/permissions/irm/settings.py index e811a7308..cf729a725 100644 --- a/office365/sharepoint/permissions/irm/settings.py +++ b/office365/sharepoint/permissions/irm/settings.py @@ -9,30 +9,29 @@ class InformationRightsManagementSettings(Entity): @property def allow_print(self): - """ - Specifies whether a user can print the downloaded document. - :rtype: bool or None - """ + # type: () -> Optional[bool] + """Specifies whether a user can print the downloaded document.""" return self.properties.get("AllowPrint", None) @property def allow_script(self): + # type: () -> Optional[bool] """ Specifies whether a user can run scripts on the downloaded document. - :rtype: bool or None """ return self.properties.get("AllowScript", None) @property def allow_write_copy(self): + # type: () -> Optional[bool] """ Specifies whether a user can write in a copy of the downloaded document. - :rtype: bool or None """ return self.properties.get("AllowWriteCopy", None) @property def disable_document_browser_view(self): + # type: () -> Optional[bool] """ Specifies whether a user can write in a copy of the downloaded document. :rtype: bool or None @@ -41,14 +40,15 @@ def disable_document_browser_view(self): @property def document_access_expire_days(self): + # type: () -> Optional[int] """ Specifies the number of days after which the downloaded document will expire. - :rtype: int or None """ return self.properties.get("DocumentAccessExpireDays", None) @property def document_library_protection_expire_date(self): + # type: () -> Optional[datetime.datetime] """ Specifies the date on which the Information Rights Management (IRM) protection of this document library will stop. @@ -59,17 +59,15 @@ def document_library_protection_expire_date(self): @property def enable_document_access_expire(self): - """ - Specifies whether the downloaded document will expire. - :rtype: int or None - """ + # type: () -> Optional[bool] + """Specifies whether the downloaded document will expire.""" return self.properties.get("EnableDocumentAccessExpire", None) @property def enable_group_protection(self): + # type: () -> Optional[bool] """ Specifies whether the permission of the downloaded document is applicable to a group. - :rtype: int or None """ return self.properties.get("EnableGroupProtection", None) diff --git a/office365/sharepoint/tenant/administration/tenant.py b/office365/sharepoint/tenant/administration/tenant.py index 7c9164145..c691694f4 100644 --- a/office365/sharepoint/tenant/administration/tenant.py +++ b/office365/sharepoint/tenant/administration/tenant.py @@ -335,6 +335,7 @@ def get_sites_by_state(self, states=None): return return_type def _poll_site_status(self, site_url, polling_interval_secs): + # type: (str, int) -> None states = [0, 1, 2] time.sleep(polling_interval_secs) diff --git a/office365/sharepoint/webs/web.py b/office365/sharepoint/webs/web.py index a49b4c7c9..495272b7b 100644 --- a/office365/sharepoint/webs/web.py +++ b/office365/sharepoint/webs/web.py @@ -1278,12 +1278,19 @@ def share( :param str email_subject: The email subject. :param str email_body: The email subject. """ + return_type = SharingResult(self.context) - def _share(picker_result, group): - # type: (ClientResult[str], Group) -> None + def _share(picker_result): + # type: (ClientResult[str]) -> None + groups = { + ExternalSharingSiteOption.View: self.associated_visitor_group, + ExternalSharingSiteOption.Edit: self.associated_member_group, + ExternalSharingSiteOption.Owner: self.associated_owner_group, + } # type: dict[ExternalSharingSiteOption, Group] + picker_input = "[{0}]".format(picker_result.value) - role_value = "group:{groupId}".format(groupId=group.id) + role_value = "group:{groupId}".format(groupId=groups[share_option].id) Web.share_object( self.context, self.url, @@ -1299,15 +1306,9 @@ def _share(picker_result, group): ) def _web_resolved(): - groups = { - ExternalSharingSiteOption.View: self.associated_visitor_group, - ExternalSharingSiteOption.Edit: self.associated_member_group, - ExternalSharingSiteOption.Owner: self.associated_owner_group, - } # type: dict[ExternalSharingSiteOption, Group] - ClientPeoplePickerWebServiceInterface.client_people_picker_resolve_user( self.context, user_principal_name - ).after_execute(_share, groups[share_option]) + ).after_execute(_share) self.ensure_properties( [ diff --git a/tests/directory/test_authentication.py b/tests/directory/test_authentication.py new file mode 100644 index 000000000..9b6b6ebc7 --- /dev/null +++ b/tests/directory/test_authentication.py @@ -0,0 +1,11 @@ +from tests.graph_case import GraphTestCase + + +class TestAuthentication(GraphTestCase): + def test1_list_methods(self): + result = self.client.me.authentication.methods.get().execute_query() + self.assertIsNotNone(result.resource_path) + + # def test2_list_strength_policies(self): + # result = self.client.policies.authentication_strength_policies().get().execute_query() + # self.assertIsNotNone(result.resource_path) diff --git a/tests/directory/test_security.py b/tests/directory/test_security.py index 3e7767a87..c2d1d526a 100644 --- a/tests/directory/test_security.py +++ b/tests/directory/test_security.py @@ -12,3 +12,7 @@ def setUpClass(cls): def test1_list_incidents(self): col = self.client.security.incidents.top(10).get().execute_query() self.assertIsNotNone(col.resource_path) + + # def test2_list_threat_assessment_requests(self): + # col = self.client.information_protection.threat_assessment_requests.top(10).get().execute_query() + # self.assertIsNotNone(col.resource_path) diff --git a/tests/onedrive/test_excel_functions.py b/tests/onedrive/test_excel_functions.py index a961697b4..f2f2beb96 100644 --- a/tests/onedrive/test_excel_functions.py +++ b/tests/onedrive/test_excel_functions.py @@ -1,4 +1,5 @@ import os +from datetime import datetime, timedelta from office365.onedrive.driveitems.driveItem import DriveItem from office365.onedrive.workbooks.tables.table import WorkbookTable @@ -35,3 +36,11 @@ def tearDownClass(cls): def test1_get_abs(self): result = self.__class__.target_item.workbook.functions.abs(-2).execute_query() self.assertEquals(result.value, 2) + + # def test2_get_days(self): + # start = datetime.now() + # end = start + timedelta(days=10) + # result = self.__class__.target_item.workbook.functions.days( + # start, end + # ).execute_query() + # self.assertGreater(result.value, 1) diff --git a/tests/outlook/test_messages.py b/tests/outlook/test_messages.py index f1102f8a6..c548e3417 100644 --- a/tests/outlook/test_messages.py +++ b/tests/outlook/test_messages.py @@ -37,9 +37,10 @@ def test_5_get_my_messages(self): self.assertIsNotNone(messages[0].resource_path) def test_6_update_message(self): - messages = self.client.me.messages.top(1).get().execute_query() - message_to_update = messages[0] - message_to_update.update().execute_query() + # messages = self.client.me.messages.top(1).get().execute_query() + # message_to_update = messages[0] + message = self.__class__.target_message + message.update().execute_query() def test_7_delete_message(self): messages = self.client.me.messages.top(1).get().execute_query()