msgraphfs 0.4__tar.gz → 0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: msgraphfs
3
- Version: 0.4
3
+ Version: 0.5
4
4
  Dynamic: Summary
5
5
  Project-URL: Source, https://github.com/acsone/msgraphfs
6
6
  Author-email: Laurent Mignon <laurent.mignon@acsone.eu>
@@ -230,3 +230,7 @@ To use this filesystem, you need to register an Azure AD application:
230
230
  6. Note the Application (client) ID, Directory (tenant) ID, and client secret
231
231
 
232
232
  The filesystem uses the OAuth2 client credentials flow with the default scope (`https://graph.microsoft.com/.default`), which automatically includes all application permissions granted to your Azure AD application.
233
+
234
+ ### Running the tests
235
+
236
+ See [TESTING.md](TESTING.md) for how to run the test suite, including the live tests against a real SharePoint site.
@@ -184,3 +184,7 @@ To use this filesystem, you need to register an Azure AD application:
184
184
  6. Note the Application (client) ID, Directory (tenant) ID, and client secret
185
185
 
186
186
  The filesystem uses the OAuth2 client credentials flow with the default scope (`https://graph.microsoft.com/.default`), which automatically includes all application permissions granted to your Azure AD application.
187
+
188
+ ### Running the tests
189
+
190
+ See [TESTING.md](TESTING.md) for how to run the test suite, including the live tests against a real SharePoint site.
@@ -64,6 +64,15 @@ def parse_range_header(range_header):
64
64
  raise ValueError("Invalid Range header format")
65
65
 
66
66
 
67
+ def split_parent_child(path: str) -> tuple[str, str]:
68
+ """Split a path into its parent path and its final component.
69
+
70
+ If ``path`` has no ``/`` separator, the parent is the empty string
71
+ (i.e. the item is considered a direct child of the root).
72
+ """
73
+ return path.rsplit("/", 1) if "/" in path else ("", path)
74
+
75
+
67
76
  def parse_msgraph_url(url_path): # noqa: C901
68
77
  """Parse a msgraph URL to extract site_name, drive_name, and path.
69
78
 
@@ -655,7 +664,7 @@ class AbstractMSGraphFS(AsyncFileSystem):
655
664
  path1, item_id=source_item_id, action="copy"
656
665
  )
657
666
  path2 = self._strip_protocol(path2)
658
- parent_path, _file_name = path2.rsplit("/", 1)
667
+ parent_path, _file_name = split_parent_child(path2)
659
668
  item_reference = await self._get_item_reference(parent_path)
660
669
  json = {
661
670
  "parentReference": item_reference,
@@ -721,7 +730,6 @@ class AbstractMSGraphFS(AsyncFileSystem):
721
730
  For example, if you want to expand the properties to include the thumbnails,
722
731
  you can pass "thumbnails" as the value of the expand parameter.
723
732
  """
724
-
725
733
  url = await self._path_to_url_async(path, item_id=item_id)
726
734
  params = {}
727
735
  if expand:
@@ -729,7 +737,7 @@ class AbstractMSGraphFS(AsyncFileSystem):
729
737
  response = await self._msgraph_get(url, params=params)
730
738
  return self._drive_item_info_to_fsspec_info(response.json())
731
739
 
