sharepoint-v1-api 0.2.2__tar.gz → 0.2.4__tar.gz

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.
Files changed (24) hide show
  1. sharepoint_v1_api-0.2.4/CHANGELOG.md +53 -0
  2. {sharepoint_v1_api-0.2.2/sharepoint_v1_api.egg-info → sharepoint_v1_api-0.2.4}/PKG-INFO +27 -3
  3. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/setup.py +1 -1
  4. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointAPI.py +123 -82
  5. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointList.py +2 -2
  6. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointListItem.py +22 -22
  7. sharepoint_v1_api-0.2.4/sharepoint_api/SharePointSite.py +188 -0
  8. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointTimeRegistration.py +6 -6
  9. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/__init__.py +3 -2
  10. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4/sharepoint_v1_api.egg-info}/PKG-INFO +27 -3
  11. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_v1_api.egg-info/SOURCES.txt +1 -0
  12. sharepoint_v1_api-0.2.2/CHANGELOG.md +0 -29
  13. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/AGENTS.md +0 -0
  14. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/LICENSE +0 -0
  15. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/MANIFEST.in +0 -0
  16. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/README.md +0 -0
  17. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/pyproject.toml +0 -0
  18. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/setup.cfg +0 -0
  19. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointLists.py +0 -0
  20. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointUser.py +0 -0
  21. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_api/SharePointUserList.py +0 -0
  22. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_v1_api.egg-info/dependency_links.txt +0 -0
  23. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_v1_api.egg-info/requires.txt +0 -0
  24. {sharepoint_v1_api-0.2.2 → sharepoint_v1_api-0.2.4}/sharepoint_v1_api.egg-info/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ ## 0.2.4 – 2025-12-03
2
+
3
+ ### Added
4
+
5
+ - New high‑level `SharePointSite` object providing lazy‑loaded site metadata
6
+
7
+ ## 0.2.3 – 2025-12-02
8
+
9
+ ### Added
10
+
11
+ - GUID validation for list identifiers with fallback to list title lookup via `GetByTitle`.
12
+ - New helper method `_is_valid_guid` in `SharePointAPI`.
13
+ - Centralized list resolution helper `_resolve_sp_list` for GUID, title, or `SharePointList` instances.
14
+ - New method `get_list_metadata` to fetch only list metadata without items.
15
+ - Type hint enhancements: added `Optional`, `Tuple` imports; updated method signatures (e.g., `get_item_versions` now uses `Optional[List[str]]`).
16
+ - Updated `get_list` to return the resolved `SharePointList` object after appending items.
17
+ - Refactored multiple methods (`get_item`, `create_item`, `update_item`, `attach_file`, `get_item_versions`, `get_case`) to use `_resolve_sp_list`, removing redundant GUID validation logic.
18
+
19
+ ### Changed
20
+
21
+ - Cleaned up stray `else:` blocks and syntax errors.
22
+ - Improved consistency of return objects across list-related methods.
23
+ - Updated imports and docstrings accordingly.
24
+
25
+ ## 0.2.2 – 2025-11-28
26
+
27
+ ### Added
28
+
29
+ - `SharePointList.fields` property to retrieve list field definitions
30
+
31
+ ## 0.2.1 – 2025-11-06
32
+
33
+ ### Added
34
+
35
+ - Optional `select_fields` parameters to list retrieval methods for more efficient queries.
36
+ - New public API methods:
37
+ - `SharePointAPI.get_group_users` – fetch users of a SharePoint group.
38
+ - Improved error handling with explicit `TypeError` exceptions.
39
+ - Detailed docstrings for core classes and methods (enhances IDE support).
40
+
41
+ ### Changed
42
+
43
+ - HTTP header handling unified; corrected `X-HTTP-Method: PUT` for full updates.
44
+ - Error handling improved: generic `sys.exit(1)` replaced with explicit `TypeError`/`ConnectionError` exceptions.
45
+
46
+ ### Fixed
47
+
48
+ - Fixed incorrect PUT header that previously sent a MERGE header.
49
+ - Minor docstring formatting issues.
50
+
51
+ ### Security
52
+
53
+ - Enforced NTLM authentication across all request helpers.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint_v1_api
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Author: Aske Bluhme Klok
5
5
  License: MIT
6
6
  Keywords: Sharepoint
