Office365-REST-Python-Client 3.1.0__py3-none-any.whl → 3.2.0__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.
Files changed (245) hide show
  1. office365/communications/callrecords/call_record.py +18 -0
  2. office365/communications/calls/call.py +3 -0
  3. office365/communications/presences/presence.py +3 -0
  4. office365/communications/quotas.py +66 -0
  5. office365/communications/virtualevents/virtual_event.py +3 -0
  6. office365/directory/domains/domain.py +3 -0
  7. office365/directory/licenses/subscribed_sku.py +3 -0
  8. office365/directory/objects/object.py +3 -0
  9. office365/directory/quotas.py +34 -0
  10. office365/entity_collection.py +2 -2
  11. office365/graph_client.py +46 -2
  12. office365/limits.py +66 -0
  13. office365/migration/__init__.py +6 -1
  14. office365/migration/adapters/__init__.py +14 -4
  15. office365/migration/adapters/filesystem.py +4 -1
  16. office365/migration/assessment/export.py +5 -5
  17. office365/migration/assessment/issue.py +19 -0
  18. office365/migration/assessment/report.py +11 -0
  19. office365/migration/assessment/scanners/base.py +37 -49
  20. office365/migration/base.py +33 -8
  21. office365/migration/checkpoint.py +1 -1
  22. office365/migration/outlook/assessor.py +4 -4
  23. office365/migration/package/__init__.py +82 -0
  24. office365/migration/package/builder.py +603 -0
  25. office365/migration/package/crypto.py +54 -0
  26. office365/migration/package/manifest_xml.py +495 -0
  27. office365/migration/package/staging.py +180 -0
  28. office365/migration/report.py +7 -2
  29. office365/migration/report_io.py +79 -0
  30. office365/migration/runner.py +117 -10
  31. office365/migration/server_job.py +126 -14
  32. office365/migration/session.py +132 -49
  33. office365/migration/settings.py +67 -0
  34. office365/migration/sharepoint/__init__.py +2 -0
  35. office365/migration/sharepoint/adapters.py +207 -41
  36. office365/migration/sharepoint/assessor.py +45 -9
  37. office365/migration/sharepoint/options.py +57 -0
  38. office365/migration/sharepoint/package_target.py +209 -0
  39. office365/migration/sharepoint/registry.py +22 -0
  40. office365/migration/sharepoint/resolvers.py +96 -0
  41. office365/migration/sharepoint/scanners/__init__.py +29 -0
  42. office365/migration/sharepoint/scanners/browser_file_handling.py +55 -0
  43. office365/migration/sharepoint/scanners/checked_out_files.py +57 -0
  44. office365/migration/sharepoint/scanners/file_versions.py +56 -0
  45. office365/migration/sharepoint/scanners/files.py +5 -2
  46. office365/migration/sharepoint/scanners/large_excel_files.py +56 -0
  47. office365/migration/sharepoint/scanners/large_lists.py +58 -0
  48. office365/migration/sharepoint/scanners/large_sites.py +4 -3
  49. office365/migration/sharepoint/scanners/long_onedrive_urls.py +52 -0
  50. office365/migration/sharepoint/scanners/lookup.py +38 -0
  51. office365/migration/sharepoint/scanners/paths.py +9 -4
  52. office365/migration/sharepoint/scanners/permissions.py +20 -2
  53. office365/migration/sharepoint/scanners/smat.py +51 -0
  54. office365/migration/sharepoint/scanners/summary.py +1 -0
  55. office365/migration/sharepoint/scanners/thicket_folders.py +38 -0
  56. office365/migration/sharepoint/scanners/unsupported_site_templates.py +90 -0
  57. office365/migration/sharepoint/tenant_assessor.py +5 -3
  58. office365/migration/sharepoint/transfer.py +4 -2
  59. office365/migration/tasks.py +121 -0
  60. office365/migration/validators.py +2 -19
  61. office365/onedrive/driveitems/driveItem.py +116 -12
  62. office365/onedrive/termstore/groups/collection.py +2 -9
  63. office365/onedrive/termstore/sets/collection.py +2 -9
  64. office365/onedrive/termstore/terms/collection.py +3 -10
  65. office365/onedrive/workbooks/workbook.py +7 -0
  66. office365/outlook/contacts/folders/collection.py +4 -13
  67. office365/runtime/auth/authentication_context.py +5 -0
  68. office365/runtime/client_object.py +18 -0
  69. office365/runtime/client_object_collection.py +75 -245
  70. office365/runtime/client_request.py +77 -1
  71. office365/runtime/client_request_exception.py +127 -39
  72. office365/runtime/client_runtime_context.py +79 -8
  73. office365/runtime/converters/csv_writer.py +10 -4
  74. office365/runtime/converters/dataframe.py +84 -4
  75. office365/runtime/converters/json_file.py +12 -3
  76. office365/runtime/converters/parquet.py +67 -0
  77. office365/runtime/converters/records.py +31 -12
  78. office365/runtime/converters/registry.py +185 -0
  79. office365/runtime/converters/scalars.py +31 -0
  80. office365/runtime/converters/sql.py +69 -0
  81. office365/runtime/converters/streamers.py +105 -0
  82. office365/runtime/converters/streams.py +42 -0
  83. office365/runtime/converters/upsert.py +101 -0
  84. office365/runtime/exceptions.py +105 -0
  85. office365/runtime/http/prefer.py +26 -0
  86. office365/runtime/http/throttling.py +258 -56
  87. office365/runtime/imports.py +571 -0
  88. office365/runtime/limits.py +455 -0
  89. office365/runtime/odata/request.py +11 -2
  90. office365/runtime/odata/type.py +1 -0
  91. office365/runtime/odata/v3/batch_request.py +4 -2
  92. office365/runtime/odata/v4/batch_request.py +22 -7
  93. office365/runtime/operations.py +29 -0
  94. office365/runtime/parallel.py +12 -14
  95. office365/runtime/paths/v4/entity.py +2 -2
  96. office365/runtime/queries/batch.py +18 -1
  97. office365/runtime/queries/get_or_create.py +109 -0
  98. office365/runtime/record_collection.py +443 -0
  99. office365/runtime/retry.py +92 -26
  100. office365/runtime/transport/base.py +6 -1
  101. office365/runtime/transport/requests_transport.py +4 -0
  102. office365/runtime/transport/throttled_transport.py +67 -0
  103. office365/runtime/verification.py +40 -0
  104. office365/sharepoint/activities/logger.py +118 -8
  105. office365/sharepoint/administration/analytics/usage_service.py +15 -7
  106. office365/sharepoint/administration/sitemove/service.py +791 -8
  107. office365/sharepoint/alerts/alert.py +79 -0
  108. office365/sharepoint/apps/app_collection.py +25 -0
  109. office365/sharepoint/apps/launcher.py +16 -0
  110. office365/sharepoint/authpolicy/tenant/compliancereport.py +5 -0
  111. office365/sharepoint/businessdata/app_bdc_catalog.py +135 -0
  112. office365/sharepoint/businessdata/entityfieldvaluedictionary.py +65 -0
  113. office365/sharepoint/businessdata/entityidentifier.py +23 -0
  114. office365/sharepoint/businessdata/entityinstance.py +52 -0
  115. office365/sharepoint/businessdata/entityview.py +31 -0
  116. office365/sharepoint/businessdata/externalsubscriptionstore.py +9 -0
  117. office365/sharepoint/businessdata/typedescriptor.py +37 -0
  118. office365/sharepoint/client_context.py +40 -23
  119. office365/sharepoint/clientsidecomponent/hostedapps/app.py +21 -0
  120. office365/sharepoint/clientsidecomponent/hostedapps/manager.py +16 -0
  121. office365/sharepoint/comments/comment.py +84 -2
  122. office365/sharepoint/contentcenter/machinelearning/models/model.py +0 -23
  123. office365/sharepoint/contentcenter/machinelearning/publications/publication.py +0 -9
  124. office365/sharepoint/contentcenter/machinelearning/samples/sample.py +0 -14
  125. office365/sharepoint/contentcenter/machinelearning/workitems/item.py +0 -80
  126. office365/sharepoint/contentsolution/template_api.py +148 -0
  127. office365/sharepoint/contenttypes/collection.py +32 -10
  128. office365/sharepoint/convergence/addnoliveblobsinodcservicestateapi.py +9 -0
  129. office365/sharepoint/convergence/consumer_permissions.py +15 -0
  130. office365/sharepoint/convergence/migration_complete_state_api.py +25 -0
  131. office365/sharepoint/convergence/odcmetadatacleanedupapi.py +9 -0
  132. office365/sharepoint/documentmanagement/document_id.py +12 -0
  133. office365/sharepoint/entity.py +11 -2
  134. office365/sharepoint/entity_collection.py +2 -2
  135. office365/sharepoint/exceptions.py +194 -0
  136. office365/sharepoint/fields/builtin_field_id.py +45 -34
  137. office365/sharepoint/fields/builtin_field_name.py +123 -0
  138. office365/sharepoint/fields/coercion.py +131 -0
  139. office365/sharepoint/fields/collection.py +54 -37
  140. office365/sharepoint/fields/field.py +17 -0
  141. office365/sharepoint/fields/name.py +46 -2
  142. office365/sharepoint/files/collection.py +7 -1
  143. office365/sharepoint/files/file.py +54 -8
  144. office365/sharepoint/files/versions/version.py +38 -13
  145. office365/sharepoint/folders/collection.py +4 -0
  146. office365/sharepoint/folders/folder.py +91 -21
  147. office365/sharepoint/htmlpages/html_page.py +16 -0
  148. office365/sharepoint/htmlpages/html_page_service.py +19 -0
  149. office365/sharepoint/ir/migration.py +9 -0
  150. office365/sharepoint/listitems/caml/__init__.py +31 -0
  151. office365/sharepoint/listitems/caml/builder.py +129 -0
  152. office365/sharepoint/listitems/caml/expressions.py +424 -0
  153. office365/sharepoint/listitems/caml/fields.py +68 -0
  154. office365/sharepoint/listitems/caml/guard.py +63 -0
  155. office365/sharepoint/listitems/caml/query.py +71 -2
  156. office365/sharepoint/listitems/caml/values.py +113 -0
  157. office365/sharepoint/listitems/collection.py +21 -20
  158. office365/sharepoint/listitems/upsert.py +75 -0
  159. office365/sharepoint/lists/collection.py +29 -22
  160. office365/sharepoint/lists/exporter.py +4 -3
  161. office365/sharepoint/lists/list.py +397 -39
  162. office365/sharepoint/lists/smart_wiki_library_utility.py +58 -0
  163. office365/sharepoint/logger/export.py +12 -0
  164. office365/sharepoint/marketplace/app_metadata.py +97 -1
  165. office365/sharepoint/marketplace/appstatequeryjobdefinition.py +9 -0
  166. office365/sharepoint/marketplace/corporatecuratedgallery/carddesigns/card_designs.py +35 -0
  167. office365/sharepoint/marketplace/sitecollection/appcatalog/allowed_item.py +5 -0
  168. office365/sharepoint/marketplace/tenant/appcatalog/accessor.py +107 -30
  169. office365/sharepoint/microfeed/data.py +0 -6
  170. office365/sharepoint/microservice/manager.py +61 -0
  171. office365/sharepoint/migrationcenter/common/task.py +193 -4
  172. office365/sharepoint/migrationcenter/common/task_entity_data.py +1 -1
  173. office365/sharepoint/migrationcenter/deploy_status.py +13 -0
  174. office365/sharepoint/migrationcenter/service/properties.py +20 -0
  175. office365/sharepoint/migrationcenter/service/storage.py +34 -0
  176. office365/sharepoint/migrationcenter/spflight.py +13 -0
  177. office365/sharepoint/migrationcenter/urlparseresult.py +77 -0
  178. office365/sharepoint/multigeo/service/spmultigeoproxy.py +13 -0
  179. office365/sharepoint/navigation/servicerest.py +127 -0
  180. office365/sharepoint/oauth/authentication.py +13 -0
  181. office365/sharepoint/portal/theme_manager.py +120 -8
  182. office365/sharepoint/principal/users/user.py +10 -0
  183. office365/sharepoint/publishing/campaign/campaign.py +36 -1
  184. office365/sharepoint/publishing/feedvideopage.py +0 -7
  185. office365/sharepoint/publishing/pages/3d/page.py +0 -6
  186. office365/sharepoint/publishing/pages/page.py +0 -117
  187. office365/sharepoint/publishing/pages/reposts/repost.py +0 -7
  188. office365/sharepoint/publishing/pages/topics/topic.py +0 -7
  189. office365/sharepoint/publishing/subtitlefile.py +0 -40
  190. office365/sharepoint/quotamanagement/consumer/consumer_quota_info_api.py +14 -0
  191. office365/sharepoint/quotamanagement/consumer/non_quota_backfill_api.py +23 -0
  192. office365/sharepoint/quotamanagement/consumer/nonquotamigrationapi.py +9 -0
  193. office365/sharepoint/quotamanagement/consumer/quotamigrationapi.py +13 -0
  194. office365/sharepoint/request.py +57 -16
  195. office365/sharepoint/search/administration/document_crawl_log.py +24 -7
  196. office365/sharepoint/search/administration/providers/crawl_versions_info.py +29 -0
  197. office365/sharepoint/search/administration/providers/recycle_bin_info.py +23 -0
  198. office365/sharepoint/search/administration/providers/site_content_processing_info.py +67 -0
  199. office365/sharepoint/sites/site.py +976 -65
  200. office365/sharepoint/sites/siteprovisioninginfoprovider.py +13 -0
  201. office365/sharepoint/tenant/administration/datagovernance/insight_client.py +292 -23
  202. office365/sharepoint/tenant/administration/ddiadapter.py +97 -0
  203. office365/sharepoint/tenant/administration/internal/aad/collection.py +20 -0
  204. office365/sharepoint/tenant/administration/internal/aad/permission_grant.py +0 -19
  205. office365/sharepoint/tenant/administration/internal/appservice/permission_request.py +72 -1
  206. office365/sharepoint/tenant/administration/internal/appservice/principal.py +1 -3
  207. office365/sharepoint/tenant/administration/odb_license_enforcement_service.py +26 -0
  208. office365/sharepoint/tenant/administration/sites/properties.py +564 -6
  209. office365/sharepoint/tenant/administration/spdatagovernancesarrestapiclient.py +60 -1
  210. office365/sharepoint/tenant/insights/reports/manager.py +24 -12
  211. office365/sharepoint/tenant/management/office365_tenant.py +451 -2
  212. office365/sharepoint/tenantadministration/catalog_management_collection.py +69 -0
  213. office365/sharepoint/tenantadministration/catalog_management_custom_properties_collection.py +237 -0
  214. office365/sharepoint/tenantadministration/catalog_management_restricted_content_discovery_collection.py +56 -0
  215. office365/sharepoint/tenantadministration/sp_data_governance_client_exception.py +7 -0
  216. office365/sharepoint/thresholds.py +529 -0
  217. office365/sharepoint/translation/jobs/job.py +72 -2
  218. office365/sharepoint/translation/jobs/status.py +47 -9
  219. office365/sharepoint/translation/sync_translator.py +15 -2
  220. office365/sharepoint/utilities/move_copy_util.py +2 -1
  221. office365/sharepoint/viva/connections/page.py +27 -0
  222. office365/sharepoint/viva/home.py +38 -0
  223. office365/sharepoint/viva/learningmanager.py +9 -0
  224. office365/sharepoint/viva/vivaresources.py +60 -0
  225. office365/sharepoint/webhooks/subscription.py +0 -11
  226. office365/sharepoint/webparts/definitions/definition.py +13 -0
  227. office365/sharepoint/webparts/limited_manager.py +29 -7
  228. office365/sharepoint/webs/context_web_information.py +14 -9
  229. office365/sharepoint/webs/templates/__init__.py +11 -0
  230. office365/sharepoint/webs/templates/type.py +74 -15
  231. office365/sharepoint/workflow/definition.py +6 -0
  232. office365/sharepoint/workflow/deployment_service.py +155 -0
  233. office365/sharepoint/workflow/instances/service.py +33 -0
  234. office365/sharepoint/workflow/interopservice.py +68 -0
  235. office365/sharepoint/workflow/messagingservice.py +24 -0
  236. office365/sharepoint/workflow/servicesmanager.py +41 -2
  237. office365/sharepoint/workflow/subscriptions/service.py +67 -0
  238. office365/sharepoint/workflow/subscriptions/subscription.py +31 -0
  239. office365/sharepoint/workflow/task.py +425 -0
  240. office365_rest_python_client-3.2.0.dist-info/METADATA +605 -0
  241. {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/RECORD +242 -185
  242. office365/migration/_util.py +0 -73
  243. office365/sharepoint/listitems/caml/types.py +0 -70
  244. office365_rest_python_client-3.1.0.dist-info/METADATA +0 -475
  245. {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/WHEEL +0 -0
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  from datetime import datetime
5
- from typing import IO, TYPE_CHECKING, AnyStr, Callable, Dict, Optional, Union
5
+ from typing import IO, TYPE_CHECKING, Any, AnyStr, Callable, Dict, Optional, Union, cast
6
6
 
7
7
  from typing_extensions import Self
8
8
 
@@ -37,8 +37,10 @@ from office365.sharepoint.flows.connector_result import ConnectorResult
37
37
  from office365.sharepoint.flows.synchronization_result import FlowSynchronizationResult
38
38
  from office365.sharepoint.folders.folder import Folder
39
39
  from office365.sharepoint.forms.collection import FormCollection
40
+ from office365.sharepoint.listitems.caml.guard import warn_if_unpaged
40
41
  from office365.sharepoint.listitems.caml.query import CamlQuery
41
42
  from office365.sharepoint.listitems.collection import ListItemCollection
43
+ from office365.sharepoint.listitems.collection_position import ListItemCollectionPosition
42
44
  from office365.sharepoint.listitems.creation_information import (
43
45
  ListItemCreationInformation,
44
46
  )
@@ -65,6 +67,7 @@ from office365.sharepoint.permissions.securable_object import SecurableObject
65
67
  from office365.sharepoint.principal.users.user import User
66
68
  from office365.sharepoint.sharing.object_sharing_settings import ObjectSharingSettings
67
69
  from office365.sharepoint.sitescripts.utility import SiteScriptUtility
70
+ from office365.sharepoint.thresholds import LIST_VIEW_THRESHOLD, SAFE_PAGE_SIZE, Limits, limit
68
71
  from office365.sharepoint.translation.user_resource import UserResource
69
72
  from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
70
73
  from office365.sharepoint.usercustomactions.collection import UserCustomActionCollection
@@ -74,7 +77,10 @@ from office365.sharepoint.views.view import View
74
77
  from office365.sharepoint.webhooks.subscription_collection import SubscriptionCollection
75
78
 
76
79
  if TYPE_CHECKING:
80
+ from office365.runtime.converters.dataframe import DataFrameResult
81
+ from office365.runtime.imports import CheckpointStore, ImportCheckpoint, ImportResult
77
82
  from office365.runtime.operations import ProgressCallback
83
+ from office365.runtime.record_collection import VerificationResult
78
84
  from office365.sharepoint.client_context import ClientContext
79
85
  from office365.sharepoint.documentmanagement.document_set import DocumentSet
80
86
  from office365.sharepoint.webs.web import Web
@@ -117,10 +123,18 @@ class List(SecurableObject):
117
123
  include_content: bool = False,
118
124
  item_exported: Optional[Callable[[ExportListProgress], None]] = None,
119
125
  ) -> Self:
120
- """Exports SharePoint List"""
126
+ """Export the list as a **package** (``.zip``): per-item JSON + optional content.
127
+
128
+ This is the package exporter (``ListExporter``), distinct from the record
129
+ export on :meth:`export_to`/``list.items.export_to``. Run with
130
+ ``execute_query()``:
131
+
132
+ >>> with open("Orders.zip", "wb") as f:
133
+ ... lst.export(f, include_content=True).execute_query()
134
+ """
121
135
  from office365.sharepoint.lists.exporter import ListExporter
122
136
 
123
- return ListExporter.export(self, local_file, include_content, item_exported) # type: ignore[override]
137
+ return cast(Self, ListExporter.export(self, local_file, include_content, item_exported))
124
138
 
125
139
  def can_customize_forms(self) -> ConnectorResult:
126
140
  """"""
@@ -656,82 +670,426 @@ class List(SecurableObject):
656
670
  # """Clears the broken taxonomy values"""
657
671
  # raise NotImplementedError("validate_broken_taxonomy_values")
658
672
 
659
- def get_items(self, caml_query: Optional[CamlQuery] = None) -> ListItemCollection:
660
- """Returns a collection of items from the list based on the specified query."""
673
+ def index_candidates(self, caml_query: CamlQuery) -> list[str]:
674
+ """The columns a CAML query filters/sorts on that are worth indexing (``ID`` excluded).
675
+
676
+ A starting point for :meth:`ensure_indexed` — indexing is explicit and the
677
+ build runs in the background. Shorthand for ``caml_query.index_candidates``.
678
+ """
679
+ return caml_query.index_candidates
680
+
681
+ def check_query(self, caml_query: CamlQuery, *, item_count: Optional[int] = None) -> None:
682
+ """Pre-flight a CAML query against the SharePoint list view threshold.
683
+
684
+ Loads the list's item count (unless ``item_count`` is given) and the
685
+ ``Indexed`` status of the columns the query filters/sorts on, and raises
686
+ :class:`~office365.sharepoint.exceptions.SPQueryThrottledException` — with
687
+ the exact columns to index — when the query would be throttled, instead of
688
+ letting the server return an opaque 500. Performs 1-2 requests.
689
+
690
+ Args:
691
+ caml_query: The query to check.
692
+ item_count: The list size, when already known (avoids a request).
693
+ """
694
+ from office365.sharepoint.exceptions import SPQueryThrottledException
695
+
696
+ if item_count is None:
697
+ self.ensure_property("ItemCount").execute_query()
698
+ item_count = self.item_count or 0
699
+ if item_count <= LIST_VIEW_THRESHOLD:
700
+ return
701
+ candidates = set(caml_query.index_candidates)
702
+ if not candidates:
703
+ return
704
+
705
+ fields = self.fields
706
+ fields.select(["InternalName", "Title", "Indexed"]).get().execute_query()
707
+ not_indexed = sorted(
708
+ str(f.internal_name or f.title or "")
709
+ for f in fields
710
+ if (f.internal_name in candidates or f.title in candidates) and f.indexed is not True
711
+ )
712
+ if not_indexed:
713
+ names = ", ".join(repr(name) for name in not_indexed)
714
+ raise SPQueryThrottledException(
715
+ f"This CAML query filters/sorts on non-indexed column(s) {names} and the list has "
716
+ f"{item_count:,} items (over the {LIST_VIEW_THRESHOLD:,}-item list view threshold), so it "
717
+ f"would be throttled. Index them first, e.g. "
718
+ f"lst.ensure_indexed({not_indexed[0]!r}).execute_query(), or filter on ID (always indexed)."
719
+ )
720
+
721
+ @limit(Limits.LIST_VIEW, arg="page_size")
722
+ def get_items(
723
+ self,
724
+ caml_query: Optional[CamlQuery] = None,
725
+ page_size: Optional[int] = None,
726
+ check: bool = False,
727
+ ) -> ListItemCollection:
728
+ """Returns a collection of items from the list based on the specified query.
729
+
730
+ Pass ``page_size`` to page the results (continues from the last item via
731
+ ``ListItemCollectionPosition``), so a query over more than the 5,000-item
732
+ list view threshold can be read in full:
733
+
734
+ >>> for item in list.get_items(query, page_size=2000).execute_query():
735
+ ... ...
736
+
737
+ Pass ``check=True`` to pre-flight the query with :meth:`check_query` first
738
+ (raises actionable guidance instead of an opaque server 500). It performs
739
+ 1-2 requests immediately, so it is off by default.
740
+ """
661
741
  if not caml_query:
662
742
  caml_query = CamlQuery.create_all_items_query()
743
+ if check:
744
+ self.check_query(caml_query)
745
+ if page_size is None:
746
+ warn_if_unpaged(caml_query, item_count=self.item_count)
663
747
  return_type = ListItemCollection(self.context, self.items.resource_path)
664
748
  payload = {"query": caml_query}
665
749
  qry = ServiceOperationQuery(self, "GetItems", None, payload, None, return_type)
666
750
  self.context.add_query(qry)
751
+ if page_size:
752
+ return_type.paged(page_size)
753
+
754
+ def _next_page() -> None:
755
+ # CAML paging: continue from the last item via its collection position,
756
+ # carrying the sort columns (then ID) so the next page starts where
757
+ # the previous one stopped.
758
+ last = return_type[-1]
759
+ parts = ["Paged=TRUE"]
760
+ parts.extend(
761
+ f"p_{name}={last.properties.get(name)}"
762
+ for name in caml_query.order_by_fields
763
+ if name.upper() != "ID"
764
+ )
765
+ parts.append(f"p_ID={last.id}")
766
+ caml_query.ListItemCollectionPosition = ListItemCollectionPosition(PagingInfo="&".join(parts))
767
+ next_qry = ServiceOperationQuery(self, "GetItems", None, {"query": caml_query}, None, return_type)
768
+ self.context.add_query(next_qry)
769
+
770
+ return_type._next_page = _next_page
667
771
  return return_type
668
772
 
669
- def ensure_field(self, name: str, field_type: FieldType = FieldType.Text, description: str | None = None) -> Self:
773
+ def ensure_field(
774
+ self,
775
+ name: str,
776
+ field_type: FieldType = FieldType.Text,
777
+ description: str | None = None,
778
+ *,
779
+ on_conflict: str = "skip",
780
+ ) -> Field:
670
781
  """Ensure a single column exists on the list, creating it if missing.
671
782
 
672
783
  The check is deferred — the column is looked up and created when the
673
784
  caller executes the query (e.g. ``list.ensure_field("Status").execute_query()``).
785
+ With ``on_conflict="update"`` an existing column's type/description is
786
+ reconciled.
674
787
 
675
788
  Args:
676
789
  name: The column title
677
790
  field_type: The field type to create it with if missing (Text by default)
678
791
  description: The description of the column
792
+ on_conflict: ``"skip"`` (default) or ``"update"``.
793
+
794
+ Returns:
795
+ Field: The existing or newly created field.
679
796
  """
680
- self.fields.ensure(FieldCreationInformation(Title=name, FieldTypeKind=field_type, Description=description))
681
- return self
797
+ return self.fields.ensure(
798
+ FieldCreationInformation(Title=name, FieldTypeKind=field_type, Description=description),
799
+ on_conflict=on_conflict,
800
+ )
682
801
 
683
- def ensure_fields(self, columns: "Dict[str, FieldType] | list[str]") -> Self:
802
+ def ensure_fields(self, columns: "Dict[str, FieldType] | list[str]", *, on_conflict: str = "skip") -> list[Field]:
684
803
  """Ensure the specified columns exist on the list, creating missing ones.
685
804
 
686
- Reconciles the source schema with the target list before data import,
687
- like migration tools do: existing fields are kept, missing ones are
688
- created with the given type (Text by default).
689
-
690
- The check is deferred — the fields are read and missing ones created
691
- when the caller executes the query (e.g.
692
- ``list.ensure_fields(...).execute_query()``).
805
+ Reconciles the source schema with the target list before data import:
806
+ existing fields are kept (or reconciled with ``on_conflict="update"``),
807
+ missing ones are created with the given type (Text by default). Deferred —
808
+ execute the query after.
693
809
 
694
810
  Args:
695
811
  columns: Either a list of field names (created as Text) or a mapping
696
812
  of field name -> FieldType
813
+ on_conflict: ``"skip"`` (default) or ``"update"``.
814
+
815
+ Returns:
816
+ list[Field]: The existing or newly created fields.
697
817
  """
698
818
  spec = columns.items() if isinstance(columns, dict) else ((c, FieldType.Text) for c in columns)
699
- for name, field_type in spec:
700
- self.ensure_field(name, field_type)
701
- return self
819
+ return [self.ensure_field(name, field_type, on_conflict=on_conflict) for name, field_type in spec]
702
820
 
703
- def from_dataframe(self, df, progress: "ProgressCallback | None" = None) -> Self:
704
- """Import a pandas DataFrame into this list.
821
+ def ensure_indexed(self, name: str) -> Field:
822
+ """Enable the index on an existing column (idempotent, deferred).
705
823
 
706
- Defines a column per DataFrame column via ``fields.from_dataframe``
707
- (type inferred from the dtype, created idempotently), then queues an
708
- item create per row — fully deferred, run the whole import with
709
- ``execute_query()``:
824
+ Indexing the columns used in a CAML/``$filter``/``$orderby`` lets queries
825
+ filter and sort past the 5,000-item list view threshold. The column must
826
+ already exist. Run with ``execute_query()``:
710
827
 
711
- >>> lst = ctx.web.lists.ensure_list("My List").execute_query()
712
- >>> lst.from_dataframe(df).execute_query()
713
- >>> lst.from_dataframe(df, progress=my_callback).execute_query()
828
+ >>> lst.ensure_indexed("Status").execute_query()
829
+ """
830
+ return self.fields.get_by_internal_name_or_title(name).ensure_indexed()
714
831
 
715
- Column names are sanitized into SharePoint field internal names
716
- (spaces/punctuation -> ``_``); NaN cells are skipped.
832
+ def from_records(
833
+ self,
834
+ source,
835
+ *,
836
+ format: str = "records", # noqa: A002
837
+ schema: "Dict[str, FieldType] | None" = None,
838
+ chunksize: int = SAFE_PAGE_SIZE,
839
+ progress: "ProgressCallback | None" = None,
840
+ total: "int | None" = None,
841
+ checkpoint: "ImportCheckpoint | CheckpointStore | str | None" = None,
842
+ on_error: str = "raise",
843
+ key: "str | list[str] | None" = None,
844
+ key_field: str = "MigrationKey",
845
+ on_conflict: str = "skip",
846
+ enforce_unique: bool = False,
847
+ dry_run: bool = False,
848
+ on_schema_change: str = "evolve",
849
+ dead_letter: "str | None" = None,
850
+ mapping: "Dict[str, str] | None" = None,
851
+ ) -> "ImportResult":
852
+ """Stream a source into this list's items, memory-bounded (streaming entry).
853
+
854
+ Returns an :class:`~office365.runtime.imports.ImportResult`; run it with
855
+ ``execute_query()`` (sequential) or ``execute_batch(...)`` (batched):
856
+
857
+ >>> lst.from_dataframe(pd.read_csv(url, chunksize=2000)).execute_batch(concurrency=5)
858
+
859
+ Fields are provisioned from the first chunk (or ``schema``) for the
860
+ ``dataframe``/``csv`` formats; columns that first appear in a later chunk
861
+ are added (``on_schema_change="evolve"``, the default) or rejected
862
+ (``"fail"``). Column names are sanitized into field internal names; NaN
863
+ cells are skipped. ``checkpoint`` resumes; ``key`` makes it idempotent.
717
864
 
718
865
  Args:
719
- df: A pandas DataFrame (requires ``pip install
720
- office365-rest-python-client[pandas]``).
721
- progress: Optional hook invoked per row as its item create completes
722
- during ``execute_query()``.
866
+ source: A DataFrame / chunk iterable / CSV path (``dataframe``/``csv``),
867
+ record batches (``records``), or a reader source for other formats.
868
+ format: Source format (default ``"records"``).
869
+ schema: Optional explicit ``{column: FieldType}``; inferred from dtypes.
870
+ A ``FieldType`` also drives typed value coercion (multi-choice,
871
+ lookup, user, URL, geolocation, ...).
872
+ chunksize: Rows per chunk for a DataFrame/CSV source.
873
+ progress: Optional hook fired live (initial, per chunk, per batch).
874
+ total: Total rows when known upfront (drives the progress percentage).
875
+ checkpoint: Optional resumable-run checkpoint (object or path).
876
+ on_error: ``"raise"`` (default) or ``"collect"``.
877
+ key: Natural-key source column(s) for idempotency; ``None`` disables it.
878
+ key_field: Target column storing the derived key hash.
879
+ on_conflict: ``"skip"`` (default) or ``"upsert"``.
880
+ enforce_unique: Mark the key column unique (guards a create race).
881
+ dry_run: Plan the create/update/skip counts without writing.
882
+ on_schema_change: ``"evolve"`` (default) or ``"fail"``.
883
+ mapping: Optional ``{source_column: target_name}`` rename map.
884
+ dead_letter: Optional JSONL path for collected chunk failures.
723
885
 
724
886
  Returns:
725
- Self: The list, for method chaining.
887
+ ImportResult: The deferred streaming import driver.
726
888
  """
889
+ from functools import partial
890
+
727
891
  from office365.runtime.converters.dataframe import records_from_dataframe
892
+ from office365.sharepoint.fields.coercion import coerce_field_value
728
893
  from office365.sharepoint.fields.name import internal_field_name
729
894
 
730
- records = records_from_dataframe(df, key_fn=internal_field_name)
731
- self.fields.from_dataframe(df)
732
- self.items.from_records(records, progress=progress)
895
+ if on_schema_change not in ("evolve", "fail"):
896
+ raise ValueError(f"on_schema_change must be 'evolve' or 'fail', got {on_schema_change!r}")
897
+ provisioned: set[str] = set()
898
+ coercions: "Dict[str, Any] | None" = None
899
+ if isinstance(schema, dict):
900
+ coercions = {
901
+ internal_field_name(str(column)): partial(coerce_field_value, field_type)
902
+ for column, field_type in schema.items()
903
+ }
904
+
905
+ def _mapped(chunk: Any) -> Any:
906
+ if mapping and hasattr(chunk, "rename"):
907
+ return chunk.rename(columns=mapping)
908
+ return chunk
909
+
910
+ def _ensure_new_fields(chunk: Any) -> None:
911
+ chunk = _mapped(chunk)
912
+ if not hasattr(chunk, "columns"): # only DataFrame/CSV chunks carry a schema
913
+ return
914
+ new = [c for c in chunk.columns if internal_field_name(str(c)) not in provisioned]
915
+ if not new:
916
+ return
917
+ if on_schema_change == "fail" and provisioned:
918
+ raise ValueError(f"source schema changed: new column(s) {new}")
919
+ self.fields.ensure_from_dataframe(chunk[new])
920
+ provisioned.update(internal_field_name(str(c)) for c in new)
921
+ self.context.execute_query()
922
+
923
+ def _prepare(first_chunk: Any) -> None:
924
+ if schema is not None:
925
+ self.ensure_fields(schema)
926
+ names = schema.keys() if isinstance(schema, dict) else schema
927
+ provisioned.update(internal_field_name(str(k)) for k in names)
928
+ else:
929
+ _ensure_new_fields(first_chunk)
930
+
931
+ def _to_records(chunk: Any) -> list[dict]:
932
+ return records_from_dataframe(_mapped(chunk), key_fn=internal_field_name)
933
+
934
+ to_records = _to_records if format in ("dataframe", "csv") else None
935
+
936
+ return self.items.from_records(
937
+ source,
938
+ format=format,
939
+ chunksize=chunksize,
940
+ total=total,
941
+ key=key,
942
+ key_field=key_field,
943
+ on_conflict=on_conflict,
944
+ enforce_unique=enforce_unique,
945
+ checkpoint=checkpoint,
946
+ on_error=on_error,
947
+ progress=progress,
948
+ prepare=_prepare,
949
+ before_chunk=_ensure_new_fields,
950
+ to_records=to_records,
951
+ dry_run=dry_run,
952
+ dead_letter=dead_letter,
953
+ mapping=mapping if to_records is None else None,
954
+ coerce=coercions,
955
+ )
956
+
957
+ def from_dataframe(self, source, **opts) -> "ImportResult":
958
+ """Stream a DataFrame / chunked CSV into this list (see :meth:`from_records`)."""
959
+ return self.from_records(source, format="dataframe", **opts)
960
+
961
+ def from_csv(self, source, **opts) -> "ImportResult":
962
+ """Stream a CSV source into this list (see :meth:`from_records`)."""
963
+ return self.from_records(source, format="csv", **opts)
964
+
965
+ def from_json(self, source, **opts) -> "ImportResult":
966
+ """Stream a JSON-array file into this list (see :meth:`from_records`)."""
967
+ return self.from_records(source, format="json", **opts)
968
+
969
+ def from_ndjson(self, source, **opts) -> "ImportResult":
970
+ """Stream an NDJSON source into this list (see :meth:`from_records`)."""
971
+ return self.from_records(source, format="ndjson", **opts)
972
+
973
+ def from_excel(self, source, **opts) -> "ImportResult":
974
+ """Stream an Excel (.xlsx) worksheet into this list (see :meth:`from_records`)."""
975
+ return self.from_records(source, format="excel", **opts)
976
+
977
+ def from_parquet(self, source, **opts) -> "ImportResult":
978
+ """Stream a Parquet file into this list (see :meth:`from_records`)."""
979
+ return self.from_records(source, format="parquet", **opts)
980
+
981
+ def from_orc(self, source, **opts) -> "ImportResult":
982
+ """Stream an ORC file into this list (see :meth:`from_records`)."""
983
+ return self.from_records(source, format="orc", **opts)
984
+
985
+ def from_feather(self, source, **opts) -> "ImportResult":
986
+ """Stream a Feather file into this list (see :meth:`from_records`)."""
987
+ return self.from_records(source, format="feather", **opts)
988
+
989
+ def from_file(
990
+ self,
991
+ server_relative_url: str,
992
+ *,
993
+ format: str = "csv", # noqa: A002
994
+ chunksize: int = SAFE_PAGE_SIZE,
995
+ **opts,
996
+ ) -> "ImportResult":
997
+ """Stream a SharePoint-hosted file (CSV/XLSX) into this list's items.
998
+
999
+ Downloads the file from this site (honoring auth) and streams it through
1000
+ :meth:`from_records` — bounded memory, resumable and idempotent with
1001
+ ``key=...``. ``format`` is ``"csv"`` (default) or ``"xlsx"``/``"excel"``.
1002
+
1003
+ >>> lst.from_file("Shared Documents/stocks.csv", key=["Name", "date"]) \\
1004
+ ... .execute_batch(concurrency=5)
1005
+ """
1006
+ import io
1007
+
1008
+ file = self.context.web.get_file_by_server_relative_url(server_relative_url)
1009
+ content = file.read()
1010
+ if format in ("xlsx", "excel"):
1011
+ from office365.runtime.converters.dataframe import require_pandas
1012
+
1013
+ df = require_pandas().read_excel(io.BytesIO(content))
1014
+ return self.from_dataframe(df, chunksize=chunksize, **opts)
1015
+ return self.from_csv(io.BytesIO(content), chunksize=chunksize, **opts)
1016
+
1017
+ def queue_dataframe(
1018
+ self,
1019
+ df,
1020
+ schema: "Dict[str, FieldType] | None" = None,
1021
+ progress: "ProgressCallback | None" = None,
1022
+ ) -> Self:
1023
+ """Queue a DataFrame into this list **deferred** (queue-all).
1024
+
1025
+ Provisions the columns (from the dtypes, or ``schema``) and queues an item
1026
+ create per row; run with ``execute_query()``. For large frames use the
1027
+ streaming :meth:`from_dataframe` (bounded memory + idempotency).
1028
+ """
1029
+ if schema is not None:
1030
+ self.ensure_fields(schema)
1031
+ else:
1032
+ self.fields.ensure_from_dataframe(df)
1033
+ self.items.queue_dataframe(df, progress=progress)
733
1034
  return self
734
1035
 
1036
+ def queue_records(self, records, progress: "ProgressCallback | None" = None) -> Self:
1037
+ """Queue record dicts into this list's items (deferred)."""
1038
+ self.items.queue_records(records, progress=progress)
1039
+ return self
1040
+
1041
+ def verify(
1042
+ self,
1043
+ source,
1044
+ *,
1045
+ key: "str | list[str]",
1046
+ format: str = "dataframe", # noqa: A002
1047
+ key_field: str = "MigrationKey",
1048
+ ) -> "VerificationResult":
1049
+ """Reconcile a source's natural keys against this list (bounded).
1050
+
1051
+ Streams the source, hashes each row's natural key and checks it against the
1052
+ list's existing keys. ``format`` is ``"dataframe"`` (default), ``"csv"`` or
1053
+ any registered format.
1054
+ """
1055
+ from office365.runtime.converters.dataframe import records_from_dataframe
1056
+ from office365.sharepoint.fields.name import internal_field_name
1057
+
1058
+ def _to_records(chunk):
1059
+ return records_from_dataframe(chunk, key_fn=internal_field_name)
1060
+
1061
+ to_records = _to_records if format in ("dataframe", "csv") else None
1062
+ return self.items.verify(source, key=key, format=format, key_field=key_field, to_records=to_records)
1063
+
1064
+ def export_to(self, target, *, format: str = "csv", page_size=None, **opts) -> Self: # noqa: A002
1065
+ """Export this list's items to ``target`` in ``format`` (deferred).
1066
+
1067
+ The record exporter (CSV/NDJSON/JSON/Excel); pass ``page_size`` to stream
1068
+ an appendable format page by page (bounded memory). For the ``.zip``
1069
+ package export use :meth:`export`. Run with ``execute_query()``.
1070
+ """
1071
+ if page_size:
1072
+ self.items.export_to(target, format=format, page_size=page_size, **opts)
1073
+ else:
1074
+ self.items.get_all().export_to(target, format=format, **opts)
1075
+ return self
1076
+
1077
+ def to_dataframe(self) -> "DataFrameResult":
1078
+ """Export this list's items to a pandas DataFrame (deferred result)."""
1079
+ return self.items.get_all().to_dataframe()
1080
+
1081
+ def to_parquet(self, target) -> Self:
1082
+ """Export this list's items to Parquet (deferred; requires the ``[parquet]`` extra)."""
1083
+ return self.export_to(target, format="parquet")
1084
+
1085
+ def to_orc(self, target) -> Self:
1086
+ """Export this list's items to ORC (deferred; requires the ``[parquet]`` extra)."""
1087
+ return self.export_to(target, format="orc")
1088
+
1089
+ def to_feather(self, target) -> Self:
1090
+ """Export this list's items to Feather (deferred; requires the ``[parquet]`` extra)."""
1091
+ return self.export_to(target, format="feather")
1092
+
735
1093
  def add_item(self, creation_information: Union[ListItemCreationInformation, Dict]) -> ListItem:
736
1094
  """The recommended way to add a list item is to send a POST request to the ListItemCollection resource endpoint,
737
1095
  as shown in ListItemCollection request examples."""
@@ -1247,7 +1605,7 @@ class List(SecurableObject):
1247
1605
  """Get list items"""
1248
1606
  return self.properties.get(
1249
1607
  "Items",
1250
- ListItemCollection(self.context, ResourcePath("items", self.resource_path)),
1608
+ ListItemCollection(self.context, ResourcePath("items", self.resource_path), parent=self),
1251
1609
  )
1252
1610
 
1253
1611
  @odata(name="RootFolder")
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from typing_extensions import Self
4
+
5
+ from office365.runtime.client_result import ClientResult
6
+ from office365.runtime.queries.service_operation import ServiceOperationQuery
7
+ from office365.runtime.types.collections import StringCollection
8
+ from office365.sharepoint.entity import Entity
9
+ from office365.sharepoint.lists.batch_convert_result import BatchConvertResult
10
+
11
+
12
+ class SmartWikiLibraryUtility(Entity):
13
+ def batch_convert_classic_wiki_pages(
14
+ self,
15
+ source_list_id: str,
16
+ source_item_ids: list[str],
17
+ target_web_url: str,
18
+ target_list_id: str,
19
+ use_fast_unordered_mode: bool,
20
+ parent_unique_id: str,
21
+ strict_asset_copy: bool,
22
+ ) -> ClientResult[BatchConvertResult]:
23
+ """BatchConvertClassicWikiPages operation.
24
+
25
+ Args:
26
+ source_list_id (str): sourceListId parameter
27
+ source_item_ids (list[str]): sourceItemIds parameter
28
+ target_web_url (str): targetWebUrl parameter
29
+ target_list_id (str): targetListId parameter
30
+ use_fast_unordered_mode (bool): useFastUnorderedMode parameter
31
+ parent_unique_id (str): parentUniqueId parameter
32
+ strict_asset_copy (bool): strictAssetCopy parameter
33
+ """
34
+ return_type = ClientResult(self.context, BatchConvertResult())
35
+ qry = ServiceOperationQuery(
36
+ self,
37
+ "BatchConvertClassicWikiPages",
38
+ None,
39
+ {
40
+ "sourceListId": source_list_id,
41
+ "sourceItemIds": StringCollection(source_item_ids),
42
+ "targetWebUrl": target_web_url,
43
+ "targetListId": target_list_id,
44
+ "useFastUnorderedMode": use_fast_unordered_mode,
45
+ "parentUniqueId": parent_unique_id,
46
+ "strictAssetCopy": strict_asset_copy,
47
+ },
48
+ None,
49
+ return_type,
50
+ )
51
+ self.context.add_query(qry)
52
+ return return_type
53
+
54
+ def ensure_smart_wiki_library_feature(self) -> Self:
55
+ """EnsureSmartWikiLibraryFeature operation."""
56
+ qry = ServiceOperationQuery(self, "EnsureSmartWikiLibraryFeature", None, {}, None, None)
57
+ self.context.add_query(qry)
58
+ return self
@@ -1,5 +1,6 @@
1
1
  from office365.runtime.client_result import ClientResult
2
2
  from office365.runtime.paths.v3.static import StaticPath
3
+ from office365.runtime.queries.function import FunctionQuery
3
4
  from office365.runtime.queries.service_operation import ServiceOperationQuery
4
5
  from office365.runtime.types.collections import StringCollection
5
6
  from office365.sharepoint.entity import Entity
@@ -33,3 +34,14 @@ class LogExport(Entity):
33
34
  @property
34
35
  def entity_type_name(self):
35
36
  return "Microsoft.Online.SharePoint.SPLogger.LogExport"
37
+
38
+ def get_partitions(self, log_type: str) -> ClientResult[StringCollection]:
39
+ """GetPartitions operation.
40
+
41
+ Args:
42
+ log_type (str): logType parameter
43
+ """
44
+ return_type = ClientResult(self.context, StringCollection())
45
+ qry = FunctionQuery(self, "GetPartitions", [log_type], return_type)
46
+ self.context.add_query(qry)
47
+ return return_type