732
- async def _ls(
740
+ async def _ls( # noqa: C901
733
741
  self,
734
742
  path: str,
735
743
  detail: bool = True,
@@ -768,13 +776,21 @@ class AbstractMSGraphFS(AsyncFileSystem):
768
776
  params = {"select": "name,parentReference"}
769
777
  if expand:
770
778
  params = {"expand": expand}
771
- response = await self._msgraph_get(url, params=params)
772
- result = response.json()
773
- items = result.get("value", [])
774
- while "@odata.nextLink" in result:
775
- response = await self._msgraph_get(result["@odata.nextLink"])
779
+ items = []
780
+ try:
781
+ response = await self._msgraph_get(url, params=params)
776
782
  result = response.json()
777
- items.extend(result.get("value", []))
783
+ items = result.get("value", [])
784
+ while "@odata.nextLink" in result:
785
+ response = await self._msgraph_get(result["@odata.nextLink"])
786
+ result = response.json()
787
+ items.extend(result.get("value", []))
788
+ except HTTPStatusError as e:
789
+ if (
790
+ not e.response.status_code == 422
791
+ and "getChildrenOnNonFolder" not in e.response.content
792
+ ):
793
+ raise e
778
794
  if not items:
779
795
  # maybe the path is a file
780
796
  try:
@@ -891,7 +907,7 @@ class AbstractMSGraphFS(AsyncFileSystem):
891
907
 
892
908
  async def _mkdir(self, path, create_parents=True, exist_ok=False, **kwargs) -> str:
893
909
  path = self._strip_protocol(path).rstrip("/")
894
- parent, child = path.rsplit("/", 1)
910
+ parent, child = split_parent_child(path)
895
911
  parent_id = await self._get_item_id(parent)
896
912
  if not parent_id and not create_parents:
897
913
  raise FileNotFoundError(f"Parent directory does not exists: {parent}")
@@ -961,7 +977,7 @@ class AbstractMSGraphFS(AsyncFileSystem):
961
977
  if destination_item_id:
962
978
  item_reference = await self._get_item_reference(path2)
963
979
  else:
964
- parent_path, name = path2.rsplit("/", 1)
980
+ parent_path, name = split_parent_child(path2)
965
981
  item_reference = await self._get_item_reference(parent_path)
966
982
  json = {
967
983
  "parentReference": item_reference,
@@ -1071,7 +1087,7 @@ class AbstractMSGraphFS(AsyncFileSystem):
1071
1087
  url, json={"lastModifiedDateTime": datetime.now().isoformat()}
1072
1088
  )
1073
1089
  else:
1074
- parent_path, file_name = path.rsplit("/", 1)
1090
+ parent_path, file_name = split_parent_child(path)
1075
1091
  parent_id = await self._get_item_id(parent_path, throw_on_missing=True)
1076
1092
  item_id = f"{parent_id}:/{file_name}:"
1077
1093
  url = await self._path_to_url_async(path, item_id=item_id, action="content")
@@ -1095,7 +1111,6 @@ class AbstractMSGraphFS(AsyncFileSystem):
1095
1111
  refresh : bool (=False)
1096
1112
  if False, look in local cache for file details first
1097
1113
  """
1098
-
1099
1114
  info = await self._info(path, refresh=refresh)
1100
1115
 
1101
1116
  if info["type"] != "directory":
@@ -1766,12 +1781,13 @@ class MSGDriveFS(AbstractMSGraphFS):
1766
1781
  }
1767
1782
 
1768
1783
  async def _get_recycle_bin_items(self) -> list[dict]:
1769
- """Get the items in the recycle bin. (Beta!!)
1784
+ """Get the items in the recycle bin.
1770
1785
 
1771
- Returns:
1772
- list[dict]: A list of dictionaries with information about the items in the recycle bin.
1786
+ (Beta!!)
1787
+ Returns:
1788
+ list[dict]: A list of dictionaries with information about the items in the recycle bin.
1773
1789
 
1774
- see https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0
1790
+ see https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0
1775
1791
  """
1776
1792
  site_id = await self._get_site_id()
1777
1793
  url = f"https://graph.microsoft.com/beta/sites/{site_id}/recycleBin/items"
@@ -1904,7 +1920,7 @@ class AsyncStreamedFileMixin:
1904
1920
  """
1905
1921
  item_id = await self.item_id
1906
1922
  if not item_id:
1907
- parent_path, file_name = self.path.rsplit("/", 1)
1923
+ parent_path, file_name = split_parent_child(self.path)
1908
1924
  parent_id = await self.fs._get_item_id(parent_path)
1909
1925
  item_id = f"{parent_id}:/{file_name}:"
1910
1926
  url = self.fs._path_to_url(
@@ -1944,7 +1960,7 @@ class AsyncStreamedFileMixin:
1944
1960
  headers["content-type"] = self.fs._guess_type(self.path)
1945
1961
  item_id = await self.item_id
1946
1962
  if not item_id:
1947
- parent_path, file_name = self.path.rsplit("/", 1)
1963
+ parent_path, file_name = split_parent_child(self.path)
1948
1964
  parent_id = await self.fs._get_item_id(parent_path, throw_on_missing=True)
1949
1965
  item_id = f"{parent_id}:/{file_name}:"
1950
1966
  url = self.fs._path_to_url(self.path, item_id=item_id, action="content")
File without changes
File without changes
File without changes