Skip to content

Commit 0e92b11

Browse files
SharePoint API: better support for addressing resources by resource path, unit test fix to wait until Team create operation is completed
1 parent b3102a6 commit 0e92b11

File tree

11 files changed

+162
-18
lines changed

11 files changed

+162
-18
lines changed

examples/sharepoint/folders/copy_folder.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
ctx = ClientContext(test_team_site_url).with_credentials(test_user_credentials)
55

6-
source_folder_url = "/sites/team/Shared Documents/Archive"
7-
target_folder_url = "/sites/team/Shared Documents/Archive2012"
6+
source_folder_url = "/sites/team/Shared Documents/Archive2012"
7+
target_folder_url = "/sites/team/Shared Documents/Archive2013"
88

99

1010
source_folder = ctx.web.get_folder_by_server_relative_url(source_folder_url)
11-
target_folder = source_folder.copy_to(target_folder_url).get().execute_query()
12-
print(f"File copied into {target_folder.serverRelativeUrl}")
11+
target_folder = source_folder.move_to_by_path(target_folder_url).get().execute_query()
12+
print(f"File copied into {target_folder.server_relative_path.DecodedUrl}")
1313

office365/runtime/client_object.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,12 @@ def set_property(self, name, value, persist_changes=True):
100100
if isinstance(prop_type, ClientObject) or isinstance(prop_type, ClientValue) and value is not None:
101101
if isinstance(value, list):
102102
[prop_type.set_property(i, v, persist_changes) for i, v in enumerate(value)]
103-
else:
103+
self._properties[name] = prop_type
104+
elif isinstance(value, dict):
104105
[prop_type.set_property(k, v, persist_changes) for k, v in value.items()]
105-
self._properties[name] = prop_type
106+
self._properties[name] = prop_type
107+
else:
108+
self._properties[name] = value
106109
else:
107110
self._properties[name] = value
108111
return self

office365/sharepoint/files/file.py

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from office365.sharepoint.files.file_version_collection import FileVersionCollection
1111
from office365.sharepoint.listitems.listitem import ListItem
1212
from office365.sharepoint.webparts.limited_webpart_manager import LimitedWebPartManager
13+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
1314

1415

1516
class AbstractFile(BaseEntity):
@@ -369,6 +370,13 @@ def serverRelativeUrl(self):
369370
"""
370371
return self.properties.get("ServerRelativeUrl", None)
371372

373+
@property
374+
def server_relative_path(self):
375+
"""Gets the server-relative Path of the list folder.
376+
:rtype: SPResPath or None
377+
"""
378+
return self.properties.get("ServerRelativePath", SPResPath(None))
379+
372380
@property
373381
def length(self):
374382
"""Gets the file size.
@@ -471,6 +479,9 @@ def set_property(self, name, value, persist_changes=True):
471479
if name == "ServerRelativeUrl":
472480
self._resource_path = ResourcePathServiceOperation(
473481
"GetFileByServerRelativeUrl", [value], ResourcePath("Web"))
482+
elif name == "ServerRelativePath":
483+
self._resource_path = ResourcePathServiceOperation("getFolderByServerRelativePath", [value],
484+
ResourcePath("Web"))
474485
elif name == "UniqueId":
475486
self._resource_path = ResourcePathServiceOperation(
476487
"GetFileById", [value], ResourcePath("Web"))

office365/sharepoint/folders/folder.py

+54-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from office365.sharepoint.storagemetrics.storage_metrics import StorageMetrics
1616
from office365.sharepoint.utilities.move_copy_options import MoveCopyOptions
1717
from office365.sharepoint.utilities.move_copy_util import MoveCopyUtil
18+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
1819

1920

2021
class Folder(BaseEntity):
@@ -128,9 +129,29 @@ def _copy_folder():
128129
opts = MoveCopyOptions(keep_both=keep_both, reset_author_and_created_on_copy=reset_author_and_created)
129130
MoveCopyUtil.copy_folder(self.context, self._build_full_url(self.serverRelativeUrl),
130131
self._build_full_url(new_relative_url), opts)
132+
131133
self.ensure_property("ServerRelativeUrl", _copy_folder)
132134
return target_folder
133135