@@ -156,13 +156,37 @@ for v in versions:
156
156
  ```
157
157
 
158
158
 
159
+ ## 0.2.4 – 2025-12-03
160
+
161
+ ### Added
162
+
163
+ - New high‑level `SharePointSite` object providing lazy‑loaded site metadata
164
+
165
+ ## 0.2.3 – 2025-12-02
166
+
167
+ ### Added
168
+
169
+ - GUID validation for list identifiers with fallback to list title lookup via `GetByTitle`.
170
+ - New helper method `_is_valid_guid` in `SharePointAPI`.
171
+ - Centralized list resolution helper `_resolve_sp_list` for GUID, title, or `SharePointList` instances.
172
+ - New method `get_list_metadata` to fetch only list metadata without items.
173
+ - Type hint enhancements: added `Optional`, `Tuple` imports; updated method signatures (e.g., `get_item_versions` now uses `Optional[List[str]]`).
174
+ - Updated `get_list` to return the resolved `SharePointList` object after appending items.
175
+ - Refactored multiple methods (`get_item`, `create_item`, `update_item`, `attach_file`, `get_item_versions`, `get_case`) to use `_resolve_sp_list`, removing redundant GUID validation logic.
176
+
177
+ ### Changed
178
+
179
+ - Cleaned up stray `else:` blocks and syntax errors.
180
+ - Improved consistency of return objects across list-related methods.
181
+ - Updated imports and docstrings accordingly.
182
+
159
183
  ## 0.2.2 – 2025-11-28
160
184
 
161
185
  ### Added
162
186
 
163
187
  - `SharePointList.fields` property to retrieve list field definitions
164
188
 
165
- ## 0.2.1 – 20251106
189
+ ## 0.2.1 – 2025-11-06
166
190
 
167
191
  ### Added
168
192
 
@@ -174,7 +198,7 @@ for v in versions:
174
198
 
175
199
  ### Changed
176
200
 
177
- - HTTP header handling unified; corrected `XHTTPMethod: PUT` for full updates.
201
+ - HTTP header handling unified; corrected `X-HTTP-Method: PUT` for full updates.
178
202
  - Error handling improved: generic `sys.exit(1)` replaced with explicit `TypeError`/`ConnectionError` exceptions.
179
203
 
180
204
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.4"
4
4
 
5
5
  with open("README.md", "r") as readme_file, open("CHANGELOG.md", "r") as changelog_file:
6
6
  long_description = f"{readme_file.read()}\n\n{changelog_file.read()}"
@@ -1,23 +1,24 @@
1
- import sys
2
1
  import os
3
- import getpass
4
2
  import json
3
+ from datetime import timezone
5
4
  import requests
6
5
  from requests_ntlm import HttpNtlmAuth
7
- from typing import List
6
+ from typing import List, Optional, Tuple
7
+ import re
8
8
 
9
9
  from .SharePointUser import SharePointUser
10
10
  from .SharePointUserList import SharePointUserList
11
11
  from .SharePointListItem import SharePointListItem, SharepointSiteCase
12
12
  from .SharePointList import SharePointList, CasesList, TimeRegistrationList
13
13
  from .SharePointLists import SharePointLists
14
+ from .SharePointSite import SharePointSite
14
15
 
15
16
 
16
17
  class SharePointAPI:
17
- """Highlevel client for interacting with SharePoint sites.
18
+ """High-level client for interacting with SharePoint sites.
18
19
 
19
20
  Provides methods for authentication, list management, file operations,
20
- and timeregistration handling. All public interactions should be performed
21
+ and time-registration handling. All public interactions should be performed
21
22
  through an instance created via :meth:`_compact_init`.
22
23
  """
23
24
 
@@ -35,7 +36,7 @@ class SharePointAPI:
35
36
  Returns
36
37
  -------
37
38
  SharePointAPI
38
- A fullyinitialised ``SharePointAPI`` object ready for use.
39
+ A fully-initialised ``SharePointAPI`` object ready for use.
39
40
 
40
41
  Notes
41
42
  -----
@@ -68,6 +69,43 @@ class SharePointAPI:
68
69
  self.password = password
69
70
  self.sharepoint_url = sharepoint_url
70
71
  self.proxies = proxies
72
+ self.timezone = timezone.utc
73
+
74
+ def _is_valid_guid(self, guid: str) -> bool:
75
+ """Validate that a string is a proper GUID."""
76
+ pattern = r'^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$'
77
+ return bool(re.fullmatch(pattern, guid))
78
+
79
+ def _resolve_sp_list(self, sharepoint_site: str, sp_list, SPListType: SharePointList = SharePointList) -> Tuple[str, Optional[SharePointList]]:
80
+ """
81
+ Resolve ``sp_list`` (GUID, title or ``SharePointList`` instance) to a GUID
82
+ and, when possible, a ``SharePointList`` object.
83
+
84
+ Returns
85
+ -------
86
+ tuple
87
+ (guid: str, list_obj: SharePointList | None)
88
+ """
89
+ # Case 1: already a SharePointList instance
90
+ if isinstance(sp_list, SharePointList):
91
+ return sp_list.guid, sp_list
92
+
93
+ # Case 2: string – could be GUID or title
94
+ if isinstance(sp_list, str):
95
+ if self._is_valid_guid(sp_list):
96
+ guid = sp_list
97
+ # Fetch minimal list metadata to build a SharePointList object
98
+ r = self._api_get_call(
99
+ f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')')
100
+ list_obj = SPListType(self, sharepoint_site, r.json()["d"])
101
+ return guid, list_obj
102
+ # Assume title – resolve via GetByTitle
103
+ list_obj = self.get_list_by_name(
104
+ sharepoint_site, sp_list, SPListType=SPListType)
105
+ return list_obj.guid, list_obj
106
+
107
+ raise TypeError(
108
+ 'Invalid sp_list argument; expected SharePointList or str')
71
109
 
72
110
  def _handle_response(self, response: requests.Response, success_codes: List[int]) -> requests.Response:
73
111
  """
@@ -140,7 +178,7 @@ class SharePointAPI:
140
178
  Perform a POST request against the SharePoint REST API.
141
179
 
142
180
  Enhanced error handling:
143
- * Networklevel errors (timeouts, DNS failures, etc.) are caught and reraised as
181
+ * Network-level errors (timeouts, DNS failures, etc.) are caught and re-raised as
144
182
  ``ConnectionError`` with a helpful message.
145
183
  * HTTP error codes are reported with status, URL and response body when available.
146
184
  * The method always returns a ``requests.Response`` on success (status 200, 201 or 204).
@@ -150,7 +188,7 @@ class SharePointAPI:
150
188
  url : str
151
189
  The full endpoint URL.
152
190
  post_data : dict
153
- JSONserialisable payload to send.
191
+ JSON-serialisable payload to send.
154
192
  form_digest_value : str | None, optional
155
193
  FormDigest required for POST/PUT/MERGE operations.
156
194
  merge : bool, optional
@@ -201,7 +239,7 @@ class SharePointAPI:
201
239
  Perform a PUT request against the SharePoint REST API.
202
240
 
203
241
  Enhanced error handling:
204
- * Networklevel errors (timeouts, DNS failures, etc.) are caught and reraised as
242
+ * Network-level errors (timeouts, DNS failures, etc.) are caught and re-raised as
205
243
  ``ConnectionError`` with a helpful message.
206
244
  * HTTP error codes are reported with status, URL and response body when available.
207
245
  * The method always returns a ``requests.Response`` on success (status 200, 201 or 204).
@@ -211,7 +249,7 @@ class SharePointAPI:
211
249
  url : str
212
250
  The full endpoint URL.
213
251
  put_data : dict
214
- JSONserialisable payload to send.
252
+ JSON-serialisable payload to send.
215
253
  form_digest_value : str | None, optional
216
254
  FormDigest required for POST/PUT/MERGE operations.
217
255
  merge : bool, optional
@@ -258,10 +296,10 @@ class SharePointAPI:
258
296
  def _api_attachment_call(self, url: str, post_data: bytes | None = None, form_digest_value: str | None = None,
259
297
  overwrite: bool = False, x_http_method: str | None = None) -> requests.Response:
260
298
  """
261
- Perform an attachmentrelated request (POST, PUT, DELETE) against the SharePoint REST API.
299
+ Perform an attachment-related request (POST, PUT, DELETE) against the SharePoint REST API.
262
300
 
263
301
  Enhanced error handling:
264
- * Networklevel errors (timeouts, DNS failures, etc.) are caught and reraised as
302
+ * Network-level errors (timeouts, DNS failures, etc.) are caught and re-raised as
265
303
  ``ConnectionError`` with a helpful message.
266
304
  * HTTP error codes are reported with status, URL and response body when available.
267
305
  * The method always returns a ``requests.Response`` on success (status 200 or 204).
@@ -286,7 +324,7 @@ class SharePointAPI:
286
324
  'X-RequestDigest': f'{form_digest_value}'
287
325
  }
288
326
 
289
- # Determine effective XHTTPMethod
327
+ # Determine effective X-HTTP-Method
290
328
  if overwrite:
291
329
  headers['X-HTTP-Method'] = "PUT"
292
330
  if x_http_method:
@@ -300,7 +338,7 @@ class SharePointAPI:
300
338
  raise ConnectionError(
301
339
  f'Unsupported X-HTTP-Method: {x_http_method}')
302
340
 
303
- # Add ContentLength when payload present
341
+ # Add Content-Length when payload present
304
342
  if post_data is not None:
305
343
  headers['Content-Length'] = str(len(post_data))
306
344
 
@@ -328,7 +366,7 @@ class SharePointAPI:
328
366
  Perform a GET request against the SharePoint REST API.
329
367
 
330
368
  Enhanced error handling:
331
- * Networklevel errors (timeouts, DNS failures, etc.) are caught and reraised as
369
+ * Network-level errors (timeouts, DNS failures, etc.) are caught and re-raised as
332
370
  ``ConnectionError`` with a helpful message.
333
371
  * HTTP error codes are reported with status, URL and response body when available.
334
372
  * The method always returns a ``requests.Response`` on success (status 200).
@@ -425,7 +463,7 @@ class SharePointAPI:
425
463
  Returns
426
464
  -------
427
465
  SharePointUserList
428
- A listlike container with :class:`SharePointUser` objects for each member.
466
+ A list-like container with :class:`SharePointUser` objects for each member.
429
467
  """
430
468
  # Build the request URL for the group's users.
431
469
  # SharePoint REST endpoint: /_api/web/sitegroups/GetByName('<group_name>')/users
@@ -466,7 +504,7 @@ class SharePointAPI:
466
504
  for user_settings in r.json()["d"]["results"]
467
505
  ]
468
506
 
469
- # Return a ``SharePointUserList`` (consistent with other userretrieval methods).
507
+ # Return a ``SharePointUserList`` (consistent with other user-retrieval methods).
470
508
  return SharePointUserList(sharepoint_site, users)
471
509
 
472
510
  def get_user(self, sharepoint_site, user_id, select_fields=None):
@@ -517,6 +555,40 @@ class SharePointAPI:
517
555
 
518
556
  return SharePointLists(_lists)
519
557
 
558
+ def get_site_metadata(self, sharepoint_site: str) -> dict:
559
+ """
560
+ Retrieve metadata of a SharePoint site.
561
+
562
+ Parameters
563
+ ----------
564
+ sharepoint_site : str
565
+ Identifier of the SharePoint site (e.g., 'mySite').
566
+
567
+ Returns
568
+ -------
569
+ dict
570
+ Dictionary containing the site's metadata as returned by the SharePoint REST API.
571
+ """
572
+ r = self._api_get_call(
573
+ f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web')
574
+ return r.json()["d"]
575
+
576
+ def get_site(self, sharepoint_site: str) -> SharePointSite:
577
+ """
578
+ Return a :class:`SharePointSite` object representing the given site.
579
+
580
+ Parameters
581
+ ----------
582
+ sharepoint_site : str
583
+ Identifier of the SharePoint site (e.g., ``'mySite'``).
584
+
585
+ Returns
586
+ -------
587
+ SharePointSite
588
+ A high-level wrapper around site-wide operations.
589
+ """
590
+ return SharePointSite(self, sharepoint_site)
591
+
520
592
  def get_list(self, sharepoint_site, sp_list, filters=None, top=1000, view_path=None, select_fields=None, SPListType: SharePointList = SharePointList) -> SharePointList:
521
593
  '''
522
594
  Returns a list from a given sharepoint_site using its guid
@@ -530,21 +602,9 @@ class SharePointAPI:
530
602
  returned for each list item via the ``$select`` OData query option.
531
603
  '''
532
604
 
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')
605
+ # Resolve sp_list to GUID and (optionally) a SharePointList object
606
+ guid, sp_list_obj = self._resolve_sp_list(
607
+ sharepoint_site, sp_list, SPListType=SPListType)
548
608
 
549
609
  arguments = []
550
610
 
@@ -578,8 +638,8 @@ class SharePointAPI:
578
638
 
579
639
  items = [SPListType.SPItem(self, sharepoint_site, guid, item_settings)
580
640
  for item_settings in r.json()["d"]["results"]]
581
- sp_list.append_items(items)
582
- return sp_list
641
+ sp_list_obj.append_items(items)
642
+ return sp_list_obj
583
643
 
584
644
  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
645
  '''
@@ -668,6 +728,21 @@ class SharePointAPI:
668
728
 
669
729
  # SP Item
670
730
 
731
+ def get_list_metadata(self, sharepoint_site, sp_list, SPListType: SharePointList = SharePointList) -> SharePointList:
732
+ """
733
+ Retrieve only the metadata of a SharePoint list without fetching its items.
734
+ Useful when only list properties (e.g., title, guid) are needed.
735
+ """
736
+ guid, list_obj = self._resolve_sp_list(
737
+ sharepoint_site, sp_list, SPListType=SPListType)
738
+ # If the list object was already resolved (e.g., via title lookup), reuse it.
739
+ if list_obj is not None:
740
+ return list_obj
741
+ # Otherwise, fetch minimal metadata using the GUID.
742
+ r = self._api_get_call(
743
+ f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')')
744
+ return SPListType(self, sharepoint_site, r.json()["d"])
745
+
671
746
  def get_item(self, sharepoint_site, sp_list, item_id, select_fields=None) -> SharePointListItem:
672
747
  '''
673
748
  Returns a single list item from a given sharepoint_site.
@@ -675,14 +750,8 @@ class SharePointAPI:
675
750
  returned via the ``$select`` OData query option.
676
751
  '''
677
752
 
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')
753
+ # Resolve sp_list to GUID (and list object if needed)
754
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
686
755
 
687
756
  # Build the request URL, adding $select if needed.
688
757
  if select_fields is not None:
@@ -701,14 +770,8 @@ class SharePointAPI:
701
770
 
702
771
  def create_item(self, sharepoint_site, sp_list, data) -> SharePointListItem:
703
772
  # 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])
773
+ # Resolve sp_list to GUID (and list object if needed)
774
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
712
775
 
713
776
  r = self._api_post_call(
714
777
  f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
@@ -731,13 +794,8 @@ class SharePointAPI:
731
794
  item_id: The id of the item
732
795
  data: Data to push to the item
733
796
  '''
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')
797
+ # Resolve sp_list to GUID (and list object if needed)
798
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
741
799
 
742
800
  r = self._api_post_call(
743
801
  f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
@@ -752,13 +810,8 @@ class SharePointAPI:
752
810
 
753
811
  def attach_file(self, sharepoint_site, sp_list, item, file_name, file_content) -> dict:
754
812
  # 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')
813
+ # Resolve sp_list to GUID (and list object if needed)
814
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
762
815
 
763
816
  r = self._api_post_call(
764
817
  f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/contextinfo', {})
@@ -766,23 +819,17 @@ class SharePointAPI:
766
819
  )["d"]["GetContextWebInformation"]["FormDigestValue"]
767
820
 
768
821
  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)
822
+ 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
823
 
771
824
  return r.json()
772
825
 
773
- def get_item_versions(self, sharepoint_site, sp_list, item_id, select_fields=[]) -> list:
826
+ def get_item_versions(self, sharepoint_site, sp_list, item_id, select_fields: Optional[List[str]] = None) -> list:
774
827
  '''
775
828
  Returns a list of users from a given sharepoint_site
776
829
  '''
777
830
 
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')
831
+ # Resolve sp_list to GUID (and list object if needed)
832
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
786
833
 
787
834
  if select_fields:
788
835
  r = self._api_get_call(
@@ -836,14 +883,8 @@ class SharePointAPI:
836
883
  Returns a list of users from a given sharepoint_site
837
884
  '''
838
885
 
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')
886
+ # Resolve sp_list to GUID (and list object if needed)
887
+ guid, _ = self._resolve_sp_list(sharepoint_site, sp_list)
847
888
 
848
889
  r = self._api_get_call(
849
890
  f'{self.sharepoint_url}/cases/{sharepoint_site}/_api/Web/Lists(guid\'{guid}\')/items({item_id})')
@@ -12,7 +12,7 @@ class SharePointList:
12
12
  Provides common functionality for handling list items, including
13
13
  retrieval, addition, and serialization to JSON. Subclasses such as
14
14
  :class:`CasesList` and :class:`TimeRegistrationList` extend this
15
- class with domainspecific behavior.
15
+ class with domain-specific behavior.
16
16
  """
17
17
 
18
18
  settings = None
@@ -194,7 +194,7 @@ class CasesList(SharePointList):
194
194
 
195
195
  class TimeRegistrationList(SharePointList):
196
196
  """
197
- Specialized list for timeregistration entries.
197
+ Specialized list for time-registration entries.
198
198
 
199
199
  Inherits from :class:`SharePointList` and sets ``SPItem`` to the
200
200
  :class:`TimeRegistration` class.
@@ -1,14 +1,15 @@
1
1
  from datetime import datetime
2
2
  # from .SharePointAPI import SharePointAPI as SP
3
3
 
4
+
4
5
  class SharePointListItem:
5
6
  """
6
7
  Represents a generic item in a SharePoint list.
7
8
 
8
9
  Provides access to common fields such as ``Id``, ``Title``, ``Created`` and
9
10
  ``Modified`` and handles lazy loading of related list data and version
10
- history. Subclasses (e.g. :class:`SharepointSiteCase` or
11
- :class:`TimeRegistration`) extend this base with domainspecific properties.
11
+ history. Sub-classes (e.g. :class:`SharepointSiteCase` or
12
+ :class:`TimeRegistration`) extend this base with domain-specific properties.
12
13
  """
13
14
  '''
14
15
  '''
@@ -34,12 +35,12 @@ class SharePointListItem:
34
35
  @property
35
36
  def versions(self) -> list:
36
37
  if not self._versions:
37
- self._versions = self.sp.get_item_versions(self.sharepoint_site, self.list_guid, self.Id)
38
+ self._versions = self.sp.get_item_versions(
39
+ self.sharepoint_site, self.list_guid, self.Id)
38
40
  if self._list is not None:
39
41
  self._list.CHANGE_DETECTED = True
40
- return self._versions
41
-
42
-
42
+ return self._versions
43
+
43
44
  @property
44
45
  def Id(self) -> str:
45
46
  """Unique identifier of the list item."""
@@ -62,7 +63,7 @@ class SharePointListItem:
62
63
  elif self.settings['Created'] is None:
63
64
  return None
64
65
  else:
65
- return datetime.strptime(self.settings['Created'], '%Y-%m-%dT%H:%M:%SZ')
66
+ return datetime.strptime(self.settings['Created'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
66
67
 
67
68
  @property
68
69
  def Modified(self) -> datetime:
@@ -76,39 +77,41 @@ class SharePointListItem:
76
77
  elif self.settings['Modified'] is None:
77
78
  return None
78
79
  else:
79
- return datetime.strptime(self.settings['Modified'], '%Y-%m-%dT%H:%M:%SZ')
80
+ return datetime.strptime(self.settings['Modified'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
80
81
 
81
82
  def attach_item(self, file_name, file_path):
82
83
  """
83
84
  Attach a file to this list item.
84
-
85
+
85
86
  Parameters
86
87
  ----------
87
88
  file_name : str
88
89
  The name of the file as it should appear in SharePoint.
89
90
  file_path : str
90
91
  Local path to the file to be uploaded.
91
-
92
+
92
93
  The method reads the file content and uses :meth:`SharePointAPI.attach_file`
93
94
  to upload it to the corresponding SharePoint list item.
94
95
  """
95
96
  with open(file_path, 'r') as f:
96
97
  file_content = f.read()
97
98
 
98
-
99
- self.sp.attach_file(self.sharepoint_site, self.list, self, file_name, file_content)
99
+ self.sp.attach_file(self.sharepoint_site, self.list,
100
+ self, file_name, file_content)
100
101
 
101
102
  def versions_select_fields(self, select_fields=[]) -> list:
102
103
  if not self._versions:
103
- self._versions = self.sp.get_item_versions(self.sharepoint_site, self.list_guid, self.Id, select_fields)
104
+ self._versions = self.sp.get_item_versions(
105
+ self.sharepoint_site, self.list_guid, self.Id, select_fields)
104
106
  return self._versions
105
107
 
106
108
  def update_fields(self, data):
107
109
  self.sp.update_item(self.sharepoint_site,
108
110
  self.list_guid, self.Id, data)
109
111
 
112
+
110
113
  class SharepointSiteCase(SharePointListItem):
111
-
114
+
112
115
  @property
113
116
  def AssignmentType(self) -> str:
114
117
  return self.settings['AssignmentType']
@@ -121,7 +124,7 @@ class SharepointSiteCase(SharePointListItem):
121
124
  elif self.settings['CaseClosed'] is None:
122
125
  return None
123
126
  else:
124
- return datetime.strptime(self.settings['CaseClosed'], '%Y-%m-%dT%H:%M:%SZ')
127
+ return datetime.strptime(self.settings['CaseClosed'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
125
128
 
126
129
  @property
127
130
  def Due(self) -> datetime:
@@ -131,7 +134,7 @@ class SharepointSiteCase(SharePointListItem):
131
134
  elif self.settings['DueDate'] is None:
132
135
  return None
133
136
  else:
134
- return datetime.strptime(self.settings['DueDate'], '%Y-%m-%dT%H:%M:%SZ')
137
+ return datetime.strptime(self.settings['DueDate'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
135
138
 
136
139
  @property
137
140
  def Priority(self) -> str:
@@ -156,7 +159,7 @@ class SharepointSiteCase(SharePointListItem):
156
159
  solved_in_time = True
157
160
  else:
158
161
  solved_in_time = True
159
-
162
+
160
163
  return solved_in_time
161
164
 
162
165
  @property
@@ -167,7 +170,7 @@ class SharepointSiteCase(SharePointListItem):
167
170
  elif self.settings['WorkBegun'] is None:
168
171
  return None
169
172
  else:
170
- return datetime.strptime(self.settings['WorkBegun'], '%Y-%m-%dT%H:%M:%SZ')
173
+ return datetime.strptime(self.settings['WorkBegun'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
171
174
 
172
175
  @property
173
176
  def DeadlineWorkTimestamp(self) -> datetime:
@@ -177,7 +180,7 @@ class SharepointSiteCase(SharePointListItem):
177
180
  elif self.settings['DeadlineWork'] is None:
178
181
  return None
179
182
  else:
180
- return datetime.strptime(self.settings['DeadlineWork'], '%Y-%m-%dT%H:%M:%SZ')
183
+ return datetime.strptime(self.settings['DeadlineWork'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
181
184
 
182
185
  @property
183
186
  def ReactedInTime(self) -> bool:
@@ -191,6 +194,3 @@ class SharepointSiteCase(SharePointListItem):
191
194
  else:
192
195
  reacted_in_time = True
193
196
  return reacted_in_time
194
-
195
-
196
-
@@ -0,0 +1,188 @@
1
+ """
2
+ Provides a high-level ``SharePointSite`` object that encapsulates site-wide
3
+ metadata and offers convenience wrappers around common ``SharePointAPI``
4
+ operations such as retrieving lists, users, and groups.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from datetime import datetime
9
+
10
+ from typing import List, Optional
11
+ import locale
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ # Imported only for type checking to avoid circular imports at runtime
17
+ from .SharePointAPI import SharePointAPI
18
+ from .SharePointList import SharePointList
19
+ from .SharePointLists import SharePointLists
20
+ from .SharePointUser import SharePointUser
21
+ from .SharePointUserList import SharePointUserList
22
+
23
+
24
+ class SharePointSite:
25
+ """
26
+ High-level representation of a SharePoint site.
27
+
28
+ Parameters
29
+ ----------
30
+ api : SharePointAPI
31
+ The low-level client used for HTTP calls.
32
+ site_id : str
33
+ Identifier of the SharePoint site (e.g. ``"mySite"``).
34
+ """
35
+
36
+ def __init__(self, api: "SharePointAPI", site_id: str):
37
+ self._api = api
38
+ self.site_id = site_id
39
+ self._metadata: Optional[dict] = None
40
+
41
+ # -----------------------------------------------------------------
42
+ # Internal helpers
43
+ # -----------------------------------------------------------------
44
+ def _ensure_metadata(self) -> dict:
45
+ """Fetch site metadata on first use and cache it."""
46
+ if self._metadata is None:
47
+ self._metadata = self._api.get_site_metadata(self.site_id)
48
+ return self._metadata
49
+
50
+ # -----------------------------------------------------------------
51
+ # Exposed properties (lazy-loaded)
52
+ # -----------------------------------------------------------------
53
+ @property
54
+ def title(self) -> str:
55
+ """Site title."""
56
+ return self._ensure_metadata().get("Title", "")
57
+
58
+ @property
59
+ def url(self) -> str:
60
+ """Absolute URL of the site."""
61
+ return self._ensure_metadata().get("Url", "")
62
+
63
+ @property
64
+ def description(self) -> str:
65
+ """Site description, if present."""
66
+ return self._ensure_metadata().get("Description", "")
67
+
68
+ @property
69
+ def created(self) -> datetime | None:
70
+ """
71
+ Creation date of the SharePoint site.
72
+
73
+ Returns a ``datetime`` object parsed from the ``Created`` metadata
74
+ field, or ``None`` if the field is missing or cannot be parsed.
75
+ """
76
+ created_str = self._ensure_metadata().get("Created")
77
+ if not created_str:
78
+ return None
79
+ try:
80
+ # ``fromisoformat`` handles the ISO‑8601 format returned by SharePoint.
81
+ return datetime.fromisoformat(created_str)
82
+ except ValueError:
83
+ # Fallback: return None if parsing fails.
84
+ return None
85
+
86
+ @property
87
+ def language_id(self) -> int:
88
+ """Raw LCID value returned by SharePoint."""
89
+ return self._ensure_metadata().get("Language", 0)
90
+
91
+ @property
92
+ def language_name(self) -> str:
93
+ """
94
+ Human-readable language name derived from the LCID using the
95
+ ``locale`` module. If the LCID is unknown or the locale cannot be set,
96
+ a fallback string is returned.
97
+ """
98
+ lcid = self.language_id
99
+ locale_code = locale.windows_locale.get(lcid)
100
+ if not locale_code:
101
+ return f"Unknown (LCID={lcid})"
102
+ try:
103
+ # Attempt to set the locale; may fail if not installed.
104
+ locale.setlocale(locale.LC_ALL, locale_code)
105
+ # locale.getlocale() returns a tuple like ('en_US', 'UTF-8')
106
+ loc = locale.getlocale()[0]
107
+ if loc:
108
+ # Return the locale identifier (e.g., 'en_US')
109
+ return loc
110
+ except locale.Error:
111
+ # Locale not available on the system; fall back to the code.
112
+ pass
113
+ # Fallback to the raw locale code string.
114
+ return locale_code
115
+
116
+ # -----------------------------------------------------------------
117
+ # Convenience wrappers around ``SharePointAPI`` methods
118
+ # -----------------------------------------------------------------
119
+ def get_lists(self) -> SharePointLists:
120
+ """Return a ``SharePointLists`` collection for this site."""
121
+ return self._api.get_lists(self.site_id)
122
+
123
+ def get_users(
124
+ self,
125
+ filters: Optional[str] = None,
126
+ select_fields: Optional[List[str]] = None,
127
+ ) -> SharePointUserList:
128
+ """Retrieve users from the site."""
129
+ return self._api.get_users(self.site_id, filters=filters, select_fields=select_fields)
130
+
131
+ def get_group_users(
132
+ self,
133
+ group_name: str,
134
+ filters: Optional[str] = None,
135
+ select_fields: Optional[List[str]] = None,
136
+ ) -> SharePointUserList:
137
+ """Retrieve members of a SharePoint group."""
138
+ return self._api.get_group_users(
139
+ self.site_id, group_name, filters=filters, select_fields=select_fields
140
+ )
141
+
142
+ def get_user(
143
+ self,
144
+ user_id: int,
145
+ select_fields: Optional[List[str]] = None,
146
+ ) -> SharePointUser:
147
+ """Retrieve a single user by ID."""
148
+ return self._api.get_user(self.site_id, user_id, select_fields=select_fields)
149
+
150
+ def get_list(
151
+ self,
152
+ sp_list,
153
+ filters=None,
154
+ top: int = 1000,
155
+ view_path=None,
156
+ select_fields=None,
157
+ SPListType: SharePointList = SharePointList,
158
+ ) -> SharePointList:
159
+ """Delegate to ``SharePointAPI.get_list`` for this site."""
160
+ return self._api.get_list(
161
+ self.site_id,
162
+ sp_list,
163
+ filters,
164
+ top,
165
+ view_path,
166
+ select_fields,
167
+ SPListType=SPListType,
168
+ )
169
+
170
+ def get_list_by_name(
171
+ self,
172
+ sp_list_name: str,
173
+ filters=None,
174
+ top: int = 1000,
175
+ view_path=None,
176
+ select_fields=None,
177
+ SPListType: SharePointList = SharePointList,
178
+ ) -> SharePointList:
179
+ """Delegate to ``SharePointAPI.get_list_by_name`` for this site."""
180
+ return self._api.get_list_by_name(
181
+ self.site_id,
182
+ sp_list_name,
183
+ filters,
184
+ top,
185
+ view_path,
186
+ select_fields,
187
+ SPListType=SPListType,
188
+ )
@@ -3,9 +3,10 @@ from datetime import datetime
3
3
  from sharepoint_api.SharePointListItem import SharePointListItem
4
4
  # from .SharePointAPI import SharePointAPI as SP
5
5
 
6
+
6
7
  class TimeRegistration(SharePointListItem):
7
8
  """
8
- Represents a timeregistration entry associated with a SharePoint list item.
9
+ Represents a time-registration entry associated with a SharePoint list item.
9
10
 
10
11
  Extends :class:`SharePointListItem` with fields specific to time tracking,
11
12
  such as ``Hours``, ``DoneBy`` and related timestamps. Provides lazy loading of the
@@ -22,8 +23,8 @@ class TimeRegistration(SharePointListItem):
22
23
  elif self.settings['DoneDate'] is None:
23
24
  return None
24
25
  else:
25
- return datetime.strptime(self.settings['DoneDate'], '%Y-%m-%dT%H:%M:%SZ')
26
-
26
+ return datetime.strptime(self.settings['DoneDate'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=self.sp.timezone)
27
+
27
28
  @property
28
29
  def Hours(self) -> str:
29
30
  return self.settings['Hours']
@@ -38,7 +39,7 @@ class TimeRegistration(SharePointListItem):
38
39
  user_list = self.sp.get_users(self.sharepoint_site)
39
40
  self._DoneBy = user_list.get_user_by_id(self.DoneById)
40
41
  return self._DoneBy
41
-
42
+
42
43
  @property
43
44
  def DoneUsername(self) -> str:
44
45
  return self.DoneBy.UserName
@@ -46,8 +47,7 @@ class TimeRegistration(SharePointListItem):
46
47
  @property
47
48
  def CaseId(self) -> str:
48
49
  return self.settings['CaseId']
49
-
50
+
50
51
  @property
51
52
  def WorkPackageId(self) -> str:
52
53
  return self.settings['WorkPackageId']
53
-
@@ -1,6 +1,6 @@
1
1
  """sharepoint_api package
2
2
 
3
- Provides a highlevel Python client for interacting with SharePoint sites via the
3
+ Provides a high-level Python client for interacting with SharePoint sites via the
4
4
  `SharePointAPI` class. The package includes helpers for working with lists,
5
5
  list items, users, and time registrations.
6
6
 
@@ -18,4 +18,5 @@ api = SharePointAPI._compact_init(creds)
18
18
  ```
