sharepoint-v1-api 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sharepoint_api/SharePointAPI.py +1101 -0
- sharepoint_api/SharePointList.py +202 -0
- sharepoint_api/SharePointListItem.py +196 -0
- sharepoint_api/SharePointLists.py +59 -0
- sharepoint_api/SharePointTimeRegistration.py +53 -0
- sharepoint_api/SharePointUser.py +33 -0
- sharepoint_api/SharePointUserList.py +36 -0
- sharepoint_api/__init__.py +21 -0
- sharepoint_v1_api-0.2.2.dist-info/METADATA +187 -0
- sharepoint_v1_api-0.2.2.dist-info/RECORD +13 -0
- sharepoint_v1_api-0.2.2.dist-info/WHEEL +5 -0
- sharepoint_v1_api-0.2.2.dist-info/licenses/LICENSE +19 -0
- sharepoint_v1_api-0.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1101 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import getpass
|
|
4
|
+
import json
|
|
5
|
+
import requests
|
|
6
|
+
from requests_ntlm import HttpNtlmAuth
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from .SharePointUser import SharePointUser
|
|
10
|
+
from .SharePointUserList import SharePointUserList
|
|
11
|
+
from .SharePointListItem import SharePointListItem, SharepointSiteCase
|
|
12
|
+
from .SharePointList import SharePointList, CasesList, TimeRegistrationList
|
|
13
|
+
from .SharePointLists import SharePointLists
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SharePointAPI:
|
|
17
|
+
"""High‑level client for interacting with SharePoint sites.
|
|
18
|
+
|
|
19
|
+
Provides methods for authentication, list management, file operations,
|
|
20
|
+
and time‑registration handling. All public interactions should be performed
|
|
21
|
+
through an instance created via :meth:`_compact_init`.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def _compact_init(cls, credentials: dict):
|
|
26
|
+
"""
|
|
27
|
+
Initialise a :class:`SharePointAPI` instance from a credentials dictionary.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
credentials : dict
|
|
32
|
+
Mapping containing the keys ``username``, ``password`` and ``sharepoint_url``.
|
|
33
|
+
An optional ``proxies`` key may be supplied to route HTTP requests through a proxy.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
SharePointAPI
|
|
38
|
+
A fully‑initialised ``SharePointAPI`` object ready for use.
|
|
39
|
+
|
|
40
|
+
Notes
|
|
41
|
+
-----
|
|
42
|
+
This method creates a new instance without invoking ``__init__`` directly,
|
|
43
|
+
then calls ``__init__`` with the extracted values. The stored credentials
|
|
44
|
+
are later used for NTLM authentication in all API calls.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
username = credentials['username']
|
|
48
|
+
password = credentials['password']
|
|
49
|
+
sharepoint_url = credentials['sharepoint_url']
|
|
50
|
+
proxies = {
|
|
51
|
+
} if 'proxies' not in credentials else credentials['proxies']
|
|
52
|
+
|
|
53
|
+
# Create a new instance without calling __init__ directly.
|
|
54
|
+
sharepoint_api = object.__new__(SharePointAPI)
|
|
55
|
+
# Initialise the instance with the provided credentials.
|
|
56
|
+
sharepoint_api.__init__(username, password, sharepoint_url, proxies)
|
|
57
|
+
|
|
58
|
+
return sharepoint_api
|
|
59
|
+
|
|
60
|
+
def __init__(self, username: str, password: str, sharepoint_url: str, proxies: dict):
|
|
61
|
+
"""
|
|
62
|
+
Initialise the SharePointAPI client with credentials.
|
|
63
|
+
|
|
64
|
+
Stores the username, password and SharePoint URL for NTLM authentication.
|
|
65
|
+
"""
|
|
66
|
+
# Store credentials for NTLM authentication
|
|
67
|
+
self.username = username
|
|
68
|
+
self.password = password
|
|
69
|
+
self.sharepoint_url = sharepoint_url
|
|
70
|
+
self.proxies = proxies
|
|
71
|
+
|
|
72
|
+
def _handle_response(self, response: requests.Response, success_codes: List[int]) -> requests.Response:
|
|
73
|
+
"""
|
|
74
|
+
Centralised HTTP response handling.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
response : requests.Response
|
|
79
|
+
The response object returned by ``requests``.
|
|
80
|
+
success_codes : List[int]
|
|
81
|
+
HTTP status codes that are considered successful for the caller.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
requests.Response
|
|
86
|
+
The original response if it is successful.
|
|
87
|
+
|
|
88
|
+
Raises
|
|
89
|
+
------
|
|
90
|
+
PermissionError
|
|
91
|
+
Raised for HTTP 401 Unauthorized responses.
|
|
92
|
+
FileNotFoundError
|
|
93
|
+
Raised for HTTP 404 Not Found responses.
|
|
94
|
+
ValueError
|
|
95
|
+
Raised for HTTP 400 Bad Request responses.
|
|
96
|
+
ConnectionError
|
|
97
|
+
Raised for any other unexpected status codes.
|
|
98
|
+
"""
|
|
99
|
+
status = response.status_code
|
|
100
|
+
|
|
101
|
+
if status == 401:
|
|
102
|
+
print('Request failed (401 Unauthorized):')
|
|
103
|
+
print(f'URL: {response.request.url}')
|
|
104
|
+
print(response.text)
|
|
105
|
+
raise PermissionError(
|
|
106
|
+
'Unauthorized (401) – authentication failed.')
|
|
107
|
+
|
|
108
|
+
if status == 404:
|
|
109
|
+
print('Request failed (404 Not Found):')
|
|
110
|
+
print(f'URL: {response.request.url}')
|
|
111
|
+
try:
|
|
112
|
+
print(response.json()['error']['message']['value'])
|
|
113
|
+
except Exception:
|
|
114
|
+
print('No detailed error message')
|
|
115
|
+
raise FileNotFoundError(
|
|
116
|
+
'Resource not found (404) – see printed details.')
|
|
117
|
+
|
|
118
|
+
if status == 400:
|
|
119
|
+
print('Request failed (400 Bad Request):')
|
|
120
|
+
print(response.text)
|
|
121
|
+
raise ValueError('Bad request (400) – see printed details.')
|
|
122
|
+
|
|
123
|
+
if status not in success_codes:
|
|
124
|
+
print(f'Request failed (status {status}):')
|
|
125
|
+
try:
|
|
126
|
+
error_msg = response.json().get('error', {}).get('message', {}).get('value')
|
|
127
|
+
if error_msg:
|
|
128
|
+
print(f'Error message: {error_msg}')
|
|
129
|
+
except Exception:
|
|
130
|
+
print(response.text)
|
|
131
|
+
raise ConnectionError(
|
|
132
|
+
f'Unexpected status code {status} – see printed details.')
|
|
133
|
+
|
|
134
|
+
return response
|
|
135
|
+
|
|
136
|
+
# NOTE: All API calls use NTLM authentication via ``requests_ntlm.HttpNtlmAuth``
|
|
137
|
+
# with the username and password supplied during ``SharePointAPI`` construction.
|
|
138
|
+
def _api_post_call(self, url: str, post_data: dict, form_digest_value: str | None = None, merge: bool = False) -> requests.Response:
|
|
139
|
+
"""
|
|
140
|
+
Perform a POST request against the SharePoint REST API.
|
|
141
|
+
|
|
142
|
+
Enhanced error handling:
|
|
143
|
+
* Network‑level errors (timeouts, DNS failures, etc.) are caught and re‑raised as
|
|
144
|
+
``ConnectionError`` with a helpful message.
|
|
145
|
+
* HTTP error codes are reported with status, URL and response body when available.
|
|
146
|
+
* The method always returns a ``requests.Response`` on success (status 200, 201 or 204).
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
url : str
|
|
151
|
+
The full endpoint URL.
|
|
152
|
+
post_data : dict
|
|
153
|
+
JSON‑serialisable payload to send.
|
|
154
|
+
form_digest_value : str | None, optional
|
|
155
|
+
FormDigest required for POST/PUT/MERGE operations.
|
|
156
|
+
merge : bool, optional
|
|
157
|
+
If ``True`` the ``X-HTTP-Method: MERGE`` header is added.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
requests.Response
|
|
162
|
+
The successful response object.
|
|
163
|
+
"""
|
|
164
|
+
# Build headers
|
|
165
|
+
headers = {
|
|
166
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0',
|
|
167
|
+
'accept': 'application/json;odata=verbose',
|
|
168
|
+
'Content-type': 'application/json; charset=utf-8',
|
|
169
|
+
'Cache-Control': 'no-cache',
|
|
170
|
+
'Connection': 'keep-alive',
|
|
171
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
172
|
+
'Content-Length': str(len(f'{post_data}')),
|
|
173
|
+
'If-Match': '*'
|
|
174
|
+
}
|
|
175
|
+
if form_digest_value is not None:
|
|
176
|
+
headers['X-RequestDigest'] = f'{form_digest_value}'
|
|
177
|
+
if merge:
|
|
178
|
+
headers['X-HTTP-Method'] = 'MERGE'
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
response = requests.post(
|
|
182
|
+
url,
|
|
183
|
+
auth=HttpNtlmAuth(self.username, self.password),
|
|
184
|
+
headers=headers,
|
|
185
|
+
json=post_data,
|
|
186
|
+
proxies=self.proxies,
|
|
187
|
+
timeout=30,
|
|
188
|
+
)
|
|
189
|
+
except requests.exceptions.RequestException as exc:
|
|
190
|
+
print(f'Network error during POST to {url}: {exc}')
|
|
191
|
+
raise ConnectionError(
|
|
192
|
+
f'Network error during POST request: {exc}') from exc
|
|
193
|
+
|
|
194
|
+
# Centralised response handling
|
|
195
|
+
return self._handle_response(response, [200, 201, 204])
|
|
196
|
+
|
|
197
|
+
# NOTE: All API calls use NTLM authentication via ``requests_ntlm.HttpNtlmAuth``
|
|
198
|
+
# with the username and password supplied during ``SharePointAPI`` construction.
|
|
199
|
+
def _api_put_call(self, url: str, put_data: dict, form_digest_value: str | None = None, merge: bool = False) -> requests.Response:
|
|
200
|
+
"""
|
|
201
|
+
Perform a PUT request against the SharePoint REST API.
|
|
202
|
+
|
|
203
|
+
Enhanced error handling:
|
|
204
|
+
* Network‑level errors (timeouts, DNS failures, etc.) are caught and re‑raised as
|
|
205
|
+
``ConnectionError`` with a helpful message.
|
|
206
|
+
* HTTP error codes are reported with status, URL and response body when available.
|
|
207
|
+
* The method always returns a ``requests.Response`` on success (status 200, 201 or 204).
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
url : str
|
|
212
|
+
The full endpoint URL.
|
|
213
|
+
put_data : dict
|
|
214
|
+
JSON‑serialisable payload to send.
|
|
215
|
+
form_digest_value : str | None, optional
|
|
216
|
+
FormDigest required for POST/PUT/MERGE operations.
|
|
217
|
+
merge : bool, optional
|
|
218
|
+
If ``True`` the ``X-HTTP-Method: MERGE`` header is added.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
requests.Response
|
|
223
|
+
The successful response object.
|
|
224
|
+
"""
|
|
225
|
+
# Build headers
|
|
226
|
+
headers = {
|
|
227
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0',
|
|
228
|
+
'accept': 'application/json;odata=verbose',
|
|
229
|
+
'Content-type': 'application/json; charset=utf-8',
|
|
230
|
+
'Cache-Control': 'no-cache',
|
|
231
|
+
'Connection': 'keep-alive',
|
|
232
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
233
|
+
'Content-Length': str(len(f'{put_data}')),
|
|
234
|
+
'If-Match': '*'
|
|
235
|
+
}
|
|
236
|
+
if form_digest_value is not None:
|
|
237
|
+
headers['X-RequestDigest'] = f'{form_digest_value}'
|
|
238
|
+
if merge:
|
|
239
|
+
headers['X-HTTP-Method'] = 'MERGE'
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
response = requests.put(
|
|
243
|
+
url,
|
|
244
|
+
auth=HttpNtlmAuth(self.username, self.password),
|
|
245
|
+
headers=headers,
|
|
246
|
+
json=put_data,
|
|
247
|
+
proxies=self.proxies,
|
|
248
|
+
timeout=30,
|
|
249
|
+
)
|
|
250
|
+
except requests.exceptions.RequestException as exc:
|
|
251
|
+
print(f'Network error during PUT to {url}: {exc}')
|
|
252
|
+
raise ConnectionError(
|
|
253
|
+
f'Network error during PUT request: {exc}') from exc
|
|
254
|
+
|
|
255
|
+
# Centralised response handling
|
|
256
|
+
return self._handle_response(response, [200, 201, 204])
|
|
257
|
+
|
|
258
|
+
def _api_attachment_call(self, url: str, post_data: bytes | None = None, form_digest_value: str | None = None,
|
|
259
|
+
overwrite: bool = False, x_http_method: str | None = None) -> requests.Response:
|
|
260
|
+
"""
|
|
261
|
+
Perform an attachment‑related request (POST, PUT, DELETE) against the SharePoint REST API.
|
|
262
|
+
|
|
263
|
+
Enhanced error handling:
|
|
264
|
+
* Network‑level errors (timeouts, DNS failures, etc.) are caught and re‑raised as
|
|
265
|
+
``ConnectionError`` with a helpful message.
|
|
266
|
+
* HTTP error codes are reported with status, URL and response body when available.
|
|
267
|
+
* The method always returns a ``requests.Response`` on success (status 200 or 204).
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
url : str
|
|
272
|
+
The full endpoint URL.
|
|
273
|
+
post_data : bytes | None, optional
|
|
274
|
+
Binary payload for the request (e.g., file content). If ``None`` a simple POST/DELETE is performed.
|
|
275
|
+
form_digest_value : str | None, optional
|
|
276
|
+
FormDigest required for POST/PUT/DELETE operations.
|
|
277
|
+
overwrite : bool, optional
|
|
278
|
+
If ``True`` the ``X-HTTP-Method: PUT`` header is added (used for overwriting files).
|
|
279
|
+
x_http_method : str | None, optional
|
|
280
|
+
Explicit HTTP method override (e.g., ``'DELETE'`` or ``'PUT'``). Takes precedence over ``overwrite``.
|
|
281
|
+
"""
|
|
282
|
+
# Build base headers
|
|
283
|
+
headers = {
|
|
284
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0',
|
|
285
|
+
'accept': 'application/json;odata=verbose',
|
|
286
|
+
'X-RequestDigest': f'{form_digest_value}'
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Determine effective X‑HTTP‑Method
|
|
290
|
+
if overwrite:
|
|
291
|
+
headers['X-HTTP-Method'] = "PUT"
|
|
292
|
+
if x_http_method:
|
|
293
|
+
method = x_http_method.lower()
|
|
294
|
+
if method == 'delete':
|
|
295
|
+
headers['X-HTTP-Method'] = "DELETE"
|
|
296
|
+
elif method == 'put':
|
|
297
|
+
headers['X-HTTP-Method'] = "PUT"
|
|
298
|
+
else:
|
|
299
|
+
print(f'X-HTTP-Method \"{x_http_method}\" is not implemented')
|
|
300
|
+
raise ConnectionError(
|
|
301
|
+
f'Unsupported X-HTTP-Method: {x_http_method}')
|
|
302
|
+
|
|
303
|
+
# Add Content‑Length when payload present
|
|
304
|
+
if post_data is not None:
|
|
305
|
+
headers['Content-Length'] = str(len(post_data))
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
response = requests.post(
|
|
309
|
+
url,
|
|
310
|
+
auth=HttpNtlmAuth(self.username, self.password),
|
|
311
|
+
data=post_data,
|
|
312
|
+
headers=headers,
|
|
313
|
+
proxies=self.proxies,
|
|
314
|
+
timeout=30,
|
|
315
|
+
)
|
|
316
|
+
except requests.exceptions.RequestException as exc:
|
|
317
|
+
print(f'Network error during attachment request to {url}: {exc}')
|
|
318
|
+
raise ConnectionError(
|
|
319
|
+
f'Network error during attachment request: {exc}') from exc
|
|
320
|
+
|
|
321
|
+
# Centralised response handling (attachment calls consider 200 and 204 as success)
|
|
322
|
+
return self._handle_response(response, [200, 204])
|
|
323
|
+
|
|
324
|
+
# NOTE: All API calls use NTLM authentication via ``requests_ntlm.HttpNtlmAuth``
|
|
325
|
+
# with the username and password supplied during ``SharePointAPI`` construction.
|
|
326
|
+
def _api_get_call(self, url, *args, **kwargs) -> requests.Response:
|
|
327
|
+
"""
|
|
328
|
+
Perform a GET request against the SharePoint REST API.
|
|
329
|
+
|
|
330
|
+
Enhanced error handling:
|
|
331
|
+
* Network‑level errors (timeouts, DNS failures, etc.) are caught and re‑raised as
|
|
332
|
+
``ConnectionError`` with a helpful message.
|
|
333
|
+
* HTTP error codes are reported with status, URL and response body when available.
|
|
334
|
+
* The method always returns a ``requests.Response`` on success (status 200).
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
url : str
|
|
339
|
+
The full endpoint URL.
|
|
340
|
+
"""
|
|
341
|
+
# Build headers
|
|
342
|
+
headers = {
|
|
343
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0',
|
|
344
|
+
'accept': 'application/json;odata=verbose',
|
|
345
|
+
'Content-type': 'application/json; charset=utf-8',
|
|
346
|
+
'Cache-Control': 'no-cache',
|
|
347
|
+
'Connection': 'keep-alive',
|
|
348
|
+
'Accept-Encoding': 'gzip, deflate, br'
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
response = requests.get(
|
|
353
|
+
url,
|
|
354
|
+
auth=HttpNtlmAuth(self.username, self.password),
|
|
355
|
+
headers=headers,
|
|
356
|
+
proxies=self.proxies,
|
|
357
|
+
timeout=30,
|
|
358
|
+
*args,
|
|
359
|
+
**kwargs
|
|
360
|
+
)
|
|
361
|
+
except requests.exceptions.RequestException as exc:
|
|
362
|
+
print(f'Network error during GET to {url}: {exc}')
|
|
363
|
+
raise ConnectionError(
|
|
364
|
+
f'Network error during GET request: {exc}') from exc
|
|
365
|
+
|
|
366
|
+
# Centralised response handling (GET expects 200)
|
|
367
|
+
return self._handle_response(response, [200])
|
|
368
|
+
|
|
369
|
+
def get_users(self, sharepoint_site, filters=None, select_fields=None):
|
|
370
|
+
'''
|
|
371
|
+
Returns a list of users from a given sharepoint_site.
|
|
372
|
+
Optional ``filters`` can be provided to filter the users using OData syntax.
|
|
373
|
+
Optional ``select_fields`` (list of strings) can be provided to limit the returned fields
|
|
374
|
+
via the ``$select`` query option.
|
|
375
|
+
'''
|
|
376
|
+
# Build query arguments
|
|
377
|
+
arguments = []
|
|
378
|
+
|
|
379
|
+
if filters is not None:
|
|
380
|
+
if not isinstance(filters, list):
|
|
381
|
+
filter_string = self.py2sp_conditional(filters)
|
|
382
|
+
else:
|
|
383
|
+
filter_string = self.py2sp_conditional(' and '.join(filters))
|
|
384
|
+
arguments.append(f'$filter={filter_string}')
|
|
385
|
+
|
|
386
|
+
if select_fields is not None:
|
|
387
|
+
if isinstance(select_fields, list):
|
|
388
|
+
select_string = ','.join(select_fields)
|
|
389
|
+
else:
|
|
390
|
+
select_string = str(select_fields)
|
|
391
|
+
arguments.append(f'$select={select_string}')
|
|
392
|
+
|
|
393
|
+
# Construct final URL
|
|
394
|
+
base_url = f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/siteusers'
|
|
395
|
+
if arguments:
|
|
396
|
+
url = f'{base_url}?{"&".join(arguments)}'
|
|
397
|
+
else:
|
|
398
|
+
url = base_url
|
|
399
|
+
|
|
400
|
+
r = self._api_get_call(url)
|
|
401
|
+
|
|
402
|
+
users = []
|
|
403
|
+
|
|
404
|
+
for user_settings in r.json()["d"]["results"]:
|
|
405
|
+
users.append(SharePointUser(user_settings))
|
|
406
|
+
|
|
407
|
+
return SharePointUserList(sharepoint_site, users)
|
|
408
|
+
|
|
409
|
+
def get_group_users(self, sharepoint_site, group_name: str, filters=None, select_fields=None) -> SharePointUserList:
|
|
410
|
+
"""
|
|
411
|
+
Retrieve users that are members of a SharePoint group, with optional OData filters
|
|
412
|
+
and field selection.
|
|
413
|
+
|
|
414
|
+
Parameters
|
|
415
|
+
----------
|
|
416
|
+
sharepoint_site : str
|
|
417
|
+
Identifier of the SharePoint site (e.g., 'mySite').
|
|
418
|
+
group_name : str
|
|
419
|
+
Exact name of the SharePoint group.
|
|
420
|
+
filters : str or list, optional
|
|
421
|
+
OData filter expression(s) to limit the returned users.
|
|
422
|
+
select_fields : list or str, optional
|
|
423
|
+
Fields to include in the response via the ``$select`` query option.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
SharePointUserList
|
|
428
|
+
A list‑like container with :class:`SharePointUser` objects for each member.
|
|
429
|
+
"""
|
|
430
|
+
# Build the request URL for the group's users.
|
|
431
|
+
# SharePoint REST endpoint: /_api/web/sitegroups/GetByName('<group_name>')/users
|
|
432
|
+
base_url = (
|
|
433
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}"
|
|
434
|
+
f"/_api/web/sitegroups/GetByName('{group_name}')/users"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Build query arguments (filters / select)
|
|
438
|
+
arguments = []
|
|
439
|
+
|
|
440
|
+
if filters is not None:
|
|
441
|
+
if not isinstance(filters, list):
|
|
442
|
+
filter_string = self.py2sp_conditional(filters)
|
|
443
|
+
else:
|
|
444
|
+
filter_string = self.py2sp_conditional(' and '.join(filters))
|
|
445
|
+
arguments.append(f"$filter={filter_string}")
|
|
446
|
+
|
|
447
|
+
if select_fields is not None:
|
|
448
|
+
if isinstance(select_fields, list):
|
|
449
|
+
select_string = ','.join(select_fields)
|
|
450
|
+
else:
|
|
451
|
+
select_string = str(select_fields)
|
|
452
|
+
arguments.append(f"$select={select_string}")
|
|
453
|
+
|
|
454
|
+
# Construct final URL
|
|
455
|
+
if arguments:
|
|
456
|
+
url = f"{base_url}?{'&'.join(arguments)}"
|
|
457
|
+
else:
|
|
458
|
+
url = base_url
|
|
459
|
+
|
|
460
|
+
# Perform the GET request.
|
|
461
|
+
r = self._api_get_call(url)
|
|
462
|
+
|
|
463
|
+
# Parse the JSON payload – users are under ``d.results``.
|
|
464
|
+
users = [
|
|
465
|
+
SharePointUser(user_settings)
|
|
466
|
+
for user_settings in r.json()["d"]["results"]
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
# Return a ``SharePointUserList`` (consistent with other user‑retrieval methods).
|
|
470
|
+
return SharePointUserList(sharepoint_site, users)
|
|
471
|
+
|
|
472
|
+
def get_user(self, sharepoint_site, user_id, select_fields=None):
|
|
473
|
+
'''
|
|
474
|
+
Returns a single user from a given sharepoint_site.
|
|
475
|
+
If ``user_id`` is ``None`` an empty :class:`SharePointUser` instance is returned.
|
|
476
|
+
Optional ``select_fields`` (list of strings) can be provided to limit the fields
|
|
477
|
+
returned via the ``$select`` OData query option.
|
|
478
|
+
'''
|
|
479
|
+
if user_id is None:
|
|
480
|
+
return SharePointUser()
|
|
481
|
+
|
|
482
|
+
# Build the request URL, adding $select if needed.
|
|
483
|
+
base_url = f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/getUserById({user_id})'
|
|
484
|
+
if select_fields is not None:
|
|
485
|
+
if isinstance(select_fields, list):
|
|
486
|
+
select_string = ','.join(select_fields)
|
|
487
|
+
else:
|
|
488
|
+
select_string = str(select_fields)
|
|
489
|
+
url = f'{base_url}?$select={select_string}'
|
|
490
|
+
else:
|
|
491
|
+
url = base_url
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
r = self._api_get_call(url)
|
|
495
|
+
except ConnectionError:
|
|
496
|
+
# If the user does not exist, return an empty user object for graceful handling.
|
|
497
|
+
print(
|
|
498
|
+
f"User with ID {user_id} does not exist in sharepoint_site {sharepoint_site}")
|
|
499
|
+
return SharePointUser()
|
|
500
|
+
|
|
501
|
+
# The endpoint returns the user object directly under the ``d`` key.
|
|
502
|
+
user_settings = r.json()["d"]
|
|
503
|
+
return SharePointUser(user_settings)
|
|
504
|
+
|
|
505
|
+
# SP Lists
|
|
506
|
+
|
|
507
|
+
def get_lists(self, sharepoint_site):
|
|
508
|
+
'''
|
|
509
|
+
Returns a list of lists from a given sharepoint_site
|
|
510
|
+
'''
|
|
511
|
+
r = self._api_get_call(
|
|
512
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists')
|
|
513
|
+
|
|
514
|
+
_lists = []
|
|
515
|
+
for list_props in r.json()["d"]["results"]:
|
|
516
|
+
_lists.append(SharePointList(self, sharepoint_site, list_props))
|
|
517
|
+
|
|
518
|
+
return SharePointLists(_lists)
|
|
519
|
+
|
|
520
|
+
def get_list(self, sharepoint_site, sp_list, filters=None, top=1000, view_path=None, select_fields=None, SPListType: SharePointList = SharePointList) -> SharePointList:
|
|
521
|
+
'''
|
|
522
|
+
Returns a list from a given sharepoint_site using its guid
|
|
523
|
+
|
|
524
|
+
Returns a subset of items from a list
|
|
525
|
+
|
|
526
|
+
sharepoint_site:
|
|
527
|
+
guid: the guid of the list to retrieve items from
|
|
528
|
+
|
|
529
|
+
Optional ``select_fields`` (list of strings) can be provided to limit the fields
|
|
530
|
+
returned for each list item via the ``$select`` OData query option.
|
|
531
|
+
'''
|
|
532
|
+
|
|
533
|
+
# Uses either guid or SharePointList
|
|
534
|
+
if isinstance(sp_list, SharePointList):
|
|
535
|
+
guid = sp_list.guid
|
|
536
|
+
r = self._api_get_call(
|
|
537
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')')
|
|
538
|
+
sp_list = SPListType(self, sharepoint_site, r.json()["d"])
|
|
539
|
+
|
|
540
|
+
elif isinstance(sp_list, str):
|
|
541
|
+
guid = sp_list
|
|
542
|
+
r = self._api_get_call(
|
|
543
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')')
|
|
544
|
+
sp_list = SPListType(self, sharepoint_site, r.json()["d"])
|
|
545
|
+
else:
|
|
546
|
+
raise TypeError(
|
|
547
|
+
'Invalid sp_list argument; expected SharePointList or str')
|
|
548
|
+
|
|
549
|
+
arguments = []
|
|
550
|
+
|
|
551
|
+
if filters is not None:
|
|
552
|
+
if not isinstance(filters, list):
|
|
553
|
+
filter_string = self.py2sp_conditional(filters)
|
|
554
|
+
# print(filters)
|
|
555
|
+
# raise('invalid search filters')
|
|
556
|
+
else:
|
|
557
|
+
|
|
558
|
+
filter_string = self.py2sp_conditional(' and '.join(filters))
|
|
559
|
+
arguments.append(f'$filter={filter_string}')
|
|
560
|
+
|
|
561
|
+
if view_path is not None:
|
|
562
|
+
# Shows top x items
|
|
563
|
+
arguments.append(f'$ViewPath={view_path}')
|
|
564
|
+
|
|
565
|
+
if top is not None:
|
|
566
|
+
# Shows top x items
|
|
567
|
+
arguments.append(f'$top={top}')
|
|
568
|
+
|
|
569
|
+
if select_fields is not None:
|
|
570
|
+
if isinstance(select_fields, list):
|
|
571
|
+
select_string = ','.join(select_fields)
|
|
572
|
+
else:
|
|
573
|
+
select_string = str(select_fields)
|
|
574
|
+
arguments.append(f'$select={select_string}')
|
|
575
|
+
|
|
576
|
+
r = self._api_get_call(
|
|
577
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items?{"&".join(arguments)}')
|
|
578
|
+
|
|
579
|
+
items = [SPListType.SPItem(self, sharepoint_site, guid, item_settings)
|
|
580
|
+
for item_settings in r.json()["d"]["results"]]
|
|
581
|
+
sp_list.append_items(items)
|
|
582
|
+
return sp_list
|
|
583
|
+
|
|
584
|
+
def get_list_by_name(self, sharepoint_site, sp_list_name: str, filters=None, top=1000, view_path=None, select_fields=None, SPListType: SharePointList = SharePointList) -> SharePointList:
|
|
585
|
+
'''
|
|
586
|
+
Returns a list from a given sharepoint_site filtering by list name
|
|
587
|
+
|
|
588
|
+
Returns a subset of items from a list
|
|
589
|
+
|
|
590
|
+
sharepoint_site: The sharepoint_site containing the list
|
|
591
|
+
sp_list_name: the name of the list
|
|
592
|
+
filters: query filters
|
|
593
|
+
top: Maximum items to query from the list
|
|
594
|
+
|
|
595
|
+
Optional ``select_fields`` (list of strings) can be provided to limit the fields
|
|
596
|
+
returned for each list item via the ``$select`` OData query option.
|
|
597
|
+
'''
|
|
598
|
+
# Retrieve the list directly by its title using SharePoint REST API v1
|
|
599
|
+
try:
|
|
600
|
+
r = self._api_get_call(
|
|
601
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists/GetByTitle('{sp_list_name}')")
|
|
602
|
+
except ConnectionError:
|
|
603
|
+
# The list was not found – provide a clear error message
|
|
604
|
+
msg = f"List '{sp_list_name}' does not exist in sharepoint_site {sharepoint_site}"
|
|
605
|
+
print(msg)
|
|
606
|
+
raise ConnectionError(msg)
|
|
607
|
+
|
|
608
|
+
sp_list = SPListType(self, sharepoint_site, r.json()["d"])
|
|
609
|
+
|
|
610
|
+
guid = sp_list.guid
|
|
611
|
+
|
|
612
|
+
arguments = []
|
|
613
|
+
|
|
614
|
+
if filters is not None:
|
|
615
|
+
if not isinstance(filters, list):
|
|
616
|
+
filter_string = self.py2sp_conditional(filters)
|
|
617
|
+
else:
|
|
618
|
+
filter_string = self.py2sp_conditional(' and '.join(filters))
|
|
619
|
+
arguments.append(f"$filter={filter_string}")
|
|
620
|
+
|
|
621
|
+
if view_path is not None:
|
|
622
|
+
arguments.append(f"$ViewPath={view_path}")
|
|
623
|
+
|
|
624
|
+
if top is not None:
|
|
625
|
+
arguments.append(f"$top={top}")
|
|
626
|
+
|
|
627
|
+
if select_fields is not None:
|
|
628
|
+
if isinstance(select_fields, list):
|
|
629
|
+
select_string = ','.join(select_fields)
|
|
630
|
+
else:
|
|
631
|
+
select_string = str(select_fields)
|
|
632
|
+
arguments.append(f"$select={select_string}")
|
|
633
|
+
|
|
634
|
+
r = self._api_get_call(
|
|
635
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid'{guid}')/items?{'&'.join(arguments)}")
|
|
636
|
+
items = [SPListType.SPItem(self, sharepoint_site, guid, item_settings)
|
|
637
|
+
for item_settings in r.json()["d"]["results"]]
|
|
638
|
+
sp_list.append_items(items)
|
|
639
|
+
return sp_list
|
|
640
|
+
|
|
641
|
+
def get_list_from_json(self, file_name, SPListType: SharePointList = SharePointList) -> SharePointList:
|
|
642
|
+
'''
|
|
643
|
+
Returns a list from a sharepoint_site based on a json file.
|
|
644
|
+
|
|
645
|
+
file_name: the json file to load the list from
|
|
646
|
+
|
|
647
|
+
'''
|
|
648
|
+
try:
|
|
649
|
+
with open(file_name, 'r') as fp:
|
|
650
|
+
data_dict = json.load(fp)
|
|
651
|
+
|
|
652
|
+
sharepoint_site = data_dict['sharepoint_site']
|
|
653
|
+
guid = data_dict['GUID']
|
|
654
|
+
|
|
655
|
+
cases = []
|
|
656
|
+
for case in data_dict['cases']:
|
|
657
|
+
settings = case["settings"]
|
|
658
|
+
versions = None if "versions" not in case else case["versions"]
|
|
659
|
+
cases.append(SPListType.SPItem(
|
|
660
|
+
self, sharepoint_site, guid, settings, versions))
|
|
661
|
+
|
|
662
|
+
return SPListType(self, sharepoint_site, data_dict['Settings'], cases)
|
|
663
|
+
except FileNotFoundError as err:
|
|
664
|
+
print(f"File '{file_name}' was not found")
|
|
665
|
+
raise err
|
|
666
|
+
except Exception as err:
|
|
667
|
+
raise err
|
|
668
|
+
|
|
669
|
+
# SP Item
|
|
670
|
+
|
|
671
|
+
def get_item(self, sharepoint_site, sp_list, item_id, select_fields=None) -> SharePointListItem:
|
|
672
|
+
'''
|
|
673
|
+
Returns a single list item from a given sharepoint_site.
|
|
674
|
+
Optional ``select_fields`` (list of strings) can be provided to limit the fields
|
|
675
|
+
returned via the ``$select`` OData query option.
|
|
676
|
+
'''
|
|
677
|
+
|
|
678
|
+
if isinstance(sp_list, SharePointList):
|
|
679
|
+
guid = sp_list.guid
|
|
680
|
+
|
|
681
|
+
elif isinstance(sp_list, str):
|
|
682
|
+
guid = sp_list
|
|
683
|
+
else:
|
|
684
|
+
raise TypeError(
|
|
685
|
+
'Invalid sp_list argument; expected SharePointList or str')
|
|
686
|
+
|
|
687
|
+
# Build the request URL, adding $select if needed.
|
|
688
|
+
if select_fields is not None:
|
|
689
|
+
if isinstance(select_fields, list):
|
|
690
|
+
select_string = ','.join(select_fields)
|
|
691
|
+
else:
|
|
692
|
+
select_string = str(select_fields)
|
|
693
|
+
url = f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})?$select={select_string}'
|
|
694
|
+
else:
|
|
695
|
+
url = f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})'
|
|
696
|
+
|
|
697
|
+
r = self._api_get_call(url)
|
|
698
|
+
|
|
699
|
+
settings = r.json()["d"]
|
|
700
|
+
return SharePointListItem(self, sharepoint_site, guid, settings)
|
|
701
|
+
|
|
702
|
+
def create_item(self, sharepoint_site, sp_list, data) -> SharePointListItem:
|
|
703
|
+
# Uses either guid or SharePointList
|
|
704
|
+
if isinstance(sp_list, SharePointList):
|
|
705
|
+
guid = sp_list.guid
|
|
706
|
+
elif isinstance(sp_list, str):
|
|
707
|
+
guid = sp_list
|
|
708
|
+
r = self._api_get_call(
|
|
709
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')')
|
|
710
|
+
sp_list = SharePointList(self,
|
|
711
|
+
sharepoint_site, r.json()["d"]["results"][0])
|
|
712
|
+
|
|
713
|
+
r = self._api_post_call(
|
|
714
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
715
|
+
|
|
716
|
+
form_digest_value = r.json(
|
|
717
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
718
|
+
|
|
719
|
+
r = self._api_post_call(
|
|
720
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items', data, form_digest_value)
|
|
721
|
+
|
|
722
|
+
settings = r.json()["d"]
|
|
723
|
+
return SharePointListItem(self, sharepoint_site, guid, settings)
|
|
724
|
+
|
|
725
|
+
def update_item(self, sharepoint_site, sp_list, item_id, data) -> None:
|
|
726
|
+
'''
|
|
727
|
+
Update a sharepoint item
|
|
728
|
+
|
|
729
|
+
sharepoint_site: The sharepoint_site containing the item
|
|
730
|
+
sp_list: The list containing the item
|
|
731
|
+
item_id: The id of the item
|
|
732
|
+
data: Data to push to the item
|
|
733
|
+
'''
|
|
734
|
+
if isinstance(sp_list, SharePointList):
|
|
735
|
+
guid = sp_list.guid
|
|
736
|
+
elif isinstance(sp_list, str):
|
|
737
|
+
guid = sp_list
|
|
738
|
+
else:
|
|
739
|
+
raise TypeError(
|
|
740
|
+
'Only "SharePointList" and "str" types are allowed')
|
|
741
|
+
|
|
742
|
+
r = self._api_post_call(
|
|
743
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
744
|
+
|
|
745
|
+
form_digest_value = r.json(
|
|
746
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
747
|
+
|
|
748
|
+
r = self._api_post_call(
|
|
749
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})', data, form_digest_value, merge=True)
|
|
750
|
+
|
|
751
|
+
return r
|
|
752
|
+
|
|
753
|
+
def attach_file(self, sharepoint_site, sp_list, item, file_name, file_content) -> dict:
|
|
754
|
+
# Uses either guid or SharePointList
|
|
755
|
+
if isinstance(sp_list, SharePointList):
|
|
756
|
+
guid = sp_list.guid
|
|
757
|
+
elif isinstance(sp_list, str):
|
|
758
|
+
guid = sp_list
|
|
759
|
+
else:
|
|
760
|
+
raise TypeError(
|
|
761
|
+
'Only "SharePointList" and "str" types are allowed')
|
|
762
|
+
|
|
763
|
+
r = self._api_post_call(
|
|
764
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
765
|
+
form_digest_value = r.json(
|
|
766
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
767
|
+
|
|
768
|
+
r = self._api_attachment_call(
|
|
769
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item.Id})/AttachmentFiles/ add(FileName='{file_name}')", file_content, form_digest_value)
|
|
770
|
+
|
|
771
|
+
return r.json()
|
|
772
|
+
|
|
773
|
+
def get_item_versions(self, sharepoint_site, sp_list, item_id, select_fields=[]) -> list:
|
|
774
|
+
'''
|
|
775
|
+
Returns a list of users from a given sharepoint_site
|
|
776
|
+
'''
|
|
777
|
+
|
|
778
|
+
if isinstance(sp_list, SharePointList):
|
|
779
|
+
guid = sp_list.guid
|
|
780
|
+
|
|
781
|
+
elif isinstance(sp_list, str):
|
|
782
|
+
guid = sp_list
|
|
783
|
+
else:
|
|
784
|
+
raise TypeError(
|
|
785
|
+
'Invalid sp_list argument; expected SharePointList or str')
|
|
786
|
+
|
|
787
|
+
if select_fields:
|
|
788
|
+
r = self._api_get_call(
|
|
789
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})/versions?$select={",".join(select_fields)}')
|
|
790
|
+
else:
|
|
791
|
+
r = self._api_get_call(
|
|
792
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})/Versions')
|
|
793
|
+
|
|
794
|
+
versions = r.json()["d"]['results']
|
|
795
|
+
return versions
|
|
796
|
+
|
|
797
|
+
# Cases
|
|
798
|
+
|
|
799
|
+
def get_cases_list_from_json(self, file_name) -> CasesList:
|
|
800
|
+
'''
|
|
801
|
+
Returns a cases list from a sharepoint_site based on a json file.
|
|
802
|
+
|
|
803
|
+
file_name: the json file to load the list from
|
|
804
|
+
|
|
805
|
+
'''
|
|
806
|
+
|
|
807
|
+
return self.get_list_from_json(file_name, SPListType=CasesList)
|
|
808
|
+
|
|
809
|
+
def get_cases_list(self, sharepoint_site, sp_list, filters=None, top=1000, view_path=None, select_fields=None) -> CasesList:
|
|
810
|
+
'''
|
|
811
|
+
Returns a cases list from a given sharepoint_site using its guid
|
|
812
|
+
|
|
813
|
+
Returns a subset of items from a list
|
|
814
|
+
|
|
815
|
+
sharepoint_site: The sharepoint_site containing the list
|
|
816
|
+
sp_list: the guid of the list to retrieve items from
|
|
817
|
+
filters: query filters
|
|
818
|
+
top: Maximum items to query from the list
|
|
819
|
+
'''
|
|
820
|
+
|
|
821
|
+
return self.get_list(sharepoint_site, sp_list, filters, top, view_path, select_fields, SPListType=CasesList)
|
|
822
|
+
|
|
823
|
+
def get_cases_list_by_name(self, sharepoint_site, sp_list_name: str = 'Cases', filters=None, top=1000, view_path=None, select_fields=None) -> CasesList:
|
|
824
|
+
'''
|
|
825
|
+
Returns a cases list from a given sharepoint_site filtering by list name
|
|
826
|
+
|
|
827
|
+
sharepoint_site: The sharepoint_site containing the list
|
|
828
|
+
sp_list_name: the name of the list
|
|
829
|
+
filters: query filters
|
|
830
|
+
top: Maximum items to query from the list
|
|
831
|
+
'''
|
|
832
|
+
return self.get_list_by_name(sharepoint_site, sp_list_name, filters, top, view_path, select_fields, SPListType=CasesList)
|
|
833
|
+
|
|
834
|
+
def get_case(self, sharepoint_site, sp_list, item_id) -> SharePointListItem:
|
|
835
|
+
'''
|
|
836
|
+
Returns a list of users from a given sharepoint_site
|
|
837
|
+
'''
|
|
838
|
+
|
|
839
|
+
if isinstance(sp_list, SharePointList):
|
|
840
|
+
guid = sp_list.guid
|
|
841
|
+
|
|
842
|
+
elif isinstance(sp_list, str):
|
|
843
|
+
guid = sp_list
|
|
844
|
+
else:
|
|
845
|
+
raise TypeError(
|
|
846
|
+
'Invalid sp_list argument; expected SharePointList or str')
|
|
847
|
+
|
|
848
|
+
r = self._api_get_call(
|
|
849
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})')
|
|
850
|
+
|
|
851
|
+
settings = r.json()["d"]
|
|
852
|
+
return SharepointSiteCase(self, sharepoint_site, guid, settings)
|
|
853
|
+
|
|
854
|
+
# Time Registration
|
|
855
|
+
|
|
856
|
+
def get_time_registration_list_from_json(self, file_name) -> TimeRegistrationList:
|
|
857
|
+
'''
|
|
858
|
+
Returns a Time Registration list from a sharepoint_site based on a json file.
|
|
859
|
+
|
|
860
|
+
file_name: the json file to load the list from
|
|
861
|
+
|
|
862
|
+
'''
|
|
863
|
+
|
|
864
|
+
return self.get_list_from_json(file_name, SPListType=TimeRegistrationList)
|
|
865
|
+
|
|
866
|
+
def get_time_registration_list(self, sharepoint_site, sp_list, filters=None, top=1000, view_path=None, select_fields=None) -> TimeRegistrationList:
|
|
867
|
+
'''
|
|
868
|
+
Returns a Time Registration list from a given sharepoint_site using its guid
|
|
869
|
+
|
|
870
|
+
Returns a subset of items from a list
|
|
871
|
+
|
|
872
|
+
sharepoint_site: The sharepoint_site containing the list
|
|
873
|
+
sp_list: the guid of the list to retrieve items from
|
|
874
|
+
filters: query filters
|
|
875
|
+
top: Maximum items to query from the list
|
|
876
|
+
'''
|
|
877
|
+
|
|
878
|
+
return self.get_list(sharepoint_site, sp_list, filters, top, view_path, select_fields, SPListType=TimeRegistrationList)
|
|
879
|
+
|
|
880
|
+
def get_time_registration_list_by_name(self, sharepoint_site, sp_list_name: str, filters=None, top=1000, view_path=None, select_fields=None) -> TimeRegistrationList:
|
|
881
|
+
'''
|
|
882
|
+
Returns a Time Registration list from a given sharepoint_site filtering by list name
|
|
883
|
+
|
|
884
|
+
sharepoint_site: The sharepoint_site containing the list
|
|
885
|
+
sp_list_name: the name of the list
|
|
886
|
+
filters: query filters
|
|
887
|
+
top: Maximum items to query from the list
|
|
888
|
+
'''
|
|
889
|
+
return self.get_list_by_name(sharepoint_site, sp_list_name, filters, top, view_path, select_fields, SPListType=TimeRegistrationList)
|
|
890
|
+
|
|
891
|
+
# SP Files
|
|
892
|
+
|
|
893
|
+
def folder_exists(self, sharepoint_site, folder, in_doc_lib=True):
|
|
894
|
+
"""
|
|
895
|
+
Check whether a folder exists in the given SharePoint site.
|
|
896
|
+
|
|
897
|
+
Parameters
|
|
898
|
+
----------
|
|
899
|
+
sharepoint_site : str
|
|
900
|
+
The SharePoint site identifier.
|
|
901
|
+
folder : str
|
|
902
|
+
The folder name (relative to the document library if ``in_doc_lib`` is True).
|
|
903
|
+
in_doc_lib : bool, optional
|
|
904
|
+
If True, the folder path is prefixed with ``DocumentLibrary``. Defaults to True.
|
|
905
|
+
|
|
906
|
+
Returns
|
|
907
|
+
-------
|
|
908
|
+
bool
|
|
909
|
+
``True`` if the folder exists, ``False`` otherwise.
|
|
910
|
+
"""
|
|
911
|
+
if in_doc_lib:
|
|
912
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
913
|
+
|
|
914
|
+
r = self._api_get_call(
|
|
915
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('/cases/{sharepoint_site}/{folder}')/ListItemAllFields")
|
|
916
|
+
|
|
917
|
+
return False if ("ListItemAllFields" in r.json()["d"] and r.json()["d"]["ListItemAllFields"] is None) else True
|
|
918
|
+
|
|
919
|
+
def create_new_folder(self, sharepoint_site, folder, new_folder, in_doc_lib=True):
|
|
920
|
+
'''
|
|
921
|
+
Creates a new folder
|
|
922
|
+
'''
|
|
923
|
+
|
|
924
|
+
if in_doc_lib:
|
|
925
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
926
|
+
|
|
927
|
+
r = self._api_post_call(
|
|
928
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
929
|
+
form_digest_value = r.json(
|
|
930
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
931
|
+
|
|
932
|
+
r = self._api_post_call(f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/web/folders",
|
|
933
|
+
form_digest_value=form_digest_value,
|
|
934
|
+
post_data={
|
|
935
|
+
"ServerRelativeUrl": f'/cases/{sharepoint_site}/{folder}/{new_folder}',
|
|
936
|
+
}
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
return r.status_code
|
|
940
|
+
|
|
941
|
+
def get_files(self, sharepoint_site, folder, in_doc_lib=True):
|
|
942
|
+
'''
|
|
943
|
+
Returns a list of files from a folder in a given sharepoint_site
|
|
944
|
+
'''
|
|
945
|
+
|
|
946
|
+
if in_doc_lib:
|
|
947
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
948
|
+
|
|
949
|
+
r = self._api_get_call(
|
|
950
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('/cases/{sharepoint_site}/{folder}')/Files")
|
|
951
|
+
|
|
952
|
+
return r.json()["d"]["results"]
|
|
953
|
+
|
|
954
|
+
def get_file_content(self, sharepoint_site, folder, file, in_doc_lib=True):
|
|
955
|
+
'''
|
|
956
|
+
Downloads and saves a file from a folder in a given sharepoint_site
|
|
957
|
+
'''
|
|
958
|
+
|
|
959
|
+
if in_doc_lib:
|
|
960
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
961
|
+
|
|
962
|
+
r = self._api_get_call(
|
|
963
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('/cases/{sharepoint_site}/{folder}')/Files('{file}')/$value")
|
|
964
|
+
return r._content
|
|
965
|
+
|
|
966
|
+
def download_file(self, sharepoint_site, folder, file, out_file=None, in_doc_lib=True):
|
|
967
|
+
'''
|
|
968
|
+
Downloads and saves a file from a folder in a given sharepoint_site
|
|
969
|
+
'''
|
|
970
|
+
|
|
971
|
+
if in_doc_lib:
|
|
972
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
973
|
+
|
|
974
|
+
if not out_file:
|
|
975
|
+
out_file = file
|
|
976
|
+
|
|
977
|
+
file_content = self.get_file_content(
|
|
978
|
+
sharepoint_site, folder, file, in_doc_lib)
|
|
979
|
+
|
|
980
|
+
with open(out_file, 'wb') as f:
|
|
981
|
+
f.write(file_content)
|
|
982
|
+
|
|
983
|
+
def upload_file_content(self, sharepoint_site, folder, file, file_content, overwrite=False, in_doc_lib=True):
|
|
984
|
+
'''
|
|
985
|
+
Uploads a file to a folder in a given sharepoint_site
|
|
986
|
+
'''
|
|
987
|
+
|
|
988
|
+
if in_doc_lib:
|
|
989
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
990
|
+
|
|
991
|
+
r = self._api_post_call(
|
|
992
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
993
|
+
form_digest_value = r.json(
|
|
994
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
995
|
+
|
|
996
|
+
r = self._api_attachment_call(
|
|
997
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('/cases/{sharepoint_site}/{folder}')/Files/add(url='{file}',overwrite={str(overwrite).lower()})",
|
|
998
|
+
file_content,
|
|
999
|
+
form_digest_value)
|
|
1000
|
+
return r.json()
|
|
1001
|
+
|
|
1002
|
+
def upload_file(self, sharepoint_site, folder, file, in_file=None, overwrite=False, in_doc_lib=True):
|
|
1003
|
+
'''
|
|
1004
|
+
Uploads a file to a folder in a given sharepoint_site
|
|
1005
|
+
'''
|
|
1006
|
+
|
|
1007
|
+
if in_doc_lib:
|
|
1008
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
1009
|
+
|
|
1010
|
+
if not in_file:
|
|
1011
|
+
in_file = file
|
|
1012
|
+
with open(in_file, 'rb') as f:
|
|
1013
|
+
file_content = f.read()
|
|
1014
|
+
|
|
1015
|
+
return self.upload_file_content(sharepoint_site, folder, file, file_content, overwrite, False)
|
|
1016
|
+
|
|
1017
|
+
def delete_file(self, sharepoint_site, folder, file, in_doc_lib=True):
|
|
1018
|
+
'''
|
|
1019
|
+
Uploads a file to a folder in a given sharepoint_site
|
|
1020
|
+
'''
|
|
1021
|
+
|
|
1022
|
+
if in_doc_lib:
|
|
1023
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
1024
|
+
|
|
1025
|
+
r = self._api_post_call(
|
|
1026
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
1027
|
+
form_digest_value = r.json(
|
|
1028
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
1029
|
+
|
|
1030
|
+
r = self._api_attachment_call(
|
|
1031
|
+
url=f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('/cases/{sharepoint_site}/{folder}')/Files('{file}')",
|
|
1032
|
+
post_data=None,
|
|
1033
|
+
form_digest_value=form_digest_value,
|
|
1034
|
+
x_http_method='DELETE')
|
|
1035
|
+
|
|
1036
|
+
def copy_file(self, sharepoint_site, folder, file, out_folder=None, out_file=None, overwrite=False, in_doc_lib=True):
|
|
1037
|
+
"""
|
|
1038
|
+
Copy a file within a SharePoint site.
|
|
1039
|
+
|
|
1040
|
+
Parameters
|
|
1041
|
+
----------
|
|
1042
|
+
sharepoint_site : str
|
|
1043
|
+
The SharePoint site identifier.
|
|
1044
|
+
folder : str
|
|
1045
|
+
Source folder (relative to the document library if ``in_doc_lib`` is True).
|
|
1046
|
+
file : str
|
|
1047
|
+
Name of the file to copy.
|
|
1048
|
+
out_folder : str, optional
|
|
1049
|
+
Destination folder. If omitted, defaults to the source folder.
|
|
1050
|
+
out_file : str, optional
|
|
1051
|
+
Destination file name. If omitted, defaults to ``'copy of ' + file``.
|
|
1052
|
+
overwrite : bool, optional
|
|
1053
|
+
Overwrite the destination file if it already exists. Defaults to ``False``.
|
|
1054
|
+
in_doc_lib : bool, optional
|
|
1055
|
+
Whether the operation is within the document library. Defaults to ``True``.
|
|
1056
|
+
|
|
1057
|
+
Returns
|
|
1058
|
+
-------
|
|
1059
|
+
None
|
|
1060
|
+
The method performs the copy operation via SharePoint REST API.
|
|
1061
|
+
"""
|
|
1062
|
+
if in_doc_lib:
|
|
1063
|
+
folder = os.path.join("DocumentLibrary", folder)
|
|
1064
|
+
|
|
1065
|
+
if not out_file:
|
|
1066
|
+
out_file = 'copy of '+file
|
|
1067
|
+
|
|
1068
|
+
if not out_folder:
|
|
1069
|
+
out_folder = folder
|
|
1070
|
+
|
|
1071
|
+
r = self._api_post_call(
|
|
1072
|
+
f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
|
|
1073
|
+
form_digest_value = r.json(
|
|
1074
|
+
)["d"]["GetContextWebInformation"]["FormDigestValue"]
|
|
1075
|
+
|
|
1076
|
+
in_path = f"/cases/{sharepoint_site}/{folder}"
|
|
1077
|
+
out_path = f"/cases/{sharepoint_site}/{out_folder}/{out_file}"
|
|
1078
|
+
|
|
1079
|
+
r = self._api_attachment_call(
|
|
1080
|
+
f"{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/GetFolderByServerRelativeUrl('{in_path}')/Files('{file}')/copyto(strnewurl='{out_path}',boverwrite={str(overwrite).lower()})",
|
|
1081
|
+
None,
|
|
1082
|
+
form_digest_value)
|
|
1083
|
+
|
|
1084
|
+
# STATIC METHODS
|
|
1085
|
+
|
|
1086
|
+
@staticmethod
|
|
1087
|
+
def py2sp_conditional(conditional: str):
|
|
1088
|
+
"""
|
|
1089
|
+
Convert a Python conditional expression to SharePoint OData query syntax.
|
|
1090
|
+
|
|
1091
|
+
Parameters
|
|
1092
|
+
----------
|
|
1093
|
+
conditional : str
|
|
1094
|
+
A conditional expression using Python comparison operators.
|
|
1095
|
+
|
|
1096
|
+
Returns
|
|
1097
|
+
-------
|
|
1098
|
+
str
|
|
1099
|
+
The expression with operators replaced by their OData equivalents.
|
|
1100
|
+
"""
|
|
1101
|
+
return conditional.replace('==', 'eq').replace('!=', 'ne').replace('>=', 'ge').replace('<=', 'le').replace('>', 'gt').replace('<', 'lt')
|