From b9d91e0e8a61501503269b6a7142b254ab90eadd Mon Sep 17 00:00:00 2001 From: vgrem Date: Sun, 19 Nov 2023 19:23:04 +0200 Subject: [PATCH] typings enhancements, new examples (get folder author) --- .../sharepoint/folders/get_system_metadata.py | 17 ++- .../assessment/broken_tax_field_value.py | 24 ++++ generator/metadata/SharePoint.xml | 93 ++++++++++++---- office365/onedrive/permissions/collection.py | 8 +- office365/runtime/client_object.py | 3 +- office365/runtime/client_object_collection.py | 30 ++--- office365/runtime/client_result.py | 4 +- office365/runtime/odata/type.py | 2 + .../files/checked_out_file_collection.py | 2 +- office365/sharepoint/files/collection.py | 20 +++- office365/sharepoint/files/versions/event.py | 9 +- .../sharepoint/files/versions/version.py | 4 +- office365/sharepoint/folders/folder.py | 13 +-- office365/sharepoint/listitems/collection.py | 4 + office365/sharepoint/listitems/listitem.py | 43 +++----- office365/sharepoint/lists/list.py | 104 ++++++++---------- .../internal/permission_grant.py | 7 ++ tests/sharepoint/test_file.py | 5 +- 18 files changed, 224 insertions(+), 168 deletions(-) create mode 100644 examples/sharepoint/lists/assessment/broken_tax_field_value.py create mode 100644 office365/sharepoint/tenant/administration/internal/permission_grant.py diff --git a/examples/sharepoint/folders/get_system_metadata.py b/examples/sharepoint/folders/get_system_metadata.py index dbd976a5..46e6dd82 100644 --- a/examples/sharepoint/folders/get_system_metadata.py +++ b/examples/sharepoint/folders/get_system_metadata.py @@ -6,16 +6,15 @@ from tests import test_client_credentials, test_team_site_url ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) -folder_url = "Shared Documents/Archive" - +list_title = "Docs" +folder_path = "Archive" # folder relative path folder_item = ( - ctx.web.get_folder_by_server_relative_url(folder_url) - .list_item_all_fields.get() - .execute_query() -) -author = ( - ctx.web.site_users.get_by_id(folder_item.get_property("AuthorId")) + ctx.web.lists.get_by_title(list_title) + .get_item_by_url(folder_path) + .select(["Author/Title"]) + .expand(["Author"]) .get() .execute_query() ) -print(author) + +print(folder_item.properties.get("Author")) diff --git a/examples/sharepoint/lists/assessment/broken_tax_field_value.py b/examples/sharepoint/lists/assessment/broken_tax_field_value.py new file mode 100644 index 00000000..9a0009c5 --- /dev/null +++ b/examples/sharepoint/lists/assessment/broken_tax_field_value.py @@ -0,0 +1,24 @@ +""" +Demonstrates how to perform list/library assessment if taxonomy field value association is broken +to term set or not. + +To prevent this exception to occur: +'-2146232832, Microsoft.SharePoint.SPFieldValidationException', 'The given guid does not exist in the term store' +""" +from office365.runtime.client_request_exception import ClientRequestException +from office365.sharepoint.client_context import ClientContext +from office365.sharepoint.taxonomy.field import TaxonomyField +from tests import test_client_credentials, test_team_site_url + +ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) +lib = ctx.web.default_document_library() +fields = lib.fields.get().execute_query() +for field in fields: + if not isinstance(field, TaxonomyField): + continue + try: + items = lib.items.select([field.internal_name]).top(1).get().execute_query() + print(field.internal_name) + except ClientRequestException: + print(field.is_term_set_valid) + print("Error: {0}".format(field.internal_name)) diff --git a/generator/metadata/SharePoint.xml b/generator/metadata/SharePoint.xml index c9b1d714..1288095a 100644 --- a/generator/metadata/SharePoint.xml +++ b/generator/metadata/SharePoint.xml @@ -8226,6 +8226,9 @@ + + + @@ -10619,6 +10622,9 @@ + + + @@ -14006,7 +14012,6 @@ - @@ -15990,6 +15995,10 @@ + + + + @@ -19424,6 +19433,18 @@ + + + + + + + + + + + + @@ -19562,6 +19583,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -25251,6 +25299,9 @@ + + + @@ -30776,6 +30827,11 @@ + + + + + @@ -30845,6 +30901,17 @@ + + + + + + + + + + + @@ -35608,6 +35675,7 @@ + @@ -38100,12 +38168,6 @@ - - - - - - @@ -38175,10 +38237,6 @@ - - - - @@ -38235,15 +38293,6 @@ - - - - - - - - - @@ -38260,10 +38309,6 @@ - - - - diff --git a/office365/onedrive/permissions/collection.py b/office365/onedrive/permissions/collection.py index ec4ea5ae..f4524664 100644 --- a/office365/onedrive/permissions/collection.py +++ b/office365/onedrive/permissions/collection.py @@ -71,9 +71,7 @@ def _add(): def delete(self, roles, identity): # type: (list[str], Application|User|Group|Device) -> Permission - """ - Deletes the permission. - """ + """Deletes the permission.""" def _delete(col): pass @@ -85,9 +83,7 @@ def _identity_loaded(): return self def delete_all(self): - """ - Remove all access to resource - """ + """Remove all access to resource""" def _after_loaded(return_type): # type: (PermissionCollection) -> None diff --git a/office365/runtime/client_object.py b/office365/runtime/client_object.py index 6eb4e04c..08f4003c 100644 --- a/office365/runtime/client_object.py +++ b/office365/runtime/client_object.py @@ -90,7 +90,6 @@ def get(self): def is_property_available(self, name): # type: (str) -> bool """Returns a Boolean value that indicates whether the specified property has been retrieved or set. - :param str name: A property name """ if name in self.properties: @@ -116,7 +115,7 @@ def select(self, names): def remove_from_parent_collection(self): if self._parent_collection is None: - return + return self self._parent_collection.remove_child(self) return self diff --git a/office365/runtime/client_object_collection.py b/office365/runtime/client_object_collection.py index db3ea9d4..3e340f9f 100644 --- a/office365/runtime/client_object_collection.py +++ b/office365/runtime/client_object_collection.py @@ -1,4 +1,4 @@ -from typing import Callable, Generic, Iterator, List, Optional, Type, TypeVar +from typing import Any, Callable, Generic, Iterator, List, Optional, Type, TypeVar from typing_extensions import Self @@ -110,7 +110,6 @@ def filter(self, expression): # type: (str) -> Self """ Allows clients to filter a collection of resources that are addressed by a request URL - :param str expression: Filter expression, for example: 'Id eq 123' """ self.query_options.filter = expression @@ -134,17 +133,13 @@ def skip(self, value): def top(self, value): # type: (int) -> Self - """ - Specifies the number of items in the queried collection to be included in the result - """ + """Specifies the number of items in the queried collection to be included in the result""" self.query_options.top = value return self def paged(self, page_size=None, page_loaded=None): # type: (int, Callable[[Self], None]) -> Self - """ - Retrieves via server-driven paging mode - """ + """Retrieves via server-driven paging mode""" self._paged_mode = True if callable(page_loaded): self._page_loaded += page_loaded @@ -152,6 +147,12 @@ 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 @@ -196,22 +197,22 @@ def _construct_request(request): def first(self, expression): # type: (str) -> T """Return the first Entity instance that matches current query - :param str expression: Filter expression """ return_type = self.create_typed_object() self.add_child(return_type) - key = return_type.property_ref_name def _after_loaded(col): # type: (ClientObjectCollection) -> None if len(col) < 1: message = "Not found for filter: {0}".format(self.query_options.filter) raise ValueError(message) - return_type.set_property(key, col[0].get_property(key)) + [ + return_type.set_property(k, v, False) + for k, v in col[0].properties.items() + ] - self.filter(expression).top(1) - self.context.load(self, [key], after_loaded=_after_loaded) + self.get().filter(expression).top(1).after_execute(_after_loaded) return return_type def single(self, expression): @@ -237,8 +238,7 @@ def _after_loaded(col): for k, v in col[0].properties.items() ] - self.filter(expression).top(2) - self.context.load(self, after_loaded=_after_loaded) + self.get().filter(expression).top(2).after_execute(_after_loaded) return return_type @property diff --git a/office365/runtime/client_result.py b/office365/runtime/client_result.py index c00a8452..5a44ddbe 100644 --- a/office365/runtime/client_result.py +++ b/office365/runtime/client_result.py @@ -59,9 +59,7 @@ def execute_query(self): def execute_query_retry( self, max_retry=5, timeout_secs=5, success_callback=None, failure_callback=None ): - """ - Executes the current set of data retrieval queries and method invocations and retries it if needed. - """ + """Executes the current set of data retrieval queries and method invocations and retries it if needed.""" self._context.execute_query_retry( max_retry=max_retry, timeout_secs=timeout_secs, diff --git a/office365/runtime/odata/type.py b/office365/runtime/odata/type.py index 52d3d55f..16548e3e 100644 --- a/office365/runtime/odata/type.py +++ b/office365/runtime/odata/type.py @@ -64,6 +64,8 @@ def try_parse_datetime(value): """ if value is None: return None + elif isinstance(value, datetime.datetime): + return value known_formats = [ "%Y-%m-%dT%H:%M:%SZ", diff --git a/office365/sharepoint/files/checked_out_file_collection.py b/office365/sharepoint/files/checked_out_file_collection.py index 4439fd0f..fab30e55 100644 --- a/office365/sharepoint/files/checked_out_file_collection.py +++ b/office365/sharepoint/files/checked_out_file_collection.py @@ -3,7 +3,7 @@ from office365.sharepoint.files.checked_out_file import CheckedOutFile -class CheckedOutFileCollection(EntityCollection): +class CheckedOutFileCollection(EntityCollection[CheckedOutFile]): def __init__(self, context, resource_path=None): super(CheckedOutFileCollection, self).__init__( context, CheckedOutFile, resource_path diff --git a/office365/sharepoint/files/collection.py b/office365/sharepoint/files/collection.py index fc2f5744..b63456ab 100644 --- a/office365/sharepoint/files/collection.py +++ b/office365/sharepoint/files/collection.py @@ -1,24 +1,31 @@ import hashlib import os import uuid -from typing import IO, Callable +from typing import IO, TYPE_CHECKING, Callable from office365.runtime.client_result import ClientResult +from office365.runtime.paths.resource_path import ResourcePath from office365.runtime.paths.service_operation import ServiceOperationPath from office365.runtime.queries.service_operation import ServiceOperationQuery +from office365.sharepoint.client_context import ClientContext from office365.sharepoint.entity_collection import EntityCollection from office365.sharepoint.files.creation_information import FileCreationInformation from office365.sharepoint.files.file import File from office365.sharepoint.types.resource_path import ResourcePath as SPResPath +if TYPE_CHECKING: + from office365.sharepoint.folders.folder import Folder + class FileCollection(EntityCollection[File]): """Represents a collection of File resources.""" def __init__(self, context, resource_path=None, parent=None): + # type: (ClientContext, ResourcePath, Folder) -> None super(FileCollection, self).__init__(context, File, resource_path, parent) def upload(self, path_or_file): + # type: (str|IO) -> File """Uploads a file into folder. Note: This method only supports files up to 4MB in size! @@ -136,7 +143,7 @@ def add_template_file(self, url_of_file, template_file_type): return_type = File(self.context) self.add_child(return_type) - def _parent_folder_loaded(): + def _add_template_file(): params = { "urlOfFile": str( SPResPath.create_relative( @@ -150,17 +157,24 @@ def _parent_folder_loaded(): ) self.context.add_query(qry) - self.parent.ensure_property("ServerRelativeUrl", _parent_folder_loaded) + self.parent.ensure_property("ServerRelativeUrl", _add_template_file) return return_type def get_by_url(self, url): + # type: (str) -> File """Retrieve File object by url""" return File( self.context, ServiceOperationPath("GetByUrl", [url], self.resource_path) ) def get_by_id(self, _id): + # type: (int) -> File """Gets the File with the specified ID.""" return File( self.context, ServiceOperationPath("getById", [_id], self.resource_path) ) + + @property + def parent(self): + # type: () -> Folder + return self._parent diff --git a/office365/sharepoint/files/versions/event.py b/office365/sharepoint/files/versions/event.py index c1e060db..60f30163 100644 --- a/office365/sharepoint/files/versions/event.py +++ b/office365/sharepoint/files/versions/event.py @@ -1,3 +1,5 @@ +from typing import Optional + from office365.runtime.client_value_collection import ClientValueCollection from office365.sharepoint.entity import Entity from office365.sharepoint.sharing.shared_with_user import SharedWithUser @@ -8,14 +10,14 @@ class FileVersionEvent(Entity): @property def event_type(self): + # type: () -> Optional[str] """Returns the type of the event.""" return self.properties.get("EventType", None) @property def editor(self): - """Returns the name of the user who initiated the event. - :rtype: str or None - """ + # type: () -> Optional[str] + """Returns the name of the user who initiated the event.""" return self.properties.get("Editor", None) @property @@ -25,6 +27,7 @@ def shared_by_user(self): @property def shared_with_users(self): + # type: () -> ClientValueCollection[SharedWithUser] """Returns the array of users that have been shared in sharing action for the change log.""" return self.properties.get( "SharedWithUsers", ClientValueCollection(SharedWithUser) diff --git a/office365/sharepoint/files/versions/version.py b/office365/sharepoint/files/versions/version.py index 595b339f..682479bf 100644 --- a/office365/sharepoint/files/versions/version.py +++ b/office365/sharepoint/files/versions/version.py @@ -104,8 +104,8 @@ def get_property(self, name, default_value=None): def set_property(self, key, value, persist_changes=True): super(FileVersion, self).set_property(key, value, persist_changes) - if self._resource_path is None: - if key == self.property_ref_name: + if key.lower() == self.property_ref_name.lower(): + if self._resource_path is None: self._resource_path = KeyPath( value, self.parent_collection.resource_path ) diff --git a/office365/sharepoint/folders/folder.py b/office365/sharepoint/folders/folder.py index 91bc6d6b..543ccee4 100644 --- a/office365/sharepoint/folders/folder.py +++ b/office365/sharepoint/folders/folder.py @@ -39,7 +39,6 @@ def __repr__(self): def from_url(abs_url): """ Addresses a Folder by absolute url - :type abs_url: str """ from office365.sharepoint.client_context import ClientContext @@ -151,9 +150,7 @@ def _update_folder(url): self.set_property("ServerRelativePath", url) def _move_to_using_path(destination_folder): - """ - :type destination_folder: Folder - """ + # type: ("Folder") -> None destination_url = "/".join( [str(destination_folder.server_relative_path), self.name] ) @@ -515,9 +512,7 @@ def name(self): @property def is_wopi_enabled(self): # type: () -> Optional[bool] - """ - Indicates whether the folder is enabled for WOPI default action. - """ + """Indicates whether the folder is enabled for WOPI default action.""" return self.properties.get("IsWOPIEnabled", None) @property @@ -571,9 +566,7 @@ def time_created(self): @property def serverRelativeUrl(self): # type: () -> Optional[str] - """ - Gets the server-relative URL of the list folder. - """ + """Gets the server-relative URL of the list folder.""" return self.properties.get("ServerRelativeUrl", None) @property diff --git a/office365/sharepoint/listitems/collection.py b/office365/sharepoint/listitems/collection.py index 167f47d1..507bd9fa 100644 --- a/office365/sharepoint/listitems/collection.py +++ b/office365/sharepoint/listitems/collection.py @@ -31,3 +31,7 @@ def get_by_string_id(self, s_id): self.context, ServiceOperationPath("GetByStringId", {"sId": s_id}, self.resource_path), ) + + def get_by_url(self, url): + """Returns the list item with the specified site or server relative url.""" + return self.single("FileRef eq '{0}'".format(url)) diff --git a/office365/sharepoint/listitems/listitem.py b/office365/sharepoint/listitems/listitem.py index 00b6212c..d31c8bd6 100644 --- a/office365/sharepoint/listitems/listitem.py +++ b/office365/sharepoint/listitems/listitem.py @@ -241,9 +241,7 @@ def _property_resolved(): return return_type def unshare(self): - """ - Unshare a ListItem (file or folder facet) - """ + """Unshare a ListItem (file or folder facet)""" return_type = SharingResult(self.context) def _property_resolved(): @@ -372,10 +370,8 @@ def parse_and_set_field_value(self, field_name, value): @property def display_name(self): - """Specifies the display name of the list item. - - :rtype: str or None - """ + # type: () -> Optional[str] + """Specifies the display name of the list item.""" return self.properties.get("DisplayName", None) @property @@ -441,34 +437,26 @@ def effective_base_permissions(self): @property def field_values(self): + # type: () -> Optional[dict] """Gets a collection of key/value pairs containing the names and values for the fields of the list item.""" return self.properties.get("FieldValues", None) @property def comments_disabled(self): - """ - Indicates whether comments for this item are disabled or not. - - :rtype: bool or None - """ + # type: () -> Optional[bool] + """Indicates whether comments for this item are disabled or not.""" return self.properties.get("CommentsDisabled", None) @property def file_system_object_type(self): - """ - Gets a value that specifies whether the list item is a file or a list folder. - - :rtype: str or None - """ + # type: () -> Optional[str] + """Gets a value that specifies whether the list item is a file or a list folder""" return self.properties.get("FileSystemObjectType", None) @property def id(self): - """ - Gets a value that specifies the list item identifier. - - :rtype: int - """ + # type: () -> Optional[int] + """Gets a value that specifies the list item identifier.""" return self.properties.get("Id", None) @property @@ -477,6 +465,7 @@ def compliance_info(self): @property def comments_disabled_scope(self): + # type: () -> Optional[str] """Indicates at what scope comments are disabled.""" return self.properties.get("CommentsDisabledScope", None) @@ -502,9 +491,7 @@ def field_values_as_html(self): @property def liked_by_information(self): - """ - Gets a value that specifies the list item identifier. - """ + """Gets a value that specifies the list item identifier.""" return self.properties.get( "LikedByInformation", LikedByInformation( @@ -570,17 +557,17 @@ def set_property(self, name, value, persist_changes=True): super(ListItem, self).set_property(name, value, persist_changes) # fallback: create a new resource path - if self._resource_path is None and self.parent_collection is not None: - if name == "Id": + if name == "Id": + if self._resource_path is None and self.parent_collection is not None: self._resource_path = KeyPath( value, self.parent_collection.resource_path ) return self def _set_taxonomy_field_value(self, name, value): + # type: (str, TaxonomyFieldValueCollection) -> None """ Sets taxonomy field value - :param str name: Taxonomy field name :param TaxonomyFieldValueCollection value: Taxonomy field value """ diff --git a/office365/sharepoint/lists/list.py b/office365/sharepoint/lists/list.py index 4017cce5..dfa0e988 100644 --- a/office365/sharepoint/lists/list.py +++ b/office365/sharepoint/lists/list.py @@ -1,5 +1,6 @@ +import os from datetime import datetime -from typing import Optional +from typing import AnyStr, Optional from office365.runtime.client_result import ClientResult from office365.runtime.client_value_collection import ClientValueCollection @@ -7,6 +8,7 @@ from office365.runtime.paths.service_operation import ServiceOperationPath from office365.runtime.queries.service_operation import ServiceOperationQuery from office365.sharepoint.changes.collection import ChangeCollection +from office365.sharepoint.changes.log_item_query import ChangeLogItemQuery from office365.sharepoint.changes.query import ChangeQuery from office365.sharepoint.changes.token import ChangeToken from office365.sharepoint.contenttypes.collection import ContentTypeCollection @@ -133,9 +135,7 @@ def get_bloom_filter(self, start_item_id=None): return return_type def get_metadata_navigation_settings(self): - """ - Retrieves the configured metadata navigation settings for the list. - """ + """Retrieves the configured metadata navigation settings for the list.""" from office365.sharepoint.navigation.metadata_settings import ( MetadataNavigationSettings, ) @@ -153,12 +153,10 @@ def _loaded(): return return_type def get_sharing_settings(self): - """ - Retrieves a sharing settings for a List - """ + """Retrieves a sharing settings for a List""" return_type = ObjectSharingSettings(self.context) - def _list_loaded(): + def _get_sharing_settings(): from office365.sharepoint.webs.web import Web list_abs_path = SPResPath.create_absolute( @@ -168,7 +166,7 @@ def _list_loaded(): self.context, str(list_abs_path), return_type=return_type ) - self.ensure_property("RootFolder", _list_loaded) + self.ensure_property("RootFolder", _get_sharing_settings) return return_type def get_site_script(self, options=None): @@ -190,9 +188,7 @@ def _list_loaded(): return return_type def get_all_rules(self): - """ - Retrieves rules of a List - """ + """Retrieves rules of a List""" return_type = ClientResult(self.context, ClientValueCollection(SPListRule)) qry = ServiceOperationQuery(self, "GetAllRules", None, None, None, return_type) self.context.add_query(qry) @@ -390,11 +386,10 @@ def get_lookup_field_choices(self, target_field_name, paging_info=None): return return_type def get_list_item_changes_since_token(self, query): + # type: (ChangeLogItemQuery) -> ClientResult[AnyStr] """ Returns the changes made to the list since the date and time specified in the change token defined by the query input parameter. - - :type query: office365.sharepoint.changes.log_item_query.ChangeLogItemQuery """ return_type = ClientResult(self.context, bytes()) payload = {"query": query} @@ -437,7 +432,6 @@ def save_as_template(self, file_name, name, description, save_data): :param str description: A string that contains the description for the list template. :param str name: A string that contains the title for the list template. :param str file_name: A string that contains the file name for the list template with an .stp extension. - :return: """ payload = { "strFileName": file_name, @@ -460,11 +454,10 @@ def get_item_by_unique_id(self, unique_id): ) def get_web_dav_url(self, source_url): + # type: (str) -> ClientResult[str] """ Gets the trusted URL for opening the folder in Explorer view. - :param str source_url: The URL of the current folder the user is in. - :return: ClientResult """ return_type = ClientResult(self.context, str()) @@ -476,10 +469,8 @@ def get_web_dav_url(self, source_url): return return_type def get_items(self, caml_query=None): - """Returns a collection of items from the list based on the specified query. - - :type caml_query: CamlQuery - """ + # type: (CamlQuery) -> ListItemCollection + """Returns a collection of items from the list based on the specified query.""" if not caml_query: caml_query = CamlQuery.create_all_items_query() return_type = ListItemCollection(self.context, self.items.resource_path) @@ -585,14 +576,29 @@ def add_validate_update_item(self, create_info, form_values=None): return return_type def get_item_by_id(self, item_id): - """Returns the list item with the specified list item identifier. - :type item_id: int - """ + # type: (int) -> ListItem + """Returns the list item with the specified list item identifier.""" return ListItem( self.context, ServiceOperationPath("getItemById", [item_id], self.resource_path), ) + def get_item_by_url(self, url): + # type: (str) -> ListItem + """Returns the list item with the specified site or server relative url.""" + return_type = ListItem(self.context) + self.items.add_child(return_type) + + def _after_loaded(item): + [return_type.set_property(k, v, False) for k, v in item.properties.items()] + + def _get_item_by_url(): + path = os.path.join(self.root_folder.serverRelativeUrl, url) + self.items.get_by_url(path).after_execute(_after_loaded, execute_first=True) + + self.ensure_property("RootFolder", _get_item_by_url) + return return_type + def get_view(self, view_id): """Returns the list view with the specified view identifier. :type view_id: str @@ -629,10 +635,9 @@ def get_checked_out_files(self): return return_type def reserve_list_item_id(self): - """ - Reserves the returned list item identifier for the idempotent creation of a list item. - """ - return_type = ClientResult(self.context, int()) # type: ClientResult[int] + # type: () -> ClientResult[int] + """Reserves the returned list item identifier for the idempotent creation of a list item.""" + return_type = ClientResult(self.context, int()) qry = ServiceOperationQuery( self, "ReserveListItemId", None, None, None, return_type ) @@ -672,8 +677,7 @@ def get_special_folder_url(self, folder_type, force_create, existing_folder_guid @property def id(self): # type: () -> Optional[str] - """ - Gets a value that specifies the list identifier.""" + """Gets a value that specifies the list identifier.""" return self.properties.get("Id", None) @property @@ -691,25 +695,19 @@ def author(self): @property def allow_content_types(self): # type: () -> Optional[bool] - """ - Specifies whether the list supports content types. - """ + """Specifies whether the list supports content types.""" return self.properties.get("AllowContentTypes", None) @property def allow_deletion(self): # type: () -> Optional[bool] - """ - Specifies whether the list could be deleted. - """ + """Specifies whether the list could be deleted.""" return self.properties.get("AllowDeletion", None) @property def base_template(self): # type: () -> Optional[int] - """ - Specifies the list server template of the list. - """ + """Specifies the list server template of the list.""" return self.properties.get("BaseTemplate", None) @property @@ -740,16 +738,12 @@ def created(self): @property def default_display_form_url(self): # type: () -> Optional[str] - """ - Specifies the location of the default display form for the list. - """ + """Specifies the location of the default display form for the list.""" return self.properties.get("DefaultDisplayFormUrl", None) @property def default_view_path(self): - """ - Specifies the server-relative URL of the default view for the list. - """ + """Specifies the server-relative URL of the default view for the list.""" return self.properties.get("DefaultViewPath", SPResPath()) @property @@ -803,9 +797,7 @@ def data_source(self): @property def default_edit_form_url(self): # type: () -> Optional[str] - """ - Gets a value that specifies the URL of the edit form to use for list items in the list. - """ + """Gets a value that specifies the URL of the edit form to use for list items in the list.""" return self.properties.get("DefaultEditFormUrl", None) @property @@ -871,9 +863,7 @@ def enable_attachments(self): @property def enable_folder_creation(self): # type: () -> Optional[bool] - """ - Specifies whether new list folders can be added to the list. - """ + """Specifies whether new list folders can be added to the list.""" return self.properties.get("EnableFolderCreation", None) @enable_folder_creation.setter @@ -884,17 +874,13 @@ def enable_folder_creation(self, value): @property def enable_minor_versions(self): # type: () -> Optional[bool] - """ - Specifies whether minor versions are enabled for the list. - """ + """Specifies whether minor versions are enabled for the list.""" return self.properties.get("EnableMinorVersions", None) @property def enable_moderation(self): # type: () -> Optional[bool] - """ - Specifies whether content approval is enabled for the list. - """ + """Specifies whether content approval is enabled for the list.""" return self.properties.get("EnableModeration", None) @property @@ -905,9 +891,7 @@ def enable_request_sign_off(self): @property def enable_versioning(self): # type: () -> Optional[bool] - """ - Specifies whether content approval is enabled for the list. - """ + """Specifies whether content approval is enabled for the list.""" return self.properties.get("EnableVersioning", None) @property diff --git a/office365/sharepoint/tenant/administration/internal/permission_grant.py b/office365/sharepoint/tenant/administration/internal/permission_grant.py new file mode 100644 index 00000000..c0bc861b --- /dev/null +++ b/office365/sharepoint/tenant/administration/internal/permission_grant.py @@ -0,0 +1,7 @@ +from office365.sharepoint.entity import Entity + + +class SPO3rdPartyAADPermissionGrant(Entity): + @property + def entity_type_name(self): + return "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPO3rdPartyAADPermissionGrant" diff --git a/tests/sharepoint/test_file.py b/tests/sharepoint/test_file.py index 0823f41b..3c2183f2 100644 --- a/tests/sharepoint/test_file.py +++ b/tests/sharepoint/test_file.py @@ -83,8 +83,9 @@ def test_10_list_file_versions(self): def test_11_delete_file_version(self): versions = self.__class__.file.versions.top(1).get().execute_query() self.assertEqual(len(versions), 1) - self.assertIsNotNone(versions[0].resource_path) - versions[0].delete_object().execute_query() + first_version = versions[0] + self.assertIsNotNone(first_version.resource_path) + first_version.delete_object().execute_query() def test_13_download_file_content(self): result = self.__class__.file.get_content().execute_query()