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
@@ -9,9 +9,27 @@ from office365.directory.permissions.identity_set import IdentitySet
9
9
  from office365.entity import Entity
10
10
  from office365.entity_collection import EntityCollection
11
11
  from office365.runtime.client_value_collection import ClientValueCollection
12
+ from office365.runtime.limits import Limit, LimitKind, limit
12
13
  from office365.runtime.paths.resource_path import ResourcePath
13
14
 
15
+ _DOC = "https://learn.microsoft.com/en-us/graph/throttling-limits"
14
16
 
17
+
18
+ @limit(
19
+ Limit("call records", 15_000, LimitKind.SUPPORTED, "requests", "app", doc=_DOC, window_seconds=20),
20
+ Limit("call records", 10_000, LimitKind.SUPPORTED, "requests", "tenant", doc=_DOC, window_seconds=20),
21
+ Limit("call records", 1_500, LimitKind.SUPPORTED, "requests", "app+tenant", doc=_DOC, window_seconds=20),
22
+ Limit(
23
+ "call records",
24
+ 40,
25
+ LimitKind.SUPPORTED,
26
+ "requests",
27
+ "resource",
28
+ note="per call record",
29
+ doc=_DOC,
30
+ window_seconds=20,
31
+ ),
32
+ )
15
33
  class CallRecord(Entity):
16
34
  """Represents a single peer-to-peer call or a group call between multiple participants,
17
35
  sometimes referred to as an online meeting."""
@@ -15,14 +15,17 @@ from office365.communications.operations.cancel_media_processing import CancelMe
15
15
  from office365.communications.operations.comms import CommsOperation
16
16
  from office365.communications.operations.unmute_participant import UnmuteParticipantOperation
17
17
  from office365.communications.operations.update_recording_status import UpdateRecordingStatusOperation
18
+ from office365.communications.quotas import CALL_QUOTAS
18
19
  from office365.communications.result_info import ResultInfo
19
20
  from office365.entity import Entity
20
21
  from office365.entity_collection import EntityCollection
21
22
  from office365.runtime.client_value_collection import ClientValueCollection
23
+ from office365.runtime.limits import limit
22
24
  from office365.runtime.paths.resource_path import ResourcePath
23
25
  from office365.runtime.queries.service_operation import ServiceOperationQuery
24
26
 
25
27
 
28
+ @limit(*CALL_QUOTAS)
26
29
  class Call(Entity):
27
30
  """
28
31
  The call resource is created when there is an incoming call for the application or the application creates a
@@ -6,13 +6,16 @@ from typing import Optional
6
6
  from typing_extensions import Self
7
7
 
8
8
  from office365.communications.presences.status_message import PresenceStatusMessage
9
+ from office365.communications.quotas import PRESENCE_QUOTAS
9
10
  from office365.entity import Entity
10
11
  from office365.outlook.calendar.dateTimeTimeZone import DateTimeTimeZone
11
12
  from office365.outlook.mail.item_body import ItemBody
13
+ from office365.runtime.limits import limit
12
14
  from office365.runtime.queries.service_operation import ServiceOperationQuery
13
15
  from office365.runtime.types.duration import Duration
14
16
 
15
17
 
18
+ @limit(*PRESENCE_QUOTAS)
16
19
  class Presence(Entity):
17
20
  """Contains information about a user's presence, including their availability and user activity."""
18
21
 
