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.
- office365/communications/callrecords/call_record.py +18 -0
- office365/communications/calls/call.py +3 -0
- office365/communications/presences/presence.py +3 -0
- office365/communications/quotas.py +66 -0
- office365/communications/virtualevents/virtual_event.py +3 -0
- office365/directory/domains/domain.py +3 -0
- office365/directory/licenses/subscribed_sku.py +3 -0
- office365/directory/objects/object.py +3 -0
- office365/directory/quotas.py +34 -0
- office365/entity_collection.py +2 -2
- office365/graph_client.py +46 -2
- office365/limits.py +66 -0
- office365/migration/__init__.py +6 -1
- office365/migration/adapters/__init__.py +14 -4
- office365/migration/adapters/filesystem.py +4 -1
- office365/migration/assessment/export.py +5 -5
- office365/migration/assessment/issue.py +19 -0
- office365/migration/assessment/report.py +11 -0
- office365/migration/assessment/scanners/base.py +37 -49
- office365/migration/base.py +33 -8
- office365/migration/checkpoint.py +1 -1
- office365/migration/outlook/assessor.py +4 -4
- office365/migration/package/__init__.py +82 -0
- office365/migration/package/builder.py +603 -0
- office365/migration/package/crypto.py +54 -0
- office365/migration/package/manifest_xml.py +495 -0
- office365/migration/package/staging.py +180 -0
- office365/migration/report.py +7 -2
- office365/migration/report_io.py +79 -0
- office365/migration/runner.py +117 -10
- office365/migration/server_job.py +126 -14
- office365/migration/session.py +132 -49
- office365/migration/settings.py +67 -0
- office365/migration/sharepoint/__init__.py +2 -0
- office365/migration/sharepoint/adapters.py +207 -41
- office365/migration/sharepoint/assessor.py +45 -9
- office365/migration/sharepoint/options.py +57 -0
- office365/migration/sharepoint/package_target.py +209 -0
- office365/migration/sharepoint/registry.py +22 -0
- office365/migration/sharepoint/resolvers.py +96 -0
- office365/migration/sharepoint/scanners/__init__.py +29 -0
- office365/migration/sharepoint/scanners/browser_file_handling.py +55 -0
- office365/migration/sharepoint/scanners/checked_out_files.py +57 -0
- office365/migration/sharepoint/scanners/file_versions.py +56 -0
- office365/migration/sharepoint/scanners/files.py +5 -2
- office365/migration/sharepoint/scanners/large_excel_files.py +56 -0
- office365/migration/sharepoint/scanners/large_lists.py +58 -0
- office365/migration/sharepoint/scanners/large_sites.py +4 -3
- office365/migration/sharepoint/scanners/long_onedrive_urls.py +52 -0
- office365/migration/sharepoint/scanners/lookup.py +38 -0
- office365/migration/sharepoint/scanners/paths.py +9 -4
- office365/migration/sharepoint/scanners/permissions.py +20 -2
- office365/migration/sharepoint/scanners/smat.py +51 -0
- office365/migration/sharepoint/scanners/summary.py +1 -0
- office365/migration/sharepoint/scanners/thicket_folders.py +38 -0
- office365/migration/sharepoint/scanners/unsupported_site_templates.py +90 -0
- office365/migration/sharepoint/tenant_assessor.py +5 -3
- office365/migration/sharepoint/transfer.py +4 -2
- office365/migration/tasks.py +121 -0
- office365/migration/validators.py +2 -19
- office365/onedrive/driveitems/driveItem.py +116 -12
- office365/onedrive/termstore/groups/collection.py +2 -9
- office365/onedrive/termstore/sets/collection.py +2 -9
- office365/onedrive/termstore/terms/collection.py +3 -10
- office365/onedrive/workbooks/workbook.py +7 -0
- office365/outlook/contacts/folders/collection.py +4 -13
- office365/runtime/auth/authentication_context.py +5 -0
- office365/runtime/client_object.py +18 -0
- office365/runtime/client_object_collection.py +75 -245
- office365/runtime/client_request.py +77 -1
- office365/runtime/client_request_exception.py +127 -39
- office365/runtime/client_runtime_context.py +79 -8
- office365/runtime/converters/csv_writer.py +10 -4
- office365/runtime/converters/dataframe.py +84 -4
- office365/runtime/converters/json_file.py +12 -3
- office365/runtime/converters/parquet.py +67 -0
- office365/runtime/converters/records.py +31 -12
- office365/runtime/converters/registry.py +185 -0
- office365/runtime/converters/scalars.py +31 -0
- office365/runtime/converters/sql.py +69 -0
- office365/runtime/converters/streamers.py +105 -0
- office365/runtime/converters/streams.py +42 -0
- office365/runtime/converters/upsert.py +101 -0
- office365/runtime/exceptions.py +105 -0
- office365/runtime/http/prefer.py +26 -0
- office365/runtime/http/throttling.py +258 -56
- office365/runtime/imports.py +571 -0
- office365/runtime/limits.py +455 -0
- office365/runtime/odata/request.py +11 -2
- office365/runtime/odata/type.py +1 -0
- office365/runtime/odata/v3/batch_request.py +4 -2
- office365/runtime/odata/v4/batch_request.py +22 -7
- office365/runtime/operations.py +29 -0
- office365/runtime/parallel.py +12 -14
- office365/runtime/paths/v4/entity.py +2 -2
- office365/runtime/queries/batch.py +18 -1
- office365/runtime/queries/get_or_create.py +109 -0
- office365/runtime/record_collection.py +443 -0
- office365/runtime/retry.py +92 -26
- office365/runtime/transport/base.py +6 -1
- office365/runtime/transport/requests_transport.py +4 -0
- office365/runtime/transport/throttled_transport.py +67 -0
- office365/runtime/verification.py +40 -0
- office365/sharepoint/activities/logger.py +118 -8
- office365/sharepoint/administration/analytics/usage_service.py +15 -7
- office365/sharepoint/administration/sitemove/service.py +791 -8
- office365/sharepoint/alerts/alert.py +79 -0
- office365/sharepoint/apps/app_collection.py +25 -0
- office365/sharepoint/apps/launcher.py +16 -0
- office365/sharepoint/authpolicy/tenant/compliancereport.py +5 -0
- office365/sharepoint/businessdata/app_bdc_catalog.py +135 -0
- office365/sharepoint/businessdata/entityfieldvaluedictionary.py +65 -0
- office365/sharepoint/businessdata/entityidentifier.py +23 -0
- office365/sharepoint/businessdata/entityinstance.py +52 -0
- office365/sharepoint/businessdata/entityview.py +31 -0
- office365/sharepoint/businessdata/externalsubscriptionstore.py +9 -0
- office365/sharepoint/businessdata/typedescriptor.py +37 -0
- office365/sharepoint/client_context.py +40 -23
- office365/sharepoint/clientsidecomponent/hostedapps/app.py +21 -0
- office365/sharepoint/clientsidecomponent/hostedapps/manager.py +16 -0
- office365/sharepoint/comments/comment.py +84 -2
- office365/sharepoint/contentcenter/machinelearning/models/model.py +0 -23
- office365/sharepoint/contentcenter/machinelearning/publications/publication.py +0 -9
- office365/sharepoint/contentcenter/machinelearning/samples/sample.py +0 -14
- office365/sharepoint/contentcenter/machinelearning/workitems/item.py +0 -80
- office365/sharepoint/contentsolution/template_api.py +148 -0
- office365/sharepoint/contenttypes/collection.py +32 -10
- office365/sharepoint/convergence/addnoliveblobsinodcservicestateapi.py +9 -0
- office365/sharepoint/convergence/consumer_permissions.py +15 -0
- office365/sharepoint/convergence/migration_complete_state_api.py +25 -0
- office365/sharepoint/convergence/odcmetadatacleanedupapi.py +9 -0
- office365/sharepoint/documentmanagement/document_id.py +12 -0
- office365/sharepoint/entity.py +11 -2
- office365/sharepoint/entity_collection.py +2 -2
- office365/sharepoint/exceptions.py +194 -0
- office365/sharepoint/fields/builtin_field_id.py +45 -34
- office365/sharepoint/fields/builtin_field_name.py +123 -0
- office365/sharepoint/fields/coercion.py +131 -0
- office365/sharepoint/fields/collection.py +54 -37
- office365/sharepoint/fields/field.py +17 -0
- office365/sharepoint/fields/name.py +46 -2
- office365/sharepoint/files/collection.py +7 -1
- office365/sharepoint/files/file.py +54 -8
- office365/sharepoint/files/versions/version.py +38 -13
- office365/sharepoint/folders/collection.py +4 -0
- office365/sharepoint/folders/folder.py +91 -21
- office365/sharepoint/htmlpages/html_page.py +16 -0
- office365/sharepoint/htmlpages/html_page_service.py +19 -0
- office365/sharepoint/ir/migration.py +9 -0
- office365/sharepoint/listitems/caml/__init__.py +31 -0
- office365/sharepoint/listitems/caml/builder.py +129 -0
- office365/sharepoint/listitems/caml/expressions.py +424 -0
- office365/sharepoint/listitems/caml/fields.py +68 -0
- office365/sharepoint/listitems/caml/guard.py +63 -0
- office365/sharepoint/listitems/caml/query.py +71 -2
- office365/sharepoint/listitems/caml/values.py +113 -0
- office365/sharepoint/listitems/collection.py +21 -20
- office365/sharepoint/listitems/upsert.py +75 -0
- office365/sharepoint/lists/collection.py +29 -22
- office365/sharepoint/lists/exporter.py +4 -3
- office365/sharepoint/lists/list.py +397 -39
- office365/sharepoint/lists/smart_wiki_library_utility.py +58 -0
- office365/sharepoint/logger/export.py +12 -0
- office365/sharepoint/marketplace/app_metadata.py +97 -1
- office365/sharepoint/marketplace/appstatequeryjobdefinition.py +9 -0
- office365/sharepoint/marketplace/corporatecuratedgallery/carddesigns/card_designs.py +35 -0
- office365/sharepoint/marketplace/sitecollection/appcatalog/allowed_item.py +5 -0
- office365/sharepoint/marketplace/tenant/appcatalog/accessor.py +107 -30
- office365/sharepoint/microfeed/data.py +0 -6
- office365/sharepoint/microservice/manager.py +61 -0
- office365/sharepoint/migrationcenter/common/task.py +193 -4
- office365/sharepoint/migrationcenter/common/task_entity_data.py +1 -1
- office365/sharepoint/migrationcenter/deploy_status.py +13 -0
- office365/sharepoint/migrationcenter/service/properties.py +20 -0
- office365/sharepoint/migrationcenter/service/storage.py +34 -0
- office365/sharepoint/migrationcenter/spflight.py +13 -0
- office365/sharepoint/migrationcenter/urlparseresult.py +77 -0
- office365/sharepoint/multigeo/service/spmultigeoproxy.py +13 -0
- office365/sharepoint/navigation/servicerest.py +127 -0
- office365/sharepoint/oauth/authentication.py +13 -0
- office365/sharepoint/portal/theme_manager.py +120 -8
- office365/sharepoint/principal/users/user.py +10 -0
- office365/sharepoint/publishing/campaign/campaign.py +36 -1
- office365/sharepoint/publishing/feedvideopage.py +0 -7
- office365/sharepoint/publishing/pages/3d/page.py +0 -6
- office365/sharepoint/publishing/pages/page.py +0 -117
- office365/sharepoint/publishing/pages/reposts/repost.py +0 -7
- office365/sharepoint/publishing/pages/topics/topic.py +0 -7
- office365/sharepoint/publishing/subtitlefile.py +0 -40
- office365/sharepoint/quotamanagement/consumer/consumer_quota_info_api.py +14 -0
- office365/sharepoint/quotamanagement/consumer/non_quota_backfill_api.py +23 -0
- office365/sharepoint/quotamanagement/consumer/nonquotamigrationapi.py +9 -0
- office365/sharepoint/quotamanagement/consumer/quotamigrationapi.py +13 -0
- office365/sharepoint/request.py +57 -16
- office365/sharepoint/search/administration/document_crawl_log.py +24 -7
- office365/sharepoint/search/administration/providers/crawl_versions_info.py +29 -0
- office365/sharepoint/search/administration/providers/recycle_bin_info.py +23 -0
- office365/sharepoint/search/administration/providers/site_content_processing_info.py +67 -0
- office365/sharepoint/sites/site.py +976 -65
- office365/sharepoint/sites/siteprovisioninginfoprovider.py +13 -0
- office365/sharepoint/tenant/administration/datagovernance/insight_client.py +292 -23
- office365/sharepoint/tenant/administration/ddiadapter.py +97 -0
- office365/sharepoint/tenant/administration/internal/aad/collection.py +20 -0
- office365/sharepoint/tenant/administration/internal/aad/permission_grant.py +0 -19
- office365/sharepoint/tenant/administration/internal/appservice/permission_request.py +72 -1
- office365/sharepoint/tenant/administration/internal/appservice/principal.py +1 -3
- office365/sharepoint/tenant/administration/odb_license_enforcement_service.py +26 -0
- office365/sharepoint/tenant/administration/sites/properties.py +564 -6
- office365/sharepoint/tenant/administration/spdatagovernancesarrestapiclient.py +60 -1
- office365/sharepoint/tenant/insights/reports/manager.py +24 -12
- office365/sharepoint/tenant/management/office365_tenant.py +451 -2
- office365/sharepoint/tenantadministration/catalog_management_collection.py +69 -0
- office365/sharepoint/tenantadministration/catalog_management_custom_properties_collection.py +237 -0
- office365/sharepoint/tenantadministration/catalog_management_restricted_content_discovery_collection.py +56 -0
- office365/sharepoint/tenantadministration/sp_data_governance_client_exception.py +7 -0
- office365/sharepoint/thresholds.py +529 -0
- office365/sharepoint/translation/jobs/job.py +72 -2
- office365/sharepoint/translation/jobs/status.py +47 -9
- office365/sharepoint/translation/sync_translator.py +15 -2
- office365/sharepoint/utilities/move_copy_util.py +2 -1
- office365/sharepoint/viva/connections/page.py +27 -0
- office365/sharepoint/viva/home.py +38 -0
- office365/sharepoint/viva/learningmanager.py +9 -0
- office365/sharepoint/viva/vivaresources.py +60 -0
- office365/sharepoint/webhooks/subscription.py +0 -11
- office365/sharepoint/webparts/definitions/definition.py +13 -0
- office365/sharepoint/webparts/limited_manager.py +29 -7
- office365/sharepoint/webs/context_web_information.py +14 -9
- office365/sharepoint/webs/templates/__init__.py +11 -0
- office365/sharepoint/webs/templates/type.py +74 -15
- office365/sharepoint/workflow/definition.py +6 -0
- office365/sharepoint/workflow/deployment_service.py +155 -0
- office365/sharepoint/workflow/instances/service.py +33 -0
- office365/sharepoint/workflow/interopservice.py +68 -0
- office365/sharepoint/workflow/messagingservice.py +24 -0
- office365/sharepoint/workflow/servicesmanager.py +41 -2
- office365/sharepoint/workflow/subscriptions/service.py +67 -0
- office365/sharepoint/workflow/subscriptions/subscription.py +31 -0
- office365/sharepoint/workflow/task.py +425 -0
- office365_rest_python_client-3.2.0.dist-info/METADATA +605 -0
- {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/RECORD +242 -185
- office365/migration/_util.py +0 -73
- office365/sharepoint/listitems/caml/types.py +0 -70
- office365_rest_python_client-3.1.0.dist-info/METADATA +0 -475
- {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/WHEEL +0 -0
|
@@ -18,14 +18,16 @@ from collections.abc import Callable
|
|
|
18
18
|
from datetime import datetime
|
|
19
19
|
from typing import TYPE_CHECKING
|
|
20
20
|
|
|
21
|
-
from office365.migration._util import coerce_int, emit_progress
|
|
22
21
|
from office365.migration.assessment.containers import ScanContainer
|
|
23
22
|
from office365.migration.assessment.report import AssessmentReport
|
|
24
23
|
from office365.migration.assessment.runner import ScanRunner
|
|
25
24
|
from office365.migration.assessment.scanners import AssessmentOptions
|
|
25
|
+
from office365.migration.sharepoint.options import SharePointAssessmentOptions
|
|
26
26
|
from office365.migration.sharepoint.registry import sharepoint_scan_pairs
|
|
27
27
|
from office365.migration.sharepoint.scanners.summary import SiteScanSummary
|
|
28
28
|
from office365.runtime.client_result import ClientResult
|
|
29
|
+
from office365.runtime.converters.scalars import parse_int
|
|
30
|
+
from office365.runtime.operations import emit_progress
|
|
29
31
|
from office365.sharepoint.entity import Entity
|
|
30
32
|
|
|
31
33
|
if TYPE_CHECKING:
|
|
@@ -48,7 +50,7 @@ class MigrationTenantAssessor(Entity):
|
|
|
48
50
|
def __init__(self, tenant: "Tenant", options: AssessmentOptions | None = None) -> None:
|
|
49
51
|
super().__init__(tenant.context)
|
|
50
52
|
self._tenant = tenant
|
|
51
|
-
self._options = options or
|
|
53
|
+
self._options = options or SharePointAssessmentOptions()
|
|
52
54
|
|
|
53
55
|
def assess(
|
|
54
56
|
self,
|
|
@@ -77,7 +79,7 @@ class MigrationTenantAssessor(Entity):
|
|
|
77
79
|
site_url=site.url,
|
|
78
80
|
owner=site.owner_login_name,
|
|
79
81
|
storage_bytes=int(storage) if storage is not None else None,
|
|
80
|
-
web_count=
|
|
82
|
+
web_count=parse_int(site.webs_count) or 0,
|
|
81
83
|
last_modified=_clean_modified(site.last_content_modified_date),
|
|
82
84
|
lock_state=site.lock_state,
|
|
83
85
|
report_impacted_only=True,
|
|
@@ -21,11 +21,12 @@ from collections.abc import Callable, Iterable
|
|
|
21
21
|
from typing import TYPE_CHECKING, Tuple
|
|
22
22
|
|
|
23
23
|
from office365.runtime.parallel import run_parallel
|
|
24
|
+
from office365.sharepoint.thresholds import Limits
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
26
27
|
from office365.sharepoint.folders.folder import Folder
|
|
27
28
|
|
|
28
|
-
_DEFAULT_CHUNK_SIZE =
|
|
29
|
+
_DEFAULT_CHUNK_SIZE = Limits.UPLOAD_SESSION_CHUNK.value
|
|
29
30
|
|
|
30
31
|
# runtime-evaluated alias (typing form: the ``|``/``tuple[...]`` operators need 3.9+)
|
|
31
32
|
Failure = Tuple[str, str] # (dest_path, error)
|
|
@@ -68,7 +69,8 @@ def _transfer_files_parallel(
|
|
|
68
69
|
|
|
69
70
|
parents = {dest.rsplit("/", 1)[0] for dest, _ in files if "/" in dest}
|
|
70
71
|
if parents:
|
|
71
|
-
target_folder.ensure_folders(parents)
|
|
72
|
+
target_folder.ensure_folders(parents)
|
|
73
|
+
target_folder.execute_query()
|
|
72
74
|
|
|
73
75
|
failures: list[Failure] = []
|
|
74
76
|
context_factory = context_factory or (lambda: target_folder.context.clone(target_folder.context.base_url))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Migration tasks — declarative source → target descriptors (SPMT ``Add-SPMTTask``).
|
|
2
|
+
|
|
3
|
+
Mirrors ``Microsoft.SharePoint.MigrationTool.PowerShell``'s task types:
|
|
4
|
+
**FileShare** (a local folder), **SharePoint** (a source site — all lists, or
|
|
5
|
+
selected ones), and **Json**-defined tasks (the ``-JsonDefinition`` format).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
__all__ = ["FILE_SHARE", "SHAREPOINT", "MigrationTask"]
|
|
14
|
+
|
|
15
|
+
FILE_SHARE = "FileShare"
|
|
16
|
+
SHAREPOINT = "SharePoint"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class MigrationTask:
|
|
21
|
+
"""A declarative migration task (source → target mapping)."""
|
|
22
|
+
|
|
23
|
+
kind: str = FILE_SHARE
|
|
24
|
+
file_share_source: str | None = None
|
|
25
|
+
sharepoint_source_site_url: str | None = None
|
|
26
|
+
source_list: str | None = None
|
|
27
|
+
source_list_relative_path: str | None = None
|
|
28
|
+
target_site_url: str | None = None
|
|
29
|
+
target_list: str | None = None
|
|
30
|
+
target_list_relative_path: str | None = None
|
|
31
|
+
migrate_all: bool = False
|
|
32
|
+
migration_type: str = "Content"
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def file_share(
|
|
36
|
+
cls,
|
|
37
|
+
source: str,
|
|
38
|
+
target_site_url: str,
|
|
39
|
+
target_list: str,
|
|
40
|
+
target_list_relative_path: str | None = None,
|
|
41
|
+
) -> "MigrationTask":
|
|
42
|
+
"""A file-share task (``Add-SPMTTask -FileShareSource …``)."""
|
|
43
|
+
return cls(
|
|
44
|
+
kind=FILE_SHARE,
|
|
45
|
+
file_share_source=source,
|
|
46
|
+
target_site_url=target_site_url,
|
|
47
|
+
target_list=target_list,
|
|
48
|
+
target_list_relative_path=target_list_relative_path,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def sharepoint(
|
|
53
|
+
cls,
|
|
54
|
+
source_site_url: str,
|
|
55
|
+
target_site_url: str,
|
|
56
|
+
*,
|
|
57
|
+
source_list: str | None = None,
|
|
58
|
+
target_list: str | None = None,
|
|
59
|
+
source_list_relative_path: str | None = None,
|
|
60
|
+
target_list_relative_path: str | None = None,
|
|
61
|
+
migrate_all: bool = False,
|
|
62
|
+
) -> "MigrationTask":
|
|
63
|
+
"""A SharePoint task — ``migrate_all``, or a single ``source_list`` → ``target_list``."""
|
|
64
|
+
return cls(
|
|
65
|
+
kind=SHAREPOINT,
|
|
66
|
+
sharepoint_source_site_url=source_site_url,
|
|
67
|
+
source_list=source_list,
|
|
68
|
+
source_list_relative_path=source_list_relative_path,
|
|
69
|
+
target_site_url=target_site_url,
|
|
70
|
+
target_list=target_list,
|
|
71
|
+
target_list_relative_path=target_list_relative_path,
|
|
72
|
+
migrate_all=migrate_all,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_json(cls, definition: str | dict) -> "MigrationTask":
|
|
77
|
+
"""Parse an SPMT JSON task definition (``Add-SPMTTask -JsonDefinition``)."""
|
|
78
|
+
data = json.loads(definition) if isinstance(definition, str) else dict(definition)
|
|
79
|
+
if "Tasks" in data: # a {"Tasks": [...]} wrapper → the first task
|
|
80
|
+
tasks = data["Tasks"]
|
|
81
|
+
if not tasks:
|
|
82
|
+
raise ValueError("no tasks in the JSON definition")
|
|
83
|
+
data = tasks[0]
|
|
84
|
+
source = data.get("SourcePath")
|
|
85
|
+
target = data.get("TargetPath")
|
|
86
|
+
lists = (data.get("Items") or {}).get("Lists") or []
|
|
87
|
+
if lists:
|
|
88
|
+
first = lists[0]
|
|
89
|
+
return cls(
|
|
90
|
+
kind=SHAREPOINT,
|
|
91
|
+
sharepoint_source_site_url=source,
|
|
92
|
+
target_site_url=target,
|
|
93
|
+
source_list=first.get("SourceList"),
|
|
94
|
+
target_list=first.get("TargetList"),
|
|
95
|
+
)
|
|
96
|
+
return cls(
|
|
97
|
+
kind=FILE_SHARE,
|
|
98
|
+
file_share_source=source,
|
|
99
|
+
target_site_url=target,
|
|
100
|
+
target_list=data.get("TargetList"),
|
|
101
|
+
target_list_relative_path=data.get("TargetListRelativePath"),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def to_json(self) -> str:
|
|
105
|
+
"""Serialize to the SPMT JSON task shape (round-trips through :meth:`from_json`)."""
|
|
106
|
+
if self.kind == SHAREPOINT:
|
|
107
|
+
data: dict = {"SourcePath": self.sharepoint_source_site_url, "TargetPath": self.target_site_url}
|
|
108
|
+
if not self.migrate_all:
|
|
109
|
+
data["Items"] = {"Lists": [{"SourceList": self.source_list, "TargetList": self.target_list}]}
|
|
110
|
+
else:
|
|
111
|
+
data = {
|
|
112
|
+
"SourcePath": self.file_share_source,
|
|
113
|
+
"TargetPath": self.target_site_url,
|
|
114
|
+
"TargetList": self.target_list,
|
|
115
|
+
"TargetListRelativePath": self.target_list_relative_path,
|
|
116
|
+
}
|
|
117
|
+
return json.dumps({name: value for name, value in data.items() if value is not None}, indent=2)
|
|
118
|
+
|
|
119
|
+
def label(self) -> str:
|
|
120
|
+
source = self.file_share_source or self.sharepoint_source_site_url or "?"
|
|
121
|
+
return f"{self.kind}: {source} -> {self.target_site_url or '?'}"
|
|
@@ -7,29 +7,12 @@ reports any missing, extra, or mismatched items.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import random
|
|
10
|
-
from dataclasses import dataclass, field
|
|
11
10
|
|
|
12
11
|
from office365.migration.adapters import DataSource, DataTarget
|
|
13
12
|
from office365.migration.manifest import Manifest
|
|
13
|
+
from office365.runtime.verification import VerificationReport
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class VerificationReport:
|
|
18
|
-
source_count: int = 0
|
|
19
|
-
target_count: int = 0
|
|
20
|
-
checked: int = 0
|
|
21
|
-
mismatches: list[str] = field(default_factory=list)
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def ok(self) -> bool:
|
|
25
|
-
return self.source_count == self.target_count and not self.mismatches
|
|
26
|
-
|
|
27
|
-
def summary(self) -> str:
|
|
28
|
-
status = "OK" if self.ok else "MISMATCH"
|
|
29
|
-
return (
|
|
30
|
-
f"{status} | source: {self.source_count}, target: {self.target_count}, "
|
|
31
|
-
f"checksums checked: {self.checked}, issues: {len(self.mismatches)}"
|
|
32
|
-
)
|
|
15
|
+
__all__ = ["VerificationReport", "verify"]
|
|
33
16
|
|
|
34
17
|
|
|
35
18
|
def verify(
|
|
@@ -249,17 +249,40 @@ class DriveItem(BaseItem):
|
|
|
249
249
|
Returns:
|
|
250
250
|
DriveItem: The target folder (existing or newly created).
|
|
251
251
|
"""
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
names = [name for name in url_path.replace("\\", "/").split("/") if name]
|
|
252
|
+
names = self._split_path(url_path)
|
|
255
253
|
if not names:
|
|
256
254
|
raise ValueError("Path is empty")
|
|
257
255
|
return_type = DriveItem(self.context)
|
|
258
256
|
|
|
257
|
+
def _on_resolved(folder: DriveItem) -> None:
|
|
258
|
+
return_type._resource_path = folder._resource_path
|
|
259
|
+
return_type.copy_from(folder)
|
|
260
|
+
|
|
261
|
+
self._resolve_path(names, _on_resolved)
|
|
262
|
+
return return_type
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def _split_path(path: str) -> list[str]:
|
|
266
|
+
"""Split a (server-relative) path into non-empty segments."""
|
|
267
|
+
return [name for name in path.replace("\\", "/").split("/") if name]
|
|
268
|
+
|
|
269
|
+
def _resolve_path(self, names: list[str], on_resolved: Callable[[DriveItem], None]) -> None:
|
|
270
|
+
"""Walk and create the folders in ``names``, then call ``on_resolved`` with the resolved folder.
|
|
271
|
+
|
|
272
|
+
Reuses an existing folder per level or creates the missing one. The
|
|
273
|
+
callback receives a query-bound parent entity, so callers can queue
|
|
274
|
+
further work (e.g. a file upload) that depends on the parent id. With no
|
|
275
|
+
names the callback runs synchronously against ``self``.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
names (list[str]): Folder names, relative to this item.
|
|
279
|
+
on_resolved (Callable[[DriveItem], None]): Invoked with the resolved folder.
|
|
280
|
+
"""
|
|
281
|
+
from office365.runtime.exceptions import ObjectNotFoundException
|
|
282
|
+
|
|
259
283
|
def _walk(parent: DriveItem, idx: int) -> None:
|
|
260
284
|
if idx == len(names):
|
|
261
|
-
|
|
262
|
-
return_type.copy_from(parent)
|
|
285
|
+
on_resolved(parent)
|
|
263
286
|
return
|
|
264
287
|
|
|
265
288
|
child = parent.get_by_path(names[idx])
|
|
@@ -282,6 +305,59 @@ class DriveItem(BaseItem):
|
|
|
282
305
|
self.get_by_path(prefix).get().after_execute(lambda resolved: _walk(resolved, idx + 1))
|
|
283
306
|
|
|
284
307
|
_walk(self, 0)
|
|
308
|
+
|
|
309
|
+
@require_permission(
|
|
310
|
+
delegated=["Files.ReadWrite", "Files.ReadWrite.All", "Sites.ReadWrite.All"],
|
|
311
|
+
application=["Files.ReadWrite.All", "Sites.ReadWrite.All"],
|
|
312
|
+
notes="Ensure a file exists in a drive, uploading its content when missing",
|
|
313
|
+
)
|
|
314
|
+
def ensure_file(self, relative_path: str, content: bytes | str = b"", *, on_conflict: str = "skip") -> DriveItem:
|
|
315
|
+
"""Ensure a file exists at the given path, uploading content when needed.
|
|
316
|
+
|
|
317
|
+
The file counterpart of :meth:`ensure_folder`: missing parent folders are
|
|
318
|
+
created (via :meth:`ensure_folder`), then the leaf file is resolved —
|
|
319
|
+
reusing the existing file or uploading ``content`` when absent. Fully
|
|
320
|
+
deferred — run the chain with ``execute_query()`` and the returned item
|
|
321
|
+
addresses the target file:
|
|
322
|
+
|
|
323
|
+
>>> file = drive_item.ensure_file("2024/Q1/placeholder.txt").execute_query()
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
relative_path (str): File name or path relative to this item, e.g.
|
|
327
|
+
``"report.txt"`` or ``"2024/Q1/report.txt"``.
|
|
328
|
+
content (bytes or str): File content; ``str`` is encoded as UTF-8.
|
|
329
|
+
on_conflict (str): Behaviour when the file already exists.
|
|
330
|
+
``"skip"`` (default) leaves it untouched, so a re-run is a no-op
|
|
331
|
+
and ``content`` is never sent; ``"replace"`` overwrites it with
|
|
332
|
+
``content`` in a single request.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
DriveItem: The target file (existing or newly uploaded).
|
|
336
|
+
"""
|
|
337
|
+
from office365.runtime.queries.get_or_create import get_or_create
|
|
338
|
+
|
|
339
|
+
if on_conflict not in ("skip", "replace"):
|
|
340
|
+
raise ValueError(f"on_conflict must be 'skip' or 'replace', got {on_conflict!r}")
|
|
341
|
+
names = self._split_path(relative_path)
|
|
342
|
+
if not names:
|
|
343
|
+
raise ValueError("Path is empty")
|
|
344
|
+
name = names[-1]
|
|
345
|
+
data = content.encode("utf-8") if isinstance(content, str) else content
|
|
346
|
+
return_type = DriveItem(self.context)
|
|
347
|
+
|
|
348
|
+
def _on_resolved(parent_item: DriveItem) -> None:
|
|
349
|
+
return_type._resource_path = parent_item.get_by_path(name)._resource_path
|
|
350
|
+
if on_conflict == "replace":
|
|
351
|
+
parent_item.context.add_query(parent_item._build_upload_query(return_type, data))
|
|
352
|
+
return
|
|
353
|
+
get_or_create(
|
|
354
|
+
find=lambda: parent_item.get_by_path(name).get(),
|
|
355
|
+
create_query=lambda: parent_item._build_upload_query(return_type, data),
|
|
356
|
+
return_type=return_type,
|
|
357
|
+
on_conflict="skip",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
self._resolve_path(names[:-1], _on_resolved)
|
|
285
361
|
return return_type
|
|
286
362
|
|
|
287
363
|
def create_powerpoint(self, name: str) -> DriveItem:
|
|
@@ -351,6 +427,22 @@ class DriveItem(BaseItem):
|
|
|
351
427
|
self.context.add_query(qry)
|
|
352
428
|
return self
|
|
353
429
|
|
|
430
|
+
def delete_object(self, bypass_shared_lock: bool = False) -> Self:
|
|
431
|
+
"""Deletes the driveItem.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
bypass_shared_lock: Send ``Prefer: bypass-shared-lock`` so the delete
|
|
435
|
+
succeeds even when the item is open for editing. Without it the
|
|
436
|
+
server rejects the request with
|
|
437
|
+
:class:`~office365.runtime.exceptions.FileLockedException` (HTTP 423).
|
|
438
|
+
"""
|
|
439
|
+
super().delete_object()
|
|
440
|
+
if bypass_shared_lock:
|
|
441
|
+
from office365.runtime.http.prefer import prefer_bypass_shared_lock
|
|
442
|
+
|
|
443
|
+
self.context.before_execute(prefer_bypass_shared_lock, once=True)
|
|
444
|
+
return self
|
|
445
|
+
|
|
354
446
|
def extract_sensitivity_labels(
|
|
355
447
|
self,
|
|
356
448
|
) -> ClientResult[ExtractSensitivityLabelsResult]:
|
|
@@ -464,6 +556,24 @@ class DriveItem(BaseItem):
|
|
|
464
556
|
self.context.add_query(qry)
|
|
465
557
|
return qry.return_type
|
|
466
558
|
|
|
559
|
+
def _build_upload_query(self, item: DriveItem, content: bytes | None) -> ServiceOperationQuery[DriveItem]:
|
|
560
|
+
"""Build (but do not queue) the ``PUT .../content`` query for ``item``.
|
|
561
|
+
|
|
562
|
+
Shared by :meth:`upload` and :meth:`ensure_file` — the latter needs a
|
|
563
|
+
query it can hand to ``get_or_create``'s deferred create slot.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
item (DriveItem): The file that receives the content.
|
|
567
|
+
content (bytes or None): The binary content to upload.
|
|
568
|
+
"""
|
|
569
|
+
qry = ServiceOperationQuery(item, "content", None, content, None, item)
|
|
570
|
+
|
|
571
|
+
def _modify_query(request: RequestOptions) -> None:
|
|
572
|
+
request.method = HttpMethod.Put
|
|
573
|
+
|
|
574
|
+
qry.before_execute(_modify_query)
|
|
575
|
+
return qry
|
|
576
|
+
|
|
467
577
|
@require_permission(
|
|
468
578
|
delegated=["Files.ReadWrite", "Files.ReadWrite.All", "Sites.ReadWrite.All"],
|
|
469
579
|
application=["Files.ReadWrite.All", "Sites.ReadWrite.All"],
|
|
@@ -474,13 +584,7 @@ class DriveItem(BaseItem):
|
|
|
474
584
|
|
|
475
585
|
return_type = DriveItem(self.context, UrlPath(name, self.resource_path))
|
|
476
586
|
self.children.add_child(return_type)
|
|
477
|
-
|
|
478
|
-
qry = ServiceOperationQuery(return_type, "content", None, content, None, return_type)
|
|
479
|
-
|
|
480
|
-
def _modify_query(request: RequestOptions) -> None:
|
|
481
|
-
request.method = HttpMethod.Put
|
|
482
|
-
|
|
483
|
-
self.context.add_query(qry).before_execute(_modify_query)
|
|
587
|
+
self.context.add_query(self._build_upload_query(return_type, content))
|
|
484
588
|
return return_type
|
|
485
589
|
|
|
486
590
|
def upload_file(self, path_or_file: str | PathLike | IOBase) -> DriveItem:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from office365.directory.permissions.require_permission import require_permission
|
|
2
2
|
from office365.entity_collection import EntityCollection
|
|
3
3
|
from office365.onedrive.termstore.groups.group import Group
|
|
4
|
-
from office365.runtime.client_request_exception import ClientRequestException, DuplicatedObjectException
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class GroupCollection(EntityCollection[Group]):
|
|
@@ -33,12 +32,6 @@ class GroupCollection(EntityCollection[Group]):
|
|
|
33
32
|
|
|
34
33
|
def ensure(self, name: str) -> Group:
|
|
35
34
|
"""Gets existing group by name or creates a new one (idempotent)."""
|
|
36
|
-
|
|
35
|
+
from office365.runtime.queries.get_or_create import create_or_get
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
if not isinstance(error, DuplicatedObjectException):
|
|
40
|
-
raise error
|
|
41
|
-
self.get_by_name(name).after_execute(lambda existing: return_type.copy_from(existing), execute_first=True)
|
|
42
|
-
|
|
43
|
-
return_type.on_error(_on_error)
|
|
44
|
-
return return_type
|
|
37
|
+
return create_or_get(create=lambda: self.add(name), find=lambda: self.get_by_name(name))
|
|
@@ -6,7 +6,6 @@ from office365.directory.permissions.require_permission import require_permissio
|
|
|
6
6
|
from office365.entity_collection import EntityCollection
|
|
7
7
|
from office365.onedrive.termstore.sets.name import LocalizedName
|
|
8
8
|
from office365.onedrive.termstore.sets.set import Set
|
|
9
|
-
from office365.runtime.client_request_exception import ClientRequestException, DuplicatedObjectException
|
|
10
9
|
from office365.runtime.client_value_collection import ClientValueCollection
|
|
11
10
|
from office365.runtime.paths.v4.entity import EntityPath
|
|
12
11
|
from office365.runtime.queries.create_entity import CreateEntityQuery
|
|
@@ -76,12 +75,6 @@ class SetCollection(EntityCollection[Set]):
|
|
|
76
75
|
|
|
77
76
|
def ensure_set(self, name: str) -> Set:
|
|
78
77
|
"""Gets existing set by name or creates a new one (idempotent)."""
|
|
79
|
-
|
|
78
|
+
from office365.runtime.queries.get_or_create import create_or_get
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
if not isinstance(error, DuplicatedObjectException):
|
|
83
|
-
raise error
|
|
84
|
-
self.get_by_name(name).after_execute(lambda existing: return_type.copy_from(existing), execute_first=True)
|
|
85
|
-
|
|
86
|
-
return_type.on_error(_on_error)
|
|
87
|
-
return return_type
|
|
80
|
+
return create_or_get(create=lambda: self.add(name), find=lambda: self.get_by_name(name))
|
|
@@ -4,7 +4,6 @@ from office365.directory.permissions.require_permission import require_permissio
|
|
|
4
4
|
from office365.entity_collection import EntityCollection
|
|
5
5
|
from office365.onedrive.termstore.terms.label import LocalizedLabel
|
|
6
6
|
from office365.onedrive.termstore.terms.term import Term
|
|
7
|
-
from office365.runtime.client_request_exception import ClientRequestException, DuplicatedObjectException
|
|
8
7
|
from office365.runtime.client_value_collection import ClientValueCollection
|
|
9
8
|
from office365.runtime.paths.v4.entity import EntityPath
|
|
10
9
|
from office365.runtime.queries.create_entity import CreateEntityQuery
|
|
@@ -50,13 +49,7 @@ class TermCollection(EntityCollection[Term]):
|
|
|
50
49
|
return return_type
|
|
51
50
|
|
|
52
51
|
def ensure(self, label: str) -> Term:
|
|
53
|
-
|
|
52
|
+
"""Gets an existing term by label or creates it (idempotent)."""
|
|
53
|
+
from office365.runtime.queries.get_or_create import create_or_get
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
if not isinstance(error, DuplicatedObjectException):
|
|
57
|
-
raise error
|
|
58
|
-
|
|
59
|
-
self.get_by_label(label).after_execute(lambda existing: return_type.copy_from(existing), execute_first=True)
|
|
60
|
-
|
|
61
|
-
return_type.on_error(_on_error)
|
|
62
|
-
return return_type
|
|
55
|
+
return create_or_get(create=lambda: self.add(label), find=lambda: self.get_by_label(label))
|
|
@@ -17,11 +17,18 @@ from office365.onedrive.workbooks.worksheets.collection import (
|
|
|
17
17
|
)
|
|
18
18
|
from office365.runtime.client_result import ClientResult
|
|
19
19
|
from office365.runtime.http.request_options import RequestOptions
|
|
20
|
+
from office365.runtime.limits import Limit, LimitKind, limit
|
|
20
21
|
from office365.runtime.paths.resource_path import ResourcePath
|
|
21
22
|
from office365.runtime.queries.function import FunctionQuery
|
|
22
23
|
from office365.runtime.queries.service_operation import ServiceOperationQuery
|
|
23
24
|
|
|
25
|
+
_DOC = "https://learn.microsoft.com/en-us/graph/throttling-limits"
|
|
24
26
|
|
|
27
|
+
|
|
28
|
+
@limit(
|
|
29
|
+
Limit("excel", 5_000, LimitKind.SUPPORTED, "requests", "app", doc=_DOC, window_seconds=10),
|
|
30
|
+
Limit("excel", 1_500, LimitKind.SUPPORTED, "requests", "app+tenant", doc=_DOC, window_seconds=10),
|
|
31
|
+
)
|
|
25
32
|
class Workbook(Entity):
|
|
26
33
|
"""The top-level object that contains related workbook objects such as worksheets, tables, and ranges."""
|
|
27
34
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from office365.delta_collection import DeltaCollection
|
|
2
2
|
from office365.outlook.contacts.folders.folder import ContactFolder
|
|
3
|
-
from office365.runtime.client_request_exception import ClientRequestException, DuplicatedObjectException
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class ContactFolderCollection(DeltaCollection[ContactFolder]):
|
|
@@ -18,18 +17,10 @@ class ContactFolderCollection(DeltaCollection[ContactFolder]):
|
|
|
18
17
|
return super().add(**kwargs)
|
|
19
18
|
|
|
20
19
|
def ensure(self, display_name: str) -> ContactFolder:
|
|
21
|
-
"""Gets existing
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if not isinstance(error, DuplicatedObjectException):
|
|
26
|
-
raise error
|
|
27
|
-
self.get_by_name(display_name).after_execute(
|
|
28
|
-
lambda existing: return_type.copy_from(existing), execute_first=True
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return_type.on_error(_on_name_exists)
|
|
32
|
-
return return_type
|
|
20
|
+
"""Gets an existing folder by name or creates it (idempotent)."""
|
|
21
|
+
from office365.runtime.queries.get_or_create import create_or_get
|
|
22
|
+
|
|
23
|
+
return create_or_get(create=lambda: self.add(display_name), find=lambda: self.get_by_name(display_name))
|
|
33
24
|
|
|
34
25
|
def get_by_name(self, display_name: str) -> ContactFolder:
|
|
35
26
|
"""Returns the group with the specified name."""
|
|
@@ -381,6 +381,11 @@ class AuthenticationContext:
|
|
|
381
381
|
self._authenticate = provider.authenticate_request
|
|
382
382
|
return self
|
|
383
383
|
|
|
384
|
+
@property
|
|
385
|
+
def is_configured(self) -> bool:
|
|
386
|
+
"""Whether an authentication provider has been set on this context."""
|
|
387
|
+
return self._authenticate is not None
|
|
388
|
+
|
|
384
389
|
def authenticate_request(self, request: RequestOptions) -> None:
|
|
385
390
|
"""Authenticate the HTTP request
|
|
386
391
|
|
|
@@ -7,6 +7,7 @@ from typing import (
|
|
|
7
7
|
Dict,
|
|
8
8
|
List,
|
|
9
9
|
Optional,
|
|
10
|
+
Tuple,
|
|
10
11
|
TypeVar,
|
|
11
12
|
)
|
|
12
13
|
|
|
@@ -16,6 +17,7 @@ from office365.runtime.client_request_exception import ClientRequestException
|
|
|
16
17
|
from office365.runtime.client_runtime_context import ClientRuntimeContext
|
|
17
18
|
from office365.runtime.converters.value import _add_type_metadata, declared_type, deserialize_value, serialize_value
|
|
18
19
|
from office365.runtime.http.request_options import RequestOptions
|
|
20
|
+
from office365.runtime.limits import Limit, LimitDecl, collect_class_limits, collect_limit_meta
|
|
19
21
|
from office365.runtime.odata.json_format import ODataJsonFormat
|
|
20
22
|
from office365.runtime.odata.query_options import QueryOptions
|
|
21
23
|
from office365.runtime.paths.resource_path import ResourcePath
|
|
@@ -32,6 +34,8 @@ class ClientObject:
|
|
|
32
34
|
"""Base client object which defines named properties and relationships of an entity."""
|
|
33
35
|
|
|
34
36
|
_odata_meta: dict[str, ODataPropertyMeta] = {}
|
|
37
|
+
_limit_meta: dict[str, Tuple[LimitDecl, ...]] = {}
|
|
38
|
+
_class_limit_decls: Tuple[LimitDecl, ...] = ()
|
|
35
39
|
|
|
36
40
|
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
37
41
|
super().__init_subclass__(**kwargs)
|
|
@@ -43,6 +47,8 @@ class ClientObject:
|
|
|
43
47
|
m.attr = attr_name
|
|
44
48
|
meta[m.name] = m
|
|
45
49
|
cls._odata_meta = meta
|
|
50
|
+
cls._limit_meta = collect_limit_meta(cls)
|
|
51
|
+
cls._class_limit_decls = collect_class_limits(cls)
|
|
46
52
|
|
|
47
53
|
def __init__(
|
|
48
54
|
self,
|
|
@@ -114,6 +120,13 @@ class ClientObject:
|
|
|
114
120
|
"""Names of properties marked ``@odata(persist=True)``."""
|
|
115
121
|
return {name for name, m in type(self)._odata_meta.items() if m.persist}
|
|
116
122
|
|
|
123
|
+
@classmethod
|
|
124
|
+
def declared_limits(cls) -> Tuple[Limit, ...]:
|
|
125
|
+
"""The limits declared on this class and its methods/properties (``@limit``)."""
|
|
126
|
+
class_limits = tuple(decl.limit for decl in cls._class_limit_decls)
|
|
127
|
+
member_limits = tuple(decl.limit for decls in cls._limit_meta.values() for decl in decls)
|
|
128
|
+
return (*class_limits, *member_limits)
|
|
129
|
+
|
|
117
130
|
def execute_query(self) -> Self:
|
|
118
131
|
"""
|
|
119
132
|
Submits all pending requests to the server.
|
|
@@ -133,6 +146,7 @@ class ClientObject:
|
|
|
133
146
|
success_callback=None,
|
|
134
147
|
failure_callback=None,
|
|
135
148
|
exceptions=(ClientRequestException,),
|
|
149
|
+
is_retriable=None,
|
|
136
150
|
) -> Self:
|
|
137
151
|
"""Executes the current set of data retrieval queries and method invocations and retries it if needed.
|
|
138
152
|
|
|
@@ -144,6 +158,9 @@ class ClientObject:
|
|
|
144
158
|
success_callback (callable): A callback to call if the request executes successfully.
|
|
145
159
|
failure_callback (callable): A callback to call if the request fails to execute.
|
|
146
160
|
exceptions (tuple): Tuple of exceptions that we retry.
|
|
161
|
+
is_retriable (callable): Optional predicate deciding whether a caught
|
|
162
|
+
exception is retried. Defaults to transient errors; pass
|
|
163
|
+
``retry_on(...)`` to also retry otherwise-permanent errors.
|
|
147
164
|
"""
|
|
148
165
|
self.context.execute_query_retry(
|
|
149
166
|
max_retry=max_retry,
|
|
@@ -153,6 +170,7 @@ class ClientObject:
|
|
|
153
170
|
success_callback=success_callback,
|
|
154
171
|
failure_callback=failure_callback,
|
|
155
172
|
exceptions=exceptions,
|
|
173
|
+
is_retriable=is_retriable,
|
|
156
174
|
)
|
|
157
175
|
return self
|
|
158
176
|
|