136+
def copy_to_by_path(self, new_relative_path, keep_both=False, reset_author_and_created=False):
137+
"""Copies the folder with files to the destination Path.
138+
139+
:type new_relative_path: str
140+
:type keep_both: bool
141+
:type reset_author_and_created: bool
142+
"""
143+
144+
target_folder = Folder(self.context)
145+
target_folder.set_property("ServerRelativePath", SPResPath(new_relative_path))
146+
147+
def _copy_folder():
148+
opts = MoveCopyOptions(keep_both=keep_both, reset_author_and_created_on_copy=reset_author_and_created)
149+
MoveCopyUtil.copy_folder_by_path(self.context, self._build_full_url(self.server_relative_path.DecodedUrl),
150+
self._build_full_url(new_relative_path), opts)
151+
152+
self.ensure_property("ServerRelativePath", _copy_folder)
153+
return target_folder
154+
134155
def move_to(self, new_relative_url, retain_editor_and_modified=False):
135156
"""Moves the folder with files to the destination URL.
136157
@@ -148,6 +169,24 @@ def _move_folder():
148169
self.ensure_property("ServerRelativeUrl", _move_folder)
149170
return target_folder
150171

172+
def move_to_by_path(self, new_relative_path, retain_editor_and_modified=False):
173+
"""Moves the folder with files to the destination Path.
174+
175+
:type new_relative_path: str
176+
:type retain_editor_and_modified: bool
177+
"""
178+
target_folder = Folder(self.context)
179+
target_folder.set_property("ServerRelativePath", SPResPath(new_relative_path))
180+
181+
def _move_folder():
182+
MoveCopyUtil.move_folder_by_path(self.context, self._build_full_url(self.server_relative_path.DecodedUrl),
183+
self._build_full_url(new_relative_path),
184+
MoveCopyOptions(
185+
retain_editor_and_modified_on_move=retain_editor_and_modified))
186+
187+
self.ensure_property("ServerRelativePath", _move_folder)
188+
return target_folder
189+
151190
@property
152191
def storage_metrics(self):
153192
return self.properties.get("StorageMetrics",
@@ -162,11 +201,9 @@ def list_item_all_fields(self):
162201
@property
163202
def files(self):
164203
"""Get a file collection"""
165-
if self.is_property_available('Files'):
166-
return self.properties["Files"]
167-
else:
168-
from office365.sharepoint.files.file_collection import FileCollection
169-
return FileCollection(self.context, ResourcePath("Files", self.resource_path))
204+
from office365.sharepoint.files.file_collection import FileCollection
205+
return self.properties.get("Files",
206+
FileCollection(self.context, ResourcePath("Files", self.resource_path)))
170207

171208
@property
172209
def folders(self):
@@ -242,10 +279,18 @@ def serverRelativeUrl(self):
242279
"""
243280
return self.properties.get("ServerRelativeUrl", None)
244281

282+
@property
283+
def server_relative_path(self):
284+
"""Gets the server-relative Path of the list folder.
285+
:rtype: SPResPath or None
286+
"""
287+
return self.properties.get("ServerRelativePath", SPResPath(None))
288+
245289
def get_property(self, name):
246290
property_mapping = {
247291
"ListItemAllFields": self.list_item_all_fields,
248-
"ParentFolder": self.parent_folder
292+
"ParentFolder": self.parent_folder,
293+
"ServerRelativePath": self.server_relative_path
249294
}
250295
if name in property_mapping:
251296
return property_mapping[name]
@@ -258,6 +303,9 @@ def set_property(self, name, value, persist_changes=True):
258303
if name == "ServerRelativeUrl":
259304
self._resource_path = ResourcePathServiceOperation("getFolderByServerRelativeUrl", [value],
260305
ResourcePath("Web"))
306+
elif name == "ServerRelativePath":
307+
self._resource_path = ResourcePathServiceOperation("getFolderByServerRelativePath", [value],
308+
ResourcePath("Web"))
261309
elif name == "UniqueId":
262310
self._resource_path = ResourcePathServiceOperation("getFolderById", [value], ResourcePath("Web"))
263311
return self

office365/sharepoint/sites/site.py

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from office365.sharepoint.sites.sph_site import SPHSite
1313
from office365.sharepoint.webs.web import Web
1414
from office365.sharepoint.webs.web_template_collection import WebTemplateCollection
15+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
1516

1617