@@ -0,0 +1,66 @@
1
+ """Microsoft Graph cloud-communications throttling quotas (reference).
2
+
3
+ Declared on the model with ``@limit(*CALL_QUOTAS)`` etc. (see ``Call``,
4
+ ``Presence``, ``VirtualEvent``). Reference-only — the library reacts to the
5
+ server signals (:mod:`office365.runtime.http.throttling`).
6
+
7
+ Source: https://learn.microsoft.com/en-us/graph/throttling-limits
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from office365.runtime.limits import Limit, LimitKind
13
+
14
+ _DOC = "https://learn.microsoft.com/en-us/graph/throttling-limits"
15
+ _SUPPORTED = LimitKind.SUPPORTED
16
+
17
+ CALL_QUOTAS = (
18
+ Limit(
19
+ "cloud communications",
20
+ 50_000,
21
+ _SUPPORTED,
22
+ "requests",
23
+ "app+tenant",
24
+ note="calls",
25
+ doc=_DOC,
26
+ window_seconds=15,
27
+ ),
28
+ )
29
+
30
+ PRESENCE_QUOTAS = (
31
+ Limit(
32
+ "cloud communications",
33
+ 10_000,
34
+ _SUPPORTED,
35
+ "requests",
36
+ "app+tenant",
37
+ note="presence",
38
+ doc=_DOC,
39
+ window_seconds=30,
40
+ ),
41
+ )
42
+
43
+ VIRTUAL_EVENT_QUOTAS = (
44
+ Limit(
45
+ "cloud communications",
46
+ 750,
47
+ _SUPPORTED,
48
+ "requests",
49
+ "app",
50
+ request_type="read",
51
+ note="virtual events",
52
+ doc=_DOC,
53
+ window_seconds=30,
54
+ ),
55
+ Limit(
56
+ "cloud communications",
57
+ 15,
58
+ _SUPPORTED,
59
+ "requests",
60
+ "app",
61
+ request_type="write",
62
+ note="virtual events",
63
+ doc=_DOC,
64
+ window_seconds=30,
65
+ ),
66
+ )
@@ -1,13 +1,16 @@
1
1
  from typing import Optional
2
2
 
3
+ from office365.communications.quotas import VIRTUAL_EVENT_QUOTAS
3
4
  from office365.communications.virtualevents.presenter import VirtualEventPresenter
4
5
  from office365.communications.virtualevents.session import VirtualEventSession
5
6
  from office365.entity import Entity
6
7
  from office365.entity_collection import EntityCollection
7
8
  from office365.outlook.mail.item_body import ItemBody
9
+ from office365.runtime.limits import limit
8
10
  from office365.runtime.paths.resource_path import ResourcePath
9
11
 
10
12
 
13
+ @limit(*VIRTUAL_EVENT_QUOTAS)
11
14
  class VirtualEvent(Entity):
12
15
  """Represents an abstract base type for a virtual event.
13
16
  Base type of virtualEventTownhall and virtualEventWebinar."""
@@ -2,14 +2,17 @@ from office365.directory.domains.dns_record import DomainDnsRecord
2
2
  from office365.directory.domains.state import DomainState
3
3
  from office365.directory.objects.collection import DirectoryObjectCollection
4
4
  from office365.directory.permissions.require_permission import require_permission
5
+ from office365.directory.quotas import IDENTITY_QUOTAS
5
6
  from office365.entity import Entity
6
7
  from office365.entity_collection import EntityCollection
8
+ from office365.runtime.limits import limit
7
9
  from office365.runtime.paths.resource_path import ResourcePath
8
10
  from office365.runtime.queries.service_operation import ServiceOperationQuery
9
11
  from office365.runtime.types.collections import StringCollection
10
12
  from office365.runtime.types.odata_property import odata
11
13
 
12
14
 
15
+ @limit(*IDENTITY_QUOTAS)
13
16
  class Domain(Entity):
14
17
  """
15
18
  Represents a domain associated with the tenant.
@@ -2,12 +2,15 @@ from typing import Optional
2
2
 
3
3
  from office365.directory.licenses.service_plan_info import ServicePlanInfo
4
4
  from office365.directory.licenses.units_detail import LicenseUnitsDetail
5
+ from office365.directory.quotas import IDENTITY_QUOTAS
5
6
  from office365.entity import Entity
6
7
  from office365.runtime.client_value_collection import ClientValueCollection
8
+ from office365.runtime.limits import limit
7
9
  from office365.runtime.types.collections import StringCollection
8
10
  from office365.runtime.types.odata_property import odata
9
11
 
10
12
 
13
+ @limit(*IDENTITY_QUOTAS)
11
14
  class SubscribedSku(Entity):
12
15
  """Contains information about a service SKU that a company is subscribed to."""
13
16
 
@@ -4,13 +4,16 @@ from datetime import datetime
4
4
 
5
5
  from typing_extensions import Self
6
6
 
7
+ from office365.directory.quotas import IDENTITY_QUOTAS
7
8
  from office365.entity import Entity
8
9
  from office365.runtime.client_result import ClientResult
10
+ from office365.runtime.limits import limit
9
11
  from office365.runtime.queries.service_operation import ServiceOperationQuery
10
12
  from office365.runtime.types.collections import StringCollection
11
13
  from office365.runtime.types.odata_property import odata
12
14
 
13
15
 
16
+ @limit(*IDENTITY_QUOTAS)
14
17
  class DirectoryObject(Entity):