19
19
  """
20
20
 
21
- from .SharePointAPI import SharePointAPI
21
+ from .SharePointAPI import SharePointAPI
22
+ from .SharePointSite import SharePointSite
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint_v1_api
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Author: Aske Bluhme Klok
5
5
  License: MIT
6
6
  Keywords: Sharepoint
@@ -156,13 +156,37 @@ for v in versions:
156
156
  ```
157
157
 
158
158
 
159
+ ## 0.2.4 – 2025-12-03
160
+
161
+ ### Added
162
+
163
+ - New high‑level `SharePointSite` object providing lazy‑loaded site metadata
164
+
165
+ ## 0.2.3 – 2025-12-02
166
+
167
+ ### Added
168
+
169
+ - GUID validation for list identifiers with fallback to list title lookup via `GetByTitle`.
170
+ - New helper method `_is_valid_guid` in `SharePointAPI`.
171
+ - Centralized list resolution helper `_resolve_sp_list` for GUID, title, or `SharePointList` instances.
172
+ - New method `get_list_metadata` to fetch only list metadata without items.
173
+ - Type hint enhancements: added `Optional`, `Tuple` imports; updated method signatures (e.g., `get_item_versions` now uses `Optional[List[str]]`).
174
+ - Updated `get_list` to return the resolved `SharePointList` object after appending items.
175
+ - Refactored multiple methods (`get_item`, `create_item`, `update_item`, `attach_file`, `get_item_versions`, `get_case`) to use `_resolve_sp_list`, removing redundant GUID validation logic.
176
+
177
+ ### Changed
178
+
179
+ - Cleaned up stray `else:` blocks and syntax errors.
180
+ - Improved consistency of return objects across list-related methods.
181
+ - Updated imports and docstrings accordingly.
182
+
159
183
  ## 0.2.2 – 2025-11-28
160
184
 
161
185
  ### Added
162
186
 
163
187
  - `SharePointList.fields` property to retrieve list field definitions
164
188
 
165
- ## 0.2.1 – 20251106
189
+ ## 0.2.1 – 2025-11-06
166
190
 
167
191
  ### Added
168
192
 
@@ -174,7 +198,7 @@ for v in versions:
174
198
 
175
199
  ### Changed
176
200
 
177
- - HTTP header handling unified; corrected `XHTTPMethod: PUT` for full updates.
201
+ - HTTP header handling unified; corrected `X-HTTP-Method: PUT` for full updates.
178
202
  - Error handling improved: generic `sys.exit(1)` replaced with explicit `TypeError`/`ConnectionError` exceptions.
179
203
 
180
204
  ### Fixed
@@ -9,6 +9,7 @@ sharepoint_api/SharePointAPI.py
9
9
  sharepoint_api/SharePointList.py
10
10
  sharepoint_api/SharePointListItem.py
11
11
  sharepoint_api/SharePointLists.py
12
+ sharepoint_api/SharePointSite.py
12
13
  sharepoint_api/SharePointTimeRegistration.py
13
14
  sharepoint_api/SharePointUser.py
14
15
  sharepoint_api/SharePointUserList.py
@@ -1,29 +0,0 @@
1
- ## 0.2.2 – 2025-11-28
2
-
3
- ### Added
4
-
5
- - `SharePointList.fields` property to retrieve list field definitions
6
-
7
- ## 0.2.1 – 2025‑11‑06
8
-
9
- ### Added
10
-
11
- - Optional `select_fields` parameters to list retrieval methods for more efficient queries.
12
- - New public API methods:
13
- - `SharePointAPI.get_group_users` – fetch users of a SharePoint group.
14
- - Improved error handling with explicit `TypeError` exceptions.
15
- - Detailed docstrings for core classes and methods (enhances IDE support).
16
-
17
- ### Changed
18
-
19
- - HTTP header handling unified; corrected `X‑HTTP‑Method: PUT` for full updates.
20
- - Error handling improved: generic `sys.exit(1)` replaced with explicit `TypeError`/`ConnectionError` exceptions.
21
-
22
- ### Fixed
23
-
24
- - Fixed incorrect PUT header that previously sent a MERGE header.
25
- - Minor docstring formatting issues.
26
-
27
- ### Security
28
-
29
- - Enforced NTLM authentication across all request helpers.