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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Typed field value coercion for record imports.
|
|
2
|
+
|
|
3
|
+
Maps plain source values (CSV/DataFrame cells) to the payload shapes SharePoint
|
|
4
|
+
expects for typed fields, driven by an explicit ``schema={column: FieldType}``.
|
|
5
|
+
``List.from_records(..., schema=...)`` (and the streaming conveniences) applies
|
|
6
|
+
this, so typed columns (choice, lookup, user, URL, geolocation, ...) can be
|
|
7
|
+
populated without hand-building payloads:
|
|
8
|
+
|
|
9
|
+
lst.from_dataframe(df, schema={"Status": FieldType.Choice, "Tags": FieldType.MultiChoice})
|
|
10
|
+
|
|
11
|
+
Values already in the expected shape (a ``ClientValue``) pass through unchanged.
|
|
12
|
+
``None`` values and unknown/``None`` field types are left as-is, so this is safe
|
|
13
|
+
to apply to every cell of a record.
|
|
14
|
+
|
|
15
|
+
SharePoint has no distinct field-type kind for multi-valued lookups/users (they
|
|
16
|
+
are ``Lookup``/``User`` fields with ``AllowMultipleValues``), so the value's
|
|
17
|
+
shape decides: a list, tuple or ``"; "``-separated string becomes the multi form.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Iterable, Mapping
|
|
23
|
+
from typing import Any, Callable, Dict, Optional
|
|
24
|
+
|
|
25
|
+
from office365.runtime.client_value import ClientValue
|
|
26
|
+
from office365.runtime.converters.scalars import parse_bool, parse_datetime, try_float, try_int
|
|
27
|
+
from office365.sharepoint.fields.geolocation_value import FieldGeolocationValue
|
|
28
|
+
from office365.sharepoint.fields.lookup_value import FieldLookupValue
|
|
29
|
+
from office365.sharepoint.fields.multi_choice_value import FieldMultiChoiceValue
|
|
30
|
+
from office365.sharepoint.fields.multi_lookup_value import FieldMultiLookupValue
|
|
31
|
+
from office365.sharepoint.fields.multi_user_value import FieldMultiUserValue
|
|
32
|
+
from office365.sharepoint.fields.type import FieldType
|
|
33
|
+
from office365.sharepoint.fields.url_value import FieldUrlValue
|
|
34
|
+
from office365.sharepoint.fields.user_value import FieldUserValue
|
|
35
|
+
|
|
36
|
+
_LIST_SEPARATOR = ";"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _as_list(value: Any) -> list[Any]:
|
|
40
|
+
"""Normalize a value into a list (a ``"; "`` string is split)."""
|
|
41
|
+
if isinstance(value, str):
|
|
42
|
+
return [part.strip() for part in value.split(_LIST_SEPARATOR) if part.strip()]
|
|
43
|
+
if isinstance(value, Iterable):
|
|
44
|
+
return list(value)
|
|
45
|
+
return [value]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_multi(value: Any) -> bool:
|
|
49
|
+
"""Whether a value should map to a multi-valued field."""
|
|
50
|
+
return isinstance(value, (list, tuple)) or (isinstance(value, str) and _LIST_SEPARATOR in value)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _lookup_id(value: Any) -> int:
|
|
54
|
+
"""Read a lookup id from a mapping (``LookupId``/``Id``) or a scalar."""
|
|
55
|
+
if isinstance(value, Mapping):
|
|
56
|
+
value = value.get("LookupId", value.get("Id", value.get("id")))
|
|
57
|
+
return int(value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _lookup_value(value: Any) -> Any:
|
|
61
|
+
if _is_multi(value):
|
|
62
|
+
return FieldMultiLookupValue([FieldLookupValue(LookupId=_lookup_id(item)) for item in _as_list(value)])
|
|
63
|
+
return FieldLookupValue(LookupId=_lookup_id(value))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _user_value(value: Any) -> FieldUserValue:
|
|
67
|
+
if isinstance(value, Mapping):
|
|
68
|
+
has_id = value.get("LookupId") is not None or value.get("Id") is not None
|
|
69
|
+
return FieldUserValue(
|
|
70
|
+
LookupId=_lookup_id(value) if has_id else None,
|
|
71
|
+
LookupValue=value.get("LookupValue"),
|
|
72
|
+
Email=value.get("Email", value.get("email")),
|
|
73
|
+
)
|
|
74
|
+
if isinstance(value, str) and "@" in value:
|
|
75
|
+
return FieldUserValue(Email=value)
|
|
76
|
+
return FieldUserValue(LookupId=_lookup_id(value))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _user(value: Any) -> Any:
|
|
80
|
+
if _is_multi(value):
|
|
81
|
+
return FieldMultiUserValue([_user_value(item) for item in _as_list(value)])
|
|
82
|
+
return _user_value(value)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _url(value: Any) -> FieldUrlValue:
|
|
86
|
+
if isinstance(value, Mapping):
|
|
87
|
+
return FieldUrlValue(Url=value.get("Url"), Description=value.get("Description"))
|
|
88
|
+
if isinstance(value, (tuple, list)):
|
|
89
|
+
return FieldUrlValue(Url=value[0], Description=value[1] if len(value) > 1 else None)
|
|
90
|
+
return FieldUrlValue(Url=str(value))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _geolocation(value: Any) -> FieldGeolocationValue:
|
|
94
|
+
if isinstance(value, Mapping):
|
|
95
|
+
return FieldGeolocationValue(Latitude=float(value["Latitude"]), Longitude=float(value["Longitude"]))
|
|
96
|
+
if isinstance(value, (tuple, list)) and len(value) > 1:
|
|
97
|
+
return FieldGeolocationValue(Latitude=float(value[0]), Longitude=float(value[1]))
|
|
98
|
+
latitude, _, longitude = str(value).partition(",")
|
|
99
|
+
return FieldGeolocationValue(Latitude=float(latitude), Longitude=float(longitude))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _datetime(value: Any) -> Any:
|
|
103
|
+
return parse_datetime(value) or value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_CONVERTERS: Dict[FieldType, Callable[[Any], Any]] = {
|
|
107
|
+
FieldType.Boolean: parse_bool,
|
|
108
|
+
FieldType.Integer: try_int,
|
|
109
|
+
FieldType.Counter: try_int,
|
|
110
|
+
FieldType.Number: try_float,
|
|
111
|
+
FieldType.Currency: try_float,
|
|
112
|
+
FieldType.DateTime: _datetime,
|
|
113
|
+
FieldType.MultiChoice: lambda value: FieldMultiChoiceValue([str(item) for item in _as_list(value)]),
|
|
114
|
+
FieldType.Lookup: _lookup_value,
|
|
115
|
+
FieldType.User: _user,
|
|
116
|
+
FieldType.URL: _url,
|
|
117
|
+
FieldType.Geolocation: _geolocation,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def coerce_field_value(field_type: Optional[FieldType], value: Any) -> Any:
|
|
122
|
+
"""Coerce a plain value into the payload shape for ``field_type``.
|
|
123
|
+
|
|
124
|
+
Supported: Boolean, Integer, Number/Currency, DateTime, MultiChoice, Lookup
|
|
125
|
+
(single/multi), User (single/multi), URL and Geolocation. Anything else (and
|
|
126
|
+
already-typed ``ClientValue`` inputs) passes through unchanged.
|
|
127
|
+
"""
|
|
128
|
+
if field_type is None or value is None or isinstance(value, ClientValue):
|
|
129
|
+
return value
|
|
130
|
+
converter = _CONVERTERS.get(field_type)
|
|
131
|
+
return converter(value) if converter is not None else value
|
|
@@ -2,12 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, cast
|
|
4
4
|
|
|
5
|
-
from typing_extensions import Self
|
|
6
|
-
|
|
7
|
-
from office365.runtime.client_request_exception import (
|
|
8
|
-
ClientRequestException,
|
|
9
|
-
ObjectNotFoundException,
|
|
10
|
-
)
|
|
11
5
|
from office365.runtime.paths.service_operation import ServiceOperationPath
|
|
12
6
|
from office365.runtime.queries.create_entity import CreateEntityQuery
|
|
13
7
|
from office365.runtime.queries.service_operation import ServiceOperationQuery
|
|
@@ -42,8 +36,14 @@ _FIELD_TYPE_BY_KIND = {
|
|
|
42
36
|
|
|
43
37
|
|
|
44
38
|
def field_type_from_kind(kind: str) -> FieldType:
|
|
45
|
-
"""Map a generic pandas dtype kind (see ``series_kind``) to a SharePoint ``FieldType``.
|
|
46
|
-
|
|
39
|
+
"""Map a generic pandas dtype kind (see ``series_kind``) to a SharePoint ``FieldType``.
|
|
40
|
+
|
|
41
|
+
Unknown kinds fall back to ``FieldType.Text``. Only the four basic column
|
|
42
|
+
types can be inferred from a pandas dtype; richer SharePoint types (Choice,
|
|
43
|
+
Lookup, User, URL, Currency, MultiLineText, ...) require an explicit
|
|
44
|
+
``schema`` passed to ``List.from_dataframe``.
|
|
45
|
+
"""
|
|
46
|
+
return _FIELD_TYPE_BY_KIND.get(kind, FieldType.Text)
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
if TYPE_CHECKING:
|
|
@@ -57,59 +57,76 @@ class FieldCollection(EntityCollection[Field]):
|
|
|
57
57
|
def __init__(self, context, resource_path=None, parent=None):
|
|
58
58
|
super().__init__(context, Field, resource_path, parent)
|
|
59
59
|
|
|
60
|
-
def
|
|
60
|
+
def ensure_from_dataframe(self, df, progress=None, *, on_conflict: str = "skip") -> list[Field]:
|
|
61
61
|
"""Define a field per DataFrame column, inferring the field type.
|
|
62
62
|
|
|
63
|
-
The schema counterpart of ``
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
``execute_query()``:
|
|
63
|
+
The schema counterpart of ``RecordCollection.from_dataframe`` (which
|
|
64
|
+
imports rows): here each column becomes a field definition. Column names
|
|
65
|
+
are sanitized into field internal names and the ``FieldType`` is inferred
|
|
66
|
+
from the pandas dtype (Boolean/DateTime/Number/Text). Each field is
|
|
67
|
+
ensured idempotently (looked up first, created when missing). Deferred —
|
|
68
|
+
run the lookups/creates with ``execute_query()``.
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
A column whose title collides with a built-in field (e.g. ``Name`` ->
|
|
71
|
+
``FileLeafRef``) is suffixed with ``_`` and a warning is emitted, since
|
|
72
|
+
SharePoint cannot create a second column with that display name.
|
|
72
73
|
|
|
73
74
|
Requires ``pip install office365-rest-python-client[pandas]``.
|
|
74
75
|
|
|
75
76
|
Args:
|
|
76
77
|
df: A pandas DataFrame whose columns become the field definitions.
|
|
78
|
+
progress: Reserved (kept for signature parity).
|
|
79
|
+
on_conflict: ``"skip"`` (default) or ``"update"``.
|
|
77
80
|
|
|
78
81
|
Returns:
|
|
79
|
-
|
|
82
|
+
list[Field]: The ensured fields.
|
|
80
83
|
"""
|
|
84
|
+
import warnings
|
|
85
|
+
|
|
81
86
|
from office365.runtime.converters.dataframe import require_pandas, series_kind
|
|
82
|
-
from office365.sharepoint.fields.name import internal_field_name
|
|
87
|
+
from office365.sharepoint.fields.name import internal_field_name, is_reserved_field_title
|
|
83
88
|
|
|
84
89
|
pd = require_pandas()
|
|
90
|
+
fields: list[Field] = []
|
|
85
91
|
for column in df.columns:
|
|
92
|
+
if is_reserved_field_title(str(column)):
|
|
93
|
+
warnings.warn(
|
|
94
|
+
f"Column '{column}' collides with a built-in SharePoint field; "
|
|
95
|
+
f"importing it as '{internal_field_name(str(column))}'.",
|
|
96
|
+
stacklevel=2,
|
|
97
|
+
)
|
|
86
98
|
field_type = field_type_from_kind(series_kind(pd, df[column]))
|
|
87
99
|
info = FieldCreationInformation(
|
|
88
100
|
Title=internal_field_name(str(column)),
|
|
89
101
|
FieldTypeKind=field_type,
|
|
90
102
|
)
|
|
91
|
-
self.ensure(info)
|
|
92
|
-
return
|
|
103
|
+
fields.append(self.ensure(info, on_conflict=on_conflict))
|
|
104
|
+
return fields
|
|
93
105
|
|
|
94
|
-
def ensure(self, parameters: FieldCreationInformation) -> Field:
|
|
95
|
-
|
|
106
|
+
def ensure(self, parameters: FieldCreationInformation, *, on_conflict: str = "skip") -> Field:
|
|
107
|
+
"""Ensure the field exists (get-or-create); ``on_conflict="update"`` reconciles it."""
|
|
108
|
+
from office365.runtime.queries.get_or_create import get_or_create
|
|
96
109
|
|
|
97
110
|
return_type = Field(self.context)
|
|
98
|
-
barrier = DeferredOperationQuery(self.context, return_type=return_type)
|
|
99
|
-
|
|
100
|
-
def _on_success(existing):
|
|
101
|
-
return_type.copy_from(existing)
|
|
102
|
-
barrier.resolve()
|
|
103
111
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
def _reconcile(field: Field) -> None:
|
|
113
|
+
changed = False
|
|
114
|
+
if parameters.FieldTypeKind is not None and field.field_type_kind != parameters.FieldTypeKind:
|
|
115
|
+
field.set_property("FieldTypeKind", parameters.FieldTypeKind)
|
|
116
|
+
changed = True
|
|
117
|
+
if parameters.Description is not None and field.properties.get("Description") != parameters.Description:
|
|
118
|
+
field.set_property("Description", parameters.Description)
|
|
119
|
+
changed = True
|
|
120
|
+
if changed:
|
|
121
|
+
field.update()
|
|
122
|
+
|
|
123
|
+
return get_or_create(
|
|
124
|
+
find=lambda: self.get_by_title(parameters.Title).get(),
|
|
125
|
+
create_query=lambda: self._build_add_field_query(parameters, return_type),
|
|
126
|
+
return_type=return_type,
|
|
127
|
+
on_conflict=on_conflict,
|
|
128
|
+
reconcile=_reconcile,
|
|
129
|
+
)
|
|
113
130
|
|
|
114
131
|
def add_calculated(self, title: str, formula: str, description: Optional[str] = None) -> FieldCalculated:
|
|
115
132
|
"""Creates a Calculated field
|
|
@@ -279,6 +279,23 @@ class Field(Entity):
|
|
|
279
279
|
def indexed(self):
|
|
280
280
|
return self.properties.get("Indexed", None)
|
|
281
281
|
|
|
282
|
+
def ensure_indexed(self) -> Self:
|
|
283
|
+
"""Ensure this field is indexed (idempotent, deferred).
|
|
284
|
+
|
|
285
|
+
Indexing a column lets queries filter/sort on it past the 5,000-item list
|
|
286
|
+
view threshold. Run with ``execute_query()``.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
def _enable(_=None) -> None:
|
|
290
|
+
if self.indexed is not True:
|
|
291
|
+
self.enable_index()
|
|
292
|
+
|
|
293
|
+
if self.indexed is None:
|
|
294
|
+
self.ensure_property("Indexed").after_execute(_enable)
|
|
295
|
+
else:
|
|
296
|
+
_enable()
|
|
297
|
+
return self
|
|
298
|
+
|
|
282
299
|
@property
|
|
283
300
|
def type_display_name(self) -> Optional[str]:
|
|
284
301
|
"""
|
|
@@ -2,10 +2,54 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
# Built-in SharePoint field titles that collide with a user column of the same
|
|
6
|
+
# name. ``FieldCollection.ensure`` looks fields up by *display name*
|
|
7
|
+
# (``getByTitle``), so a column titled e.g. "Name" resolves to the built-in
|
|
8
|
+
# ``FileLeafRef`` field and is never created as a custom column. Such titles are
|
|
9
|
+
# suffixed with ``_`` so the column can be imported (and the item keys stay
|
|
10
|
+
# consistent, since both field creation and record keys use this mapping).
|
|
11
|
+
_RESERVED_FIELD_TITLES = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"name", # FileLeafRef
|
|
14
|
+
"title",
|
|
15
|
+
"id",
|
|
16
|
+
"created",
|
|
17
|
+
"modified",
|
|
18
|
+
"author",
|
|
19
|
+
"editor",
|
|
20
|
+
"attachments",
|
|
21
|
+
"contenttype",
|
|
22
|
+
"contenttypeid",
|
|
23
|
+
"version",
|
|
24
|
+
"uniqueid",
|
|
25
|
+
"fileleafref",
|
|
26
|
+
"fileref",
|
|
27
|
+
"filedirref",
|
|
28
|
+
"fsobjtype",
|
|
29
|
+
"owshiddenversion",
|
|
30
|
+
"workflowversion",
|
|
31
|
+
"metainfo",
|
|
32
|
+
"scopeid",
|
|
33
|
+
"instanceid",
|
|
34
|
+
"permask",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
5
38
|
|
|
6
39
|
def internal_field_name(name: str) -> str:
|
|
7
40
|
"""Sanitize a display/column name into a SharePoint field internal name.
|
|
8
41
|
|
|
9
|
-
SharePoint field internal names cannot contain spaces or punctuation
|
|
42
|
+
SharePoint field internal names cannot contain spaces or punctuation, and a
|
|
43
|
+
title that collides with a built-in field's display name (e.g. ``Name`` ->
|
|
44
|
+
``FileLeafRef``) cannot be created as a custom column — those are suffixed
|
|
45
|
+
with ``_`` so the import still works.
|
|
10
46
|
"""
|
|
11
|
-
|
|
47
|
+
sanitized = re.sub(r"\W", "_", name)
|
|
48
|
+
if sanitized.lower() in _RESERVED_FIELD_TITLES:
|
|
49
|
+
return f"{sanitized}_"
|
|
50
|
+
return sanitized
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_reserved_field_title(name: str) -> bool:
|
|
54
|
+
"""Whether a column title collides with a built-in SharePoint field title."""
|
|
55
|
+
return re.sub(r"\W", "_", name).lower() in _RESERVED_FIELD_TITLES
|
|
@@ -17,12 +17,13 @@ from office365.sharepoint.files.creation_information import FileCreationInformat
|
|
|
17
17
|
from office365.sharepoint.files.file import File
|
|
18
18
|
from office365.sharepoint.files.publish.status import FileStatus
|
|
19
19
|
from office365.sharepoint.pages.template_file_type import TemplateFileType
|
|
20
|
+
from office365.sharepoint.thresholds import LIST_VIEW_THRESHOLD, Limits, ensure_within, limit
|
|
20
21
|
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
23
24
|
from office365.sharepoint.folders.folder import Folder
|
|
24
25
|
|
|
25
|
-
_DEFAULT_CHUNK_SIZE =
|
|
26
|
+
_DEFAULT_CHUNK_SIZE = Limits.UPLOAD_SESSION_CHUNK.value # simple-upload threshold / upload-session chunk
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _stream_size(stream: IO) -> int:
|
|
@@ -47,6 +48,9 @@ def _stream_size(stream: IO) -> int:
|
|
|
47
48
|
class FileCollection(EntityCollection[File]):
|
|
48
49
|
"""Represents a collection of File resources."""
|
|
49
50
|
|
|
51
|
+
_list_view_threshold = LIST_VIEW_THRESHOLD
|
|
52
|
+
_truncation_hint = "page it with Folder.get_files(page_size=2000) or files.get_all(page_size=2000)"
|
|
53
|
+
|
|
50
54
|
def __init__(
|
|
51
55
|
self,
|
|
52
56
|
context: ClientContext,
|
|
@@ -82,6 +86,7 @@ class FileCollection(EntityCollection[File]):
|
|
|
82
86
|
content = path_or_file.read()
|
|
83
87
|
return self.add(name, content, True)
|
|
84
88
|
|
|
89
|
+
@limit(Limits.FILE_UPLOAD)
|
|
85
90
|
def upload_content(
|
|
86
91
|
self,
|
|
87
92
|
content: bytes,
|
|
@@ -103,6 +108,7 @@ class FileCollection(EntityCollection[File]):
|
|
|
103
108
|
(``done``/``total`` in bytes; per chunk for session uploads, once
|
|
104
109
|
after the upload completes for the simple path).
|
|
105
110
|
"""
|
|
111
|
+
ensure_within(Limits.FILE_UPLOAD, len(content), context=f"file '{file_name}'")
|
|
106
112
|
if len(content) <= chunk_size:
|
|
107
113
|
file = self.upload(io.BytesIO(content), file_name)
|
|
108
114
|
if callable(progress):
|
|
@@ -49,6 +49,7 @@ from office365.sharepoint.webparts.limited_manager import LimitedWebPartManager
|
|
|
49
49
|
from office365.sharepoint.webparts.personalization_scope import PersonalizationScope
|
|
50
50
|
|
|
51
51
|
if TYPE_CHECKING:
|
|
52
|
+
from office365.runtime.converters.dataframe import DataFrameResult
|
|
52
53
|
from office365.sharepoint.client_context import ClientContext
|
|
53
54
|
|
|
54
55
|
|
|
@@ -145,6 +146,37 @@ class File(AbstractFile):
|
|
|
145
146
|
self.context.add_query(qry)
|
|
146
147
|
return return_type
|
|
147
148
|
|
|
149
|
+
def read_dataframe(self, *, format: str = "csv", **opts) -> "DataFrameResult": # noqa: A002
|
|
150
|
+
"""Read this file's **content** into a pandas DataFrame (deferred result).
|
|
151
|
+
|
|
152
|
+
Parses the file content (CSV/XLSX/JSON/Parquet) — it does **not** export
|
|
153
|
+
the file's metadata; those are plain properties (``file.name``,
|
|
154
|
+
``file.length``, ``file.time_last_modified``, ...). Run with
|
|
155
|
+
``execute_query()`` and read ``.value``:
|
|
156
|
+
|
|
157
|
+
>>> df = file.read_dataframe().execute_query().value
|
|
158
|
+
"""
|
|
159
|
+
from office365.runtime.converters.dataframe import DataFrameResult, dataframe_from_bytes
|
|
160
|
+
|
|
161
|
+
result = DataFrameResult(self.context)
|
|
162
|
+
|
|
163
|
+
def _convert(return_type: ClientResult[bytes]) -> None:
|
|
164
|
+
result.set_property("__value", dataframe_from_bytes(return_type.value, format, **opts))
|
|
165
|
+
|
|
166
|
+
self.get_content().after_execute(_convert)
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
def write_dataframe(self, df, *, format: str = "csv", index: bool = False, **opts) -> Self: # noqa: A002
|
|
170
|
+
"""Write a pandas DataFrame to this file's **content** (deferred).
|
|
171
|
+
|
|
172
|
+
Serializes the frame (CSV is UTF-8 with a BOM) and queues a
|
|
173
|
+
``SaveBinaryStream``; run with ``execute_query()``. This replaces the
|
|
174
|
+
file content, not its metadata.
|
|
175
|
+
"""
|
|
176
|
+
from office365.runtime.converters.dataframe import dataframe_to_bytes
|
|
177
|
+
|
|
178
|
+
return self.save_binary_stream(dataframe_to_bytes(df, format, index, **opts))
|
|
179
|
+
|
|
148
180
|
def get_exists(self) -> ClientResult[bool]:
|
|
149
181
|
result = ClientResult(self.context, bool())
|
|
150
182
|
result.set_property("__value", False)
|
|
@@ -272,6 +304,22 @@ class File(AbstractFile):
|
|
|
272
304
|
self.context.add_query(qry)
|
|
273
305
|
return return_type
|
|
274
306
|
|
|
307
|
+
def delete_object(self, bypass_shared_lock: bool = False) -> Self:
|
|
308
|
+
"""Deletes the file.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
bypass_shared_lock: Send ``Prefer: bypass-shared-lock`` so the delete
|
|
312
|
+
succeeds even when the file is open for editing. Without it the
|
|
313
|
+
server rejects the request with
|
|
314
|
+
:class:`~office365.runtime.exceptions.FileLockedException` (HTTP 423).
|
|
315
|
+
"""
|
|
316
|
+
super().delete_object()
|
|
317
|
+
if bypass_shared_lock:
|
|
318
|
+
from office365.runtime.http.prefer import prefer_bypass_shared_lock
|
|
319
|
+
|
|
320
|
+
self.context.before_execute(prefer_bypass_shared_lock, once=True)
|
|
321
|
+
return self
|
|
322
|
+
|
|
275
323
|
def recycle(self) -> ClientResult[str]:
|
|
276
324
|
"""Moves the file to the Recycle Bin and returns the identifier of the new Recycle Bin item."""
|
|
277
325
|
return_type = ClientResult(self.context, str())
|
|
@@ -686,10 +734,9 @@ class File(AbstractFile):
|
|
|
686
734
|
# OData literals escape embedded single quotes by doubling them (' -> '')
|
|
687
735
|
decoded_server_relative_url = decoded.replace("'", "''")
|
|
688
736
|
|
|
689
|
-
url =
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
safe=":/",
|
|
737
|
+
url = (
|
|
738
|
+
f"{context.service_root_url}/web/getFileByServerRelativePath"
|
|
739
|
+
f"(DecodedUrl='{quote(decoded_server_relative_url)}')/$value"
|
|
693
740
|
)
|
|
694
741
|
|
|
695
742
|
request = RequestOptions(url)
|
|
@@ -711,10 +758,9 @@ class File(AbstractFile):
|
|
|
711
758
|
# OData literals escape embedded single quotes by doubling them (' -> '')
|
|
712
759
|
decoded_server_relative_url = decoded.replace("'", "''")
|
|
713
760
|
|
|
714
|
-
url =
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
safe=":/",
|
|
761
|
+
url = (
|
|
762
|
+
f"{context.service_root_url}/web/getFileByServerRelativePath"
|
|
763
|
+
f"(DecodedUrl='{quote(decoded_server_relative_url)}')/$value"
|
|
718
764
|
)
|
|
719
765
|
|
|
720
766
|
request = RequestOptions(url)
|
|
@@ -5,7 +5,6 @@ from typing_extensions import Self
|
|
|
5
5
|
|
|
6
6
|
from office365.runtime.client_result import ClientResult
|
|
7
7
|
from office365.runtime.paths.resource_path import ResourcePath
|
|
8
|
-
from office365.runtime.paths.v3.entity import EntityPath
|
|
9
8
|
from office365.runtime.queries.service_operation import ServiceOperationQuery
|
|
10
9
|
from office365.runtime.types.odata_property import odata
|
|
11
10
|
from office365.sharepoint.entity import Entity
|
|
@@ -57,10 +56,7 @@ class FileVersion(Entity):
|
|
|
57
56
|
"""Gets the user that created the file version."""
|
|
58
57
|
from office365.sharepoint.principal.users.user import User
|
|
59
58
|
|
|
60
|
-
return self.properties.get(
|
|
61
|
-
"CreatedBy",
|
|
62
|
-
User(self.context, ResourcePath("CreatedBy", self.resource_path)),
|
|
63
|
-
)
|
|
59
|
+
return self.properties.get("CreatedBy", User(self.context, ResourcePath("CreatedBy", self.resource_path)))
|
|
64
60
|
|
|
65
61
|
@property
|
|
66
62
|
def id(self) -> Optional[int]:
|
|
@@ -87,12 +83,41 @@ class FileVersion(Entity):
|
|
|
87
83
|
"""Gets a value that specifies the check-in comment."""
|
|
88
84
|
return self.properties.get("CheckInComment", None)
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
@property
|
|
87
|
+
def check_in_comment(self) -> Optional[str]:
|
|
88
|
+
"""Gets the CheckInComment property"""
|
|
89
|
+
return self.properties.get("CheckInComment", None)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def expiration_date(self) -> Optional[str]:
|
|
93
|
+
"""Gets the ExpirationDate property"""
|
|
94
|
+
return self.properties.get("ExpirationDate", None)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def length(self) -> Optional[int]:
|
|
98
|
+
"""Gets the Length property"""
|
|
99
|
+
return self.properties.get("Length", None)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def size(self) -> Optional[int]:
|
|
103
|
+
"""Gets the Size property"""
|
|
104
|
+
return self.properties.get("Size", None)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def snapshot_date(self) -> Optional[str]:
|
|
108
|
+
"""Gets the SnapshotDate property"""
|
|
109
|
+
return self.properties.get("SnapshotDate", None)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def property_ref_name(self) -> str:
|
|
113
|
+
return "ID"
|
|
114
|
+
|
|
115
|
+
def set_expiration_date(self, expiration_date: datetime) -> Self:
|
|
116
|
+
"""SetExpirationDate operation.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
expiration_date (datetime): expirationDate parameter
|
|
120
|
+
"""
|
|
121
|
+
qry = ServiceOperationQuery(self, "SetExpirationDate", None, {"expirationDate": expiration_date}, None, None)
|
|
122
|
+
self.context.add_query(qry)
|
|
98
123
|
return self
|
|
@@ -10,12 +10,16 @@ from office365.sharepoint.folders.coloring_information import (
|
|
|
10
10
|
)
|
|
11
11
|
from office365.sharepoint.folders.colors import FolderColors
|
|
12
12
|
from office365.sharepoint.folders.folder import Folder
|
|
13
|
+
from office365.sharepoint.thresholds import LIST_VIEW_THRESHOLD
|
|
13
14
|
from office365.sharepoint.types.resource_path import ResourcePath as SPResPath
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class FolderCollection(EntityCollection[Folder]):
|
|
17
18
|
"""Represents a collection of Folder resources."""
|
|
18
19
|
|
|
20
|
+
_list_view_threshold = LIST_VIEW_THRESHOLD
|
|
21
|
+
_truncation_hint = "page it with folders.get_all(page_size=2000)"
|
|
22
|
+
|
|
19
23
|
def __init__(self, context, resource_path=None, parent=None):
|
|
20
24
|
super().__init__(context, Folder, resource_path, parent)
|
|
21
25
|
|