15
18
  """Represents an Azure Active Directory object. The directoryObject type is the base type for many other
16
19
  directory entity types."""
@@ -0,0 +1,34 @@
1
+ """Microsoft Graph identity throttling quotas (reference).
2
+
3
+ Declared on the model with ``@limit(*IDENTITY_QUOTAS)`` (see ``DirectoryObject``,
4
+ ``SubscribedSku``, ``Domain``) — Graph throttles the identity API surface with a
5
+ **ResourceUnits** token bucket plus write quotas. Reference-only: the library
6
+ reacts to the server signals (:mod:`office365.runtime.http.throttling`).
7
+
8
+ Source: https://learn.microsoft.com/en-us/graph/throttling-limits
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from office365.runtime.limits import Limit, LimitKind
14
+
15
+ _DOC = "https://learn.microsoft.com/en-us/graph/throttling-limits"
16
+ _SUPPORTED = LimitKind.SUPPORTED
17
+
18
+ #: Identity and access quotas (the whole `/users`, `/groups`, ... surface).
19
+ IDENTITY_QUOTAS = (
20
+ Limit(
21
+ "identity",
22
+ 3_500,
23
+ _SUPPORTED,
24
+ "resource_units",
25
+ "app+tenant",
26
+ note="small tenants (<50 users); 5,000 medium (50-500), 8,000 large (>500)",
27
+ doc=_DOC,
28
+ window_seconds=10,
29
+ ),
30
+ Limit("identity", 150_000, _SUPPORTED, "resource_units", "app", doc=_DOC, window_seconds=20),
31
+ Limit("identity", 3_000, _SUPPORTED, "requests", "app+tenant", doc=_DOC, window_seconds=150, request_type="write"),
32
+ Limit("identity", 35_000, _SUPPORTED, "requests", "app", doc=_DOC, window_seconds=300, request_type="write"),
33
+ Limit("identity", 18_000, _SUPPORTED, "requests", "tenant", doc=_DOC, window_seconds=300, request_type="write"),
34
+ )
@@ -4,16 +4,16 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union, cast
4
4
 
5
5
  from office365.entity import Entity
6
6
  from office365.runtime.client_object import ClientObjectT
7
- from office365.runtime.client_object_collection import ClientObjectCollection
8
7
  from office365.runtime.paths.resource_path import ResourcePath
9
8
  from office365.runtime.paths.v4.entity import EntityPath
10
9
  from office365.runtime.queries.create_entity import CreateEntityQuery
10
+ from office365.runtime.record_collection import RecordCollection
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from office365.graph_client import GraphClient
14
14
 
15
15
 
16
- class EntityCollection(ClientObjectCollection[ClientObjectT]):
16
+ class EntityCollection(RecordCollection[ClientObjectT]):
17
17
  """A collection container which represents a named collections of entities"""
18
18
 
19
19
  def __init__(
office365/graph_client.py CHANGED
@@ -65,6 +65,7 @@ from office365.outlook.calendar.rooms.list import RoomList
65
65
  from office365.planner.planner import Planner
66
66
  from office365.reports.root import ReportRoot
67
67
  from office365.runtime.client_runtime_context import ClientRuntimeContext
68
+ from office365.runtime.http.request_options import RequestOptions
68
69
  from office365.runtime.odata.v4.batch_request import DEFAULT_MAX_BATCH_BYTES, ODataV4BatchRequest
69
70
  from office365.runtime.odata.v4.json_format import V4JsonFormat
70
71
  from office365.runtime.paths.resource_path import ResourcePath
@@ -353,12 +354,37 @@ class GraphClient(ClientRuntimeContext):
353
354
  )
354
355
  return self
355
356
 
357
+ def with_throttle_priority(self, priority: str = "normal") -> Self:
358
+ """Send ``x-ms-throttle-priority`` on every request (Graph throttling).
359
+
360
+ Per Microsoft Graph, low-priority requests are throttled first and
361
+ high-priority ones last; the header doesn't change the limits. Use
362
+ ``"high"`` only for requests initiated by a user (where throttling causes
363
+ user-visible failures) and ``"low"`` for background work.
364
+
365
+ Args:
366
+ priority: ``"low"``, ``"normal"`` (default) or ``"high"``.
367
+
368
+ Raises:
369
+ ValueError: When ``priority`` isn't one of the accepted values.
370
+ """
371
+ value = priority.lower()
372
+ if value not in ("low", "normal", "high"):
373
+ raise ValueError("priority must be one of 'low', 'normal', 'high'")
374
+
375
+ def _set_header(request: RequestOptions) -> None:
376
+ request.set_header("x-ms-throttle-priority", value)
377
+
378
+ self.pending_request().before_execute(_set_header, once=False)
379
+ return self
380
+
356
381
  def execute_batch(
357
382
  self,
358
383
  items_per_batch: int = 20,
359
384
  success_callback: Optional[Callable[[List[Any]], None]] = None,
360
385
  concurrency: int = 1,
361
386
  max_batch_bytes: Optional[int] = None,
387
+ sequential: bool = False,
362
388
  ) -> Self:
363
389
  """