1718
class Site(BaseEntity):
@@ -190,6 +191,13 @@ def is_hub_site(self):
190191
"""
191192
return self.properties.get("IsHubSite", None)
192193

194+
@property
195+
def server_relative_path(self):
196+
"""Gets the server-relative Path of the Site.
197+
:rtype: SPResPath or None
198+
"""
199+
return self.properties.get("ServerRelativePath", SPResPath(None))
200+
193201
@property
194202
def recycle_bin(self):
195203
"""Get recycle bin"""

office365/sharepoint/types/resource_path.py

+7
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ class ResourcePath(ClientValue):
66
def __init__(self, decoded_url):
77
super().__init__()
88
self.DecodedUrl = decoded_url
9+
10+
@property
11+
def entity_type_name(self):
12+
return "SP.ResourcePath"
13+
14+
def __str__(self):
15+
return f"DecodedUrl='{self.DecodedUrl}'"

office365/sharepoint/utilities/move_copy_util.py

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from office365.runtime.client_result import ClientResult
22
from office365.runtime.queries.service_operation_query import ServiceOperationQuery
33
from office365.sharepoint.base_entity import BaseEntity
4+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
45

56

67
class MoveCopyUtil(BaseEntity):
@@ -26,6 +27,27 @@ def copy_folder(context, srcUrl, destUrl, options):
2627
context.add_query(qry)
2728
return result
2829

30+
@staticmethod
31+
def copy_folder_by_path(context, srcPath, destPath, options):
32+
"""
33+
34+
:param office365.sharepoint.utilities.move_copy_options.MoveCopyOptions options:
35+
:param str srcPath:
36+
:param str destPath:
37+
:param office365.sharepoint.client_context.ClientContext context: client context
38+
"""
39+
result = ClientResult(context)
40+
util = MoveCopyUtil(context)
41+
payload = {
42+
"srcPath": SPResPath(srcPath),
43+
"destPath": SPResPath(destPath),
44+
"options": options
45+
}
46+
qry = ServiceOperationQuery(util, "CopyFolderByPath", None, payload, None, result)
47+
qry.static = True
48+
context.add_query(qry)
49+
return result
50+
2951
@staticmethod
3052
def move_folder(context, srcUrl, destUrl, options):
3153
"""
@@ -45,3 +67,23 @@ def move_folder(context, srcUrl, destUrl, options):
4567
qry.static = True
4668
context.add_query(qry)
4769
return util
70+
71+
@staticmethod
72+
def move_folder_by_path(context, srcPath, destPath, options):
73+
"""
74+
75+
:param office365.sharepoint.utilities.move_copy_options.MoveCopyOptions options:
76+
:param str srcPath:
77+
:param str destPath:
78+
:param office365.sharepoint.client_context.ClientContext context: client context
79+
"""
80+
util = MoveCopyUtil(context)
81+
payload = {
82+
"srcPath": SPResPath(srcPath),
83+
"destPath": SPResPath(destPath),
84+
"options": options
85+
}
86+
qry = ServiceOperationQuery(util, "MoveFolderByPath", None, payload, None, None)
87+
qry.static = True
88+
context.add_query(qry)
89+
return util

office365/sharepoint/views/view.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from office365.sharepoint.contenttypes.content_type_id import ContentTypeId
77
from office365.sharepoint.listitems.caml.caml_query import CamlQuery
88
from office365.sharepoint.views.view_field_collection import ViewFieldCollection
9+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
910

1011

1112
class View(BaseEntity):
@@ -110,15 +111,31 @@ def base_view_id(self):
110111
"""Gets a value that specifies the base view identifier of the list view."""
111112
return self.properties.get('BaseViewId', None)
112113

114+
@property
115+
def server_relative_path(self):
116+
"""Gets the server-relative Path of the View.
117+
:rtype: SPResPath or None
118+
"""
119+
return self.properties.get("ServerRelativePath", SPResPath(None))
120+
113121
def get_property(self, name):
114-
if name == "ViewFields":
115-
return self.view_fields
116-
elif name == "DefaultView":
117-
return self.default_view
122+
property_mapping = {
123+
"ViewFields": self.view_fields,
124+
"DefaultView": self.default_view,
125+
"ServerRelativePath": self.server_relative_path
126+
}
127+
if name in property_mapping:
128+
return property_mapping[name]
118129
else:
119130
return super(View, self).get_property(name)
120131

121132
def set_property(self, name, value, persist_changes=True):
133+
"""
134+
135+
:type name: str
136+
:type value: any
137+
:type persist_changes: bool
138+
"""
122139
super(View, self).set_property(name, value, persist_changes)
123140
# fallback: create a new resource path
124141
if self._resource_path is None:

office365/sharepoint/webs/web.py

+8
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from office365.sharepoint.webs.regional_settings import RegionalSettings
4242
from office365.sharepoint.webs.web_information_collection import WebInformationCollection
4343
from office365.sharepoint.webs.web_template_collection import WebTemplateCollection
44+
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
4445

4546

4647
class Web(SecurableObject):
@@ -849,6 +850,13 @@ def ui_version(self):
849850
"""
850851
return self.properties.get('UIVersion', None)
851852

853+
@property
854+
def server_relative_path(self):
855+
"""Gets the server-relative Path of the Web.
856+
:rtype: SPResPath or None
857+
"""
858+
return self.properties.get("ServerRelativePath", SPResPath(None))
859+
852860
def get_property(self, name):
853861
if name == "ContentTypes":
854862
return self.content_types

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name="Office365-REST-Python-Client",
13-
version="2.3.4",
13+
version="2.3.5",
1414
author="Vadim Gremyachev",
1515
author_email="[email protected]",
1616
maintainer="Konrad Gądek, Domenico Di Nicola",

tests/teams/test_team.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def setUpClass(cls):
2626

2727
def test1_ensure_team(self):
2828
self.__class__.target_group = _create_group(self.client).execute_query()
29-
new_team = self.__class__.target_group.add_team().execute_query_retry()
29+
new_team = self.__class__.target_group.add_team().execute_query_retry(max_retry=6, timeout_secs=5)
3030
self.assertIsNotNone(new_team.id)
3131

3232
def test3_get_all_teams(self):

0 commit comments

Comments
 (0)