364
390
  Execute batched requests
@@ -371,21 +397,39 @@ class GraphClient(ClientRuntimeContext):
371
397
  writes aren't re-applied. ``success_callback`` runs on the caller thread
372
398
  in completion order (not submission order).
373
399
 
400
+ Pass ``sequential=True`` to chain the sub-requests with ``dependsOn`` so
401
+ Graph runs them in order (a failed dependency yields HTTP 424). Use it
402
+ only for **ordering-dependent side effects** — read-after-write on the
403
+ same resource, delete-then-create, parent/child ordering — not for
404
+ independent requests (Graph's default parallel mode is faster). A batch
405
+ can't reference another sub-request's response, so it doesn't enable
406
+ create-then-use-id flows. Implies ``concurrency=1`` because Graph only
407
+ sequences within a single ``$batch``.
408
+
374
409
  Args:
375
410
  items_per_batch: Maximum items per batch (default: 20)
376
411
  success_callback: Optional callback for successful requests
377
412
  concurrency: Maximum number of concurrent batch requests (default 1)
378
413
  max_batch_bytes: Maximum estimated batch payload size (default ~3 MB)
414
+ sequential: Chain sub-requests with ``dependsOn`` (Graph v4 only).
415
+
416
+ Raises:
417
+ ValueError: When ``sequential`` is combined with ``concurrency > 1``.
379
418
  """
419
+ if sequential and concurrency > 1:
420
+ raise ValueError("sequential=True requires concurrency=1 (Graph sequences within a batch only)")
380
421
  max_bytes = DEFAULT_MAX_BATCH_BYTES if max_batch_bytes is None else max_batch_bytes
381
422
  batches = self._split_batches(items_per_batch, max_bytes)
423
+ if sequential:
424
+ for batch in batches:
425
+ batch.sequential = True
382
426
  if concurrency <= 1:
383
427
  batch_request = ODataV4BatchRequest("", V4JsonFormat())
384
428
  batch_request.beforeExecute += self.pending_request().authenticate_request
385
429
  for qry in batches:
386
430
  batch_request.execute_query_with_retry(qry)
387
- if callable(success_callback) and qry.return_type is not None:
388
- success_callback(qry.return_type)
431
+ if callable(success_callback) and qry.return_types:
432
+ success_callback(qry.return_types)
389
433
  return self
390
434
 
391
435
  self.pending_request()
office365/limits.py ADDED
@@ -0,0 +1,66 @@
1
+ """Unified access to service limits — mechanics, catalogs and discovery.
2
+
3
+ The library declares limits in three layers:
4
+
5
+ - **Mechanics** (:mod:`office365.runtime.limits`): :class:`Limit` (a static
6
+ threshold *or* a rate quota), the :func:`limit` decorator, and the guardrails
7
+ (:func:`exceeds` / :func:`warn_if_exceeds` / :func:`ensure_within` /
8
+ :func:`hint`).
9
+ - **Catalogs**: product vocabularies — e.g. ``sharepoint.thresholds.Limits``
10
+ (SharePoint) and the model-bound Graph quotas (``directory.quotas`` /
11
+ ``communications.quotas``).
12
+ - **Discovery**: a class carries its limits via ``@limit`` (``declared_limits()``);
13
+ :func:`limits_of` / :func:`verify_limits` inspect them; :func:`catalog`
14
+ aggregates every registered source.
15
+
16
+ Products register their catalogs with :func:`register_catalog`; ``@limit`` on a
17
+ class registers that class automatically (under ``product="model"``).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from office365.runtime.limits import (
23
+ DEFAULT_BATCH_SIZE,
24
+ SAFE_PAGE_SIZE,
25
+ Limit,
26
+ LimitCatalog,
27
+ LimitDecl,
28
+ LimitExceededError,
29
+ LimitKind,
30
+ LimitReport,
31
+ bounded,
32
+ catalog,
33
+ collect_class_limits,
34
+ collect_limit_meta,
35
+ ensure_within,
36
+ exceeds,
37
+ hint,
38
+ limit,
39
+ limits_of,
40
+ register_catalog,
41
+ verify_limits,
42
+ warn_if_exceeds,
43
+ )
44
+
45
+ __all__ = [
46
+ "DEFAULT_BATCH_SIZE",
47
+ "SAFE_PAGE_SIZE",
48
+ "Limit",
49
+ "LimitCatalog",
50
+ "LimitDecl",
51
+ "LimitExceededError",
52
+ "LimitKind",
53
+ "LimitReport",
54
+ "bounded",
55
+ "catalog",
56
+ "collect_class_limits",
57
+ "collect_limit_meta",
58
+ "ensure_within",
59
+ "exceeds",
60
+ "hint",
61
+ "limit",
62
+ "limits_of",
63
+ "register_catalog",
64
+ "verify_limits",
65
+ "warn_if_exceeds",
66
+ ]
@@ -44,7 +44,9 @@ from office365.migration.manifest import Manifest
44
44
  from office365.migration.report import MigrationReport, build_report, export_reports
45
45
  from office365.migration.runner import MigrationRunner
46
46
  from office365.migration.server_job import MigrationServerJob
47
- from office365.migration.session import MigrationSession
47
+ from office365.migration.session import MigrationSession, SessionTask
48
+ from office365.migration.settings import MigrationSettings
49
+ from office365.migration.tasks import MigrationTask
48
50
  from office365.migration.validators import VerificationReport, verify
49
51
 
50
52
  if TYPE_CHECKING:
@@ -91,10 +93,13 @@ __all__ = [
91
93
  "MigrationRunner",
92
94
  "MigrationServerJob",
93
95
  "MigrationSession",
96
+ "MigrationSettings",
94
97
  "MigrationStats",
98
+ "MigrationTask",
95
99
  "ScanContainer",
96
100
  "ScanDefinition",
97
101
  "ScanReport",
102
+ "SessionTask",
98
103
  "VerificationReport",
99
104
  "build_report",
100
105
  "export_reports",
@@ -9,8 +9,8 @@ SharePoint, the filesystem, and (later) S3 / PostgreSQL / Kafka all plug in here
9
9
  from __future__ import annotations
10
10
 
11
11
  from abc import ABC, abstractmethod
12
- from collections.abc import Callable, Iterable
13
- from typing import TYPE_CHECKING, Optional
12
+ from collections.abc import Iterable
13
+ from typing import TYPE_CHECKING, Callable, Optional
14
14
 
15
15
  from office365.migration.base import ConflictResolution, MigrationItem
16
16
 
@@ -22,7 +22,12 @@ MigrationProgress = Optional[Callable[["Progress"], None]]
22
22
 
23
23
 
24
24
  class DataSource(ABC):
25
- """Reads migration items from a source."""
25
+ """Reads migration items from a source.
26
+
27
+ Optional hook (not declared here so ``hasattr`` reflects real support):
28
+ ``read_permissions(item) -> list[PermissionEntry]`` — the item's ACLs, used
29
+ for best-effort ``preserve_permissions``. Absent when the source has none.
30
+ """
26
31
 
27
32
  @abstractmethod
28
33
  def list_items(self, progress: MigrationProgress = None) -> list[MigrationItem]:
@@ -38,7 +43,12 @@ class DataSource(ABC):
38
43
 
39
44
 
40
45
  class DataTarget(ABC):
41
- """Writes migration items to a target."""
46
+ """Writes migration items to a target.
47
+
48
+ Optional hooks (not declared here so ``hasattr`` reflects real support),
49
+ used for best-effort fidelity after a write:
50
+ ``apply_timestamps(item)`` and ``apply_permissions(item, permissions)``.
51
+ """
42
52
 
43
53
  @abstractmethod
44
54
  def exists(self, item: MigrationItem) -> bool:
@@ -12,9 +12,11 @@ import json
12
12
  from datetime import datetime, timezone
13
13
  from pathlib import Path
14
14
 
15
- from office365.migration._util import emit_progress, iso, record_to_json
16
15
  from office365.migration.adapters import MigrationProgress
17
16
  from office365.migration.base import MigrationItem
17
+ from office365.runtime.converters.json_file import record_to_json
18
+ from office365.runtime.converters.scalars import iso
19
+ from office365.runtime.operations import emit_progress
18
20
 
19
21
 
20
22
  class FileSystemSource:
@@ -41,6 +43,7 @@ class FileSystemSource:
41
43
  dest_path=str(path.relative_to(self._root)).replace("\\", "/"),
42
44
  size_bytes=stat.st_size,
43
45
  modified=iso(datetime.fromtimestamp(stat.st_mtime, timezone.utc)),
46
+ created=iso(datetime.fromtimestamp(stat.st_ctime, timezone.utc)),
44
47
  )
45
48
  items.append(item)
46
49
  emit_progress(progress, done=len(items), stage="planning", items=[item])
@@ -12,8 +12,8 @@ from __future__ import annotations
12
12
  import os
13
13
  from pathlib import Path
14
14
 
15
- from office365.migration._util import write_csv_json
16
15
  from office365.migration.assessment.report import AssessmentReport
16
+ from office365.migration.report_io import write_formats
17
17
 
18
18
  _COLUMNS = ["severity", "category", "location", "message", "suggestion"]
19
19
 
@@ -28,7 +28,7 @@ def export_assessment(report: AssessmentReport, output_dir: str | Path) -> list[
28
28
  Returns:
29
29
  List of written file paths.
30
30
  """
31
- written = write_csv_json(output_dir, "AssessmentReport", report.to_records(), _COLUMNS)
31
+ written = write_formats(report.to_records(), output_dir, "AssessmentReport", columns=_COLUMNS)
32
32
  written += export_scan_reports(report, output_dir)
33
33
  return written
34
34
 
@@ -42,10 +42,10 @@ def export_scan_reports(report: AssessmentReport, output_dir: str | Path) -> lis
42
42
  for scan in report.scan_reports.values():
43
43
  if not scan.records:
44
44
  continue
45
- written += write_csv_json(
45
+ written += write_formats(
46
+ scan.to_records(),
46
47
  os.path.join(output_dir, "ScannerReports"),
47
48
  f"{scan.name}-detail",
48
- scan.to_records(),
49
- list(scan.columns),
49
+ columns=list(scan.columns),
50
50
  )
51
51
  return written
@@ -1,4 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from office365.runtime.limits import Limit
2
7
 
3
8
 
4
9
  @dataclass
@@ -8,3 +13,17 @@ class AssessmentIssue:
8
13
  location: str # list/folder/field path
9
14
  message: str
10
15
  suggestion: str = ""
16
+ risk_code: str = "" # product-specific scan-assessment code (SPMT), when applicable
17
+ limit: Optional[Limit] = None # the authoritative (product-agnostic) limit, when applicable
18
+
19
+
20
+ #: SPMT scan-assessment risk codes the library can emit (a subset — the ones tied
21
+ #: to SharePoint service limits). See
22
+ #: https://learn.microsoft.com/en-us/sharepointmigration/spmt-scan-risk-codes
23
+ RISK_CODES: dict[str, str] = {
24
+ "LIST_VIEW_EXCEED_LIMIT": "The list view shows more items than the list view threshold.",
25
+ "ITEM_COUNT_EXCEED_INDEX_LIMIT": "Item count is too large to create a column index.",
26
+ "ITEM_COUNT_EXCEED_LIMIT": "Item count exceeds the per-list maximum.",
27
+ "LIST_VIEW_LOOKUP_EXCEED_LIMIT": "Lookup/people/managed-metadata columns exceed the list view lookup threshold.",
28
+ "UNIQUE_PERMISSION_EXCEED_LIMIT": "Unique permissions per list exceed the supported/recommended limit.",
29
+ }
@@ -172,6 +172,17 @@ class AssessmentReport(ClientValue):
172
172
  "location": issue.location,
173
173
  "message": issue.message,
174
174
  "suggestion": issue.suggestion,
175
+ "risk_code": issue.risk_code,
175
176
  }
176
177
  for issue in self.issues
177
178
  ]
179
+
180
+ @property
181
+ def by_risk_code(self) -> dict[str, int]:
182
+ """Issue counts per SPMT risk code (issues without a code are excluded)."""
183
+ self._ensure_finalized()
184
+ counts: dict[str, int] = {}
185
+ for issue in self.issues:
186
+ if issue.risk_code:
187
+ counts[issue.risk_code] = counts.get(issue.risk_code, 0) + 1
188
+ return counts