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,424 @@
|
|
|
1
|
+
"""Typed CAML query expressions.
|
|
2
|
+
|
|
3
|
+
Build a ``<Where>`` clause from composable Python objects instead of raw CAML
|
|
4
|
+
strings. Every ``And``/``Or`` node holds **exactly two** children, so any
|
|
5
|
+
combination renders valid, binary-nested CAML (CAML forbids 3+ children):
|
|
6
|
+
|
|
7
|
+
from office365.sharepoint.listitems.caml import Caml
|
|
8
|
+
|
|
9
|
+
where = (
|
|
10
|
+
Caml.text("Email").eq("support@google.com")
|
|
11
|
+
.or_(Caml.text("Email").eq("plus@google.com"))
|
|
12
|
+
.or_(Caml.text("Title").begins_with("[Google]"))
|
|
13
|
+
)
|
|
14
|
+
# identical alternatives:
|
|
15
|
+
where = Caml.or_(a, b, c) # variadic -> left-folded binary nesting
|
|
16
|
+
where = a | b | c # operators
|
|
17
|
+
|
|
18
|
+
Logical operators are ``&`` (And), ``|`` (Or), ``~`` (Not); the method forms
|
|
19
|
+
``.and_()/.or_()/.not_()`` read closer to CAML. Python's ``and``/``or``/``not``
|
|
20
|
+
cannot be used (they are keywords) and are rejected at runtime via ``__bool__``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
|
+
from typing import Any, Callable, Iterable, Optional, TypeVar
|
|
27
|
+
|
|
28
|
+
from office365.sharepoint.listitems.caml.fields import FieldRef
|
|
29
|
+
from office365.sharepoint.listitems.caml.values import Value, ValueType, list_property
|
|
30
|
+
from office365.sharepoint.listitems.caml.values import month as _month
|
|
31
|
+
from office365.sharepoint.listitems.caml.values import now as _now
|
|
32
|
+
from office365.sharepoint.listitems.caml.values import today as _today
|
|
33
|
+
from office365.sharepoint.listitems.caml.values import user_id as _user_id
|
|
34
|
+
|
|
35
|
+
_T = TypeVar("_T")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CamlExpr(ABC):
|
|
39
|
+
"""Base class for CAML query expressions."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def to_xml(self) -> str:
|
|
43
|
+
"""Render this expression as CAML XML."""
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def field_refs(self) -> set[str]:
|
|
47
|
+
"""The field names referenced by this expression."""
|
|
48
|
+
return set()
|
|
49
|
+
|
|
50
|
+
def __and__(self, other: "CamlExpr") -> "And":
|
|
51
|
+
return And(self, other)
|
|
52
|
+
|
|
53
|
+
def __or__(self, other: "CamlExpr") -> "Or":
|
|
54
|
+
return Or(self, other)
|
|
55
|
+
|
|
56
|
+
def __invert__(self) -> "Not":
|
|
57
|
+
return Not(self)
|
|
58
|
+
|
|
59
|
+
def and_(self, other: "CamlExpr") -> "And":
|
|
60
|
+
"""Combine with ``other`` using ``<And>`` (same as ``&``)."""
|
|
61
|
+
return And(self, other)
|
|
62
|
+
|
|
63
|
+
def or_(self, other: "CamlExpr") -> "Or":
|
|
64
|
+
"""Combine with ``other`` using ``<Or>`` (same as ``|``)."""
|
|
65
|
+
return Or(self, other)
|
|
66
|
+
|
|
67
|
+
def not_(self) -> "Not":
|
|
68
|
+
"""Negate this expression (same as ``~``)."""
|
|
69
|
+
return Not(self)
|
|
70
|
+
|
|
71
|
+
def __bool__(self) -> bool:
|
|
72
|
+
raise TypeError(
|
|
73
|
+
"CAML expressions cannot be used with Python 'and'/'or'/'not'; "
|
|
74
|
+
"use '&', '|', '~' or the .and_()/.or_()/.not_() methods"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Comparison(CamlExpr):
|
|
79
|
+
"""A comparison operator element, e.g. ``<Eq><FieldRef/><Value/></Eq>``."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, op: str, field: FieldRef, value: Value) -> None:
|
|
82
|
+
self.op = op
|
|
83
|
+
self.field = field
|
|
84
|
+
self.value = value
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def field_refs(self) -> set[str]:
|
|
88
|
+
return {self.field.name}
|
|
89
|
+
|
|
90
|
+
def to_xml(self) -> str:
|
|
91
|
+
return f"<{self.op}>{self.field.to_xml()}{self.value.to_xml()}</{self.op}>"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class IsNull(CamlExpr):
|
|
95
|
+
"""``<IsNull>`` (or ``<IsNotNull>``) element."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, field: FieldRef, negate: bool = False) -> None:
|
|
98
|
+
self.field = field
|
|
99
|
+
self.negate = negate
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def field_refs(self) -> set[str]:
|
|
103
|
+
return {self.field.name}
|
|
104
|
+
|
|
105
|
+
def to_xml(self) -> str:
|
|
106
|
+
tag = "IsNotNull" if self.negate else "IsNull"
|
|
107
|
+
return f"<{tag}>{self.field.to_xml()}</{tag}>"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class In(CamlExpr):
|
|
111
|
+
"""``<In>`` element over a list of values."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, field: FieldRef, values: Iterable[Value]) -> None:
|
|
114
|
+
self.field = field
|
|
115
|
+
self.values = list(values)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def field_refs(self) -> set[str]:
|
|
119
|
+
return {self.field.name}
|
|
120
|
+
|
|
121
|
+
def to_xml(self) -> str:
|
|
122
|
+
values = "".join(value.to_xml() for value in self.values)
|
|
123
|
+
return f"<In>{self.field.to_xml()}<Values>{values}</Values></In>"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Membership(CamlExpr):
|
|
127
|
+
"""``<Membership>`` element (e.g. ``Type="CurrentUserGroups"``)."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, field: FieldRef, type: str = "CurrentUserGroups") -> None: # noqa: A002
|
|
130
|
+
self.field = field
|
|
131
|
+
self.type = type
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def field_refs(self) -> set[str]:
|
|
135
|
+
return {self.field.name}
|
|
136
|
+
|
|
137
|
+
def to_xml(self) -> str:
|
|
138
|
+
from xml.sax.saxutils import quoteattr
|
|
139
|
+
|
|
140
|
+
return f"<Membership Type={quoteattr(self.type)}>{self.field.to_xml()}</Membership>"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class DateRangesOverlap(CamlExpr):
|
|
144
|
+
"""``<DateRangesOverlap>`` element."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, field: FieldRef, value: Value) -> None:
|
|
147
|
+
self.field = field
|
|
148
|
+
self.value = value
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def field_refs(self) -> set[str]:
|
|
152
|
+
return {self.field.name}
|
|
153
|
+
|
|
154
|
+
def to_xml(self) -> str:
|
|
155
|
+
return f"<DateRangesOverlap>{self.field.to_xml()}{self.value.to_xml()}</DateRangesOverlap>"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class _Binary(CamlExpr):
|
|
159
|
+
"""Base for the binary logical joins (``<And>``/``<Or>``)."""
|
|
160
|
+
|
|
161
|
+
tag = ""
|
|
162
|
+
|
|
163
|
+
def __init__(self, left: CamlExpr, right: CamlExpr) -> None:
|
|
164
|
+
self.left = left
|
|
165
|
+
self.right = right
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def field_refs(self) -> set[str]:
|
|
169
|
+
return self.left.field_refs | self.right.field_refs
|
|
170
|
+
|
|
171
|
+
def to_xml(self) -> str:
|
|
172
|
+
return f"<{self.tag}>{self.left.to_xml()}{self.right.to_xml()}</{self.tag}>"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class And(_Binary):
|
|
176
|
+
"""``<And>`` — exactly two children (nest for 3+)."""
|
|
177
|
+
|
|
178
|
+
tag = "And"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class Or(_Binary):
|
|
182
|
+
"""``<Or>`` — exactly two children (nest for 3+)."""
|
|
183
|
+
|
|
184
|
+
tag = "Or"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class Not(CamlExpr):
|
|
188
|
+
"""``<Not>`` — exactly one child."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, child: CamlExpr) -> None:
|
|
191
|
+
self.child = child
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def field_refs(self) -> set[str]:
|
|
195
|
+
return self.child.field_refs
|
|
196
|
+
|
|
197
|
+
def to_xml(self) -> str:
|
|
198
|
+
return f"<Not>{self.child.to_xml()}</Not>"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _fold(op: Callable[[CamlExpr, CamlExpr], _T], exprs: Iterable[CamlExpr]) -> _T:
|
|
202
|
+
"""Left-fold conditions into binary-nested nodes (``And(And(A,B),C)``)."""
|
|
203
|
+
items = list(exprs)
|
|
204
|
+
if not items:
|
|
205
|
+
raise ValueError("at least one condition is required")
|
|
206
|
+
result: Any = items[0]
|
|
207
|
+
for item in items[1:]:
|
|
208
|
+
result = op(result, item)
|
|
209
|
+
return result # type: ignore[no-any-return]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _as_field_ref(field: Any) -> FieldRef:
|
|
213
|
+
if isinstance(field, FieldRef):
|
|
214
|
+
return field
|
|
215
|
+
if isinstance(field, Field):
|
|
216
|
+
return field.field_ref
|
|
217
|
+
return FieldRef(str(field))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _as_value(value: Any, value_type: Optional[ValueType] = None, **value_opts: Any) -> Value:
|
|
221
|
+
if isinstance(value, Value):
|
|
222
|
+
return value
|
|
223
|
+
return Value(value, type=value_type, **value_opts)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Field:
|
|
227
|
+
"""A typed field with fluent comparison methods.
|
|
228
|
+
|
|
229
|
+
``Caml.text("Status").eq("Active")`` renders ``<Eq>`` with ``Type='Text'``;
|
|
230
|
+
``Caml.lookup("Category").id().in_([2, 3])`` renders an ``<In>`` over
|
|
231
|
+
``Integer`` values with ``LookupId="TRUE"``.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def __init__(self, name: str, value_type: ValueType = ValueType.Text, *, lookup_id: bool = False) -> None:
|
|
235
|
+
self.name = name
|
|
236
|
+
self.value_type = value_type
|
|
237
|
+
self.lookup_id = lookup_id
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def field_ref(self) -> FieldRef:
|
|
241
|
+
return FieldRef(self.name, lookup_id=self.lookup_id)
|
|
242
|
+
|
|
243
|
+
def id(self) -> "Field":
|
|
244
|
+
"""Treat this field as a lookup id (``LookupId="TRUE"``, ``Integer``)."""
|
|
245
|
+
return Field(self.name, ValueType.Integer, lookup_id=True)
|
|
246
|
+
|
|
247
|
+
def _compare(self, op: str, value: Any, **value_opts: Any) -> Comparison:
|
|
248
|
+
return Comparison(op, self.field_ref, _as_value(value, self.value_type, **value_opts))
|
|
249
|
+
|
|
250
|
+
def eq(self, value: Any, **value_opts: Any) -> Comparison:
|
|
251
|
+
return self._compare("Eq", value, **value_opts)
|
|
252
|
+
|
|
253
|
+
def neq(self, value: Any, **value_opts: Any) -> Comparison:
|
|
254
|
+
return self._compare("Neq", value, **value_opts)
|
|
255
|
+
|
|
256
|
+
def gt(self, value: Any, **value_opts: Any) -> Comparison:
|
|
257
|
+
return self._compare("Gt", value, **value_opts)
|
|
258
|
+
|
|
259
|
+
def geq(self, value: Any, **value_opts: Any) -> Comparison:
|
|
260
|
+
return self._compare("Geq", value, **value_opts)
|
|
261
|
+
|
|
262
|
+
def lt(self, value: Any, **value_opts: Any) -> Comparison:
|
|
263
|
+
return self._compare("Lt", value, **value_opts)
|
|
264
|
+
|
|
265
|
+
def leq(self, value: Any, **value_opts: Any) -> Comparison:
|
|
266
|
+
return self._compare("Leq", value, **value_opts)
|
|
267
|
+
|
|
268
|
+
def begins_with(self, value: Any, **value_opts: Any) -> Comparison:
|
|
269
|
+
return self._compare("BeginsWith", value, **value_opts)
|
|
270
|
+
|
|
271
|
+
def contains(self, value: Any, **value_opts: Any) -> Comparison:
|
|
272
|
+
return self._compare("Contains", value, **value_opts)
|
|
273
|
+
|
|
274
|
+
def not_contains(self, value: Any, **value_opts: Any) -> Comparison:
|
|
275
|
+
return self._compare("NotContains", value, **value_opts)
|
|
276
|
+
|
|
277
|
+
def includes(self, value: Any, **value_opts: Any) -> Comparison:
|
|
278
|
+
return self._compare("Includes", value, **value_opts)
|
|
279
|
+
|
|
280
|
+
def not_includes(self, value: Any, **value_opts: Any) -> Comparison:
|
|
281
|
+
return self._compare("NotIncludes", value, **value_opts)
|
|
282
|
+
|
|
283
|
+
def is_null(self) -> IsNull:
|
|
284
|
+
return IsNull(self.field_ref)
|
|
285
|
+
|
|
286
|
+
def is_not_null(self) -> IsNull:
|
|
287
|
+
return IsNull(self.field_ref, negate=True)
|
|
288
|
+
|
|
289
|
+
def in_(self, values: Iterable[Any]) -> In:
|
|
290
|
+
return In(self.field_ref, [_as_value(value, self.value_type) for value in values])
|
|
291
|
+
|
|
292
|
+
def eq_current_user(self) -> Comparison:
|
|
293
|
+
"""``<Eq><FieldRef LookupId='TRUE'/><Value><UserID/></Value></Eq>``."""
|
|
294
|
+
return Comparison("Eq", self.field_ref, _user_id())
|
|
295
|
+
|
|
296
|
+
def in_current_user_groups(self) -> Membership:
|
|
297
|
+
"""``<Membership Type='CurrentUserGroups'>``."""
|
|
298
|
+
return Membership(self.field_ref, "CurrentUserGroups")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class Caml:
|
|
302
|
+
"""Factory namespace for CAML expressions and values."""
|
|
303
|
+
|
|
304
|
+
# ── Special values ───────────────────────────────────────────
|
|
305
|
+
now: Value = _now()
|
|
306
|
+
today: Value = _today()
|
|
307
|
+
month: Value = _month()
|
|
308
|
+
user_id: Value = _user_id()
|
|
309
|
+
list_property = staticmethod(list_property)
|
|
310
|
+
|
|
311
|
+
# ── Typed fields ─────────────────────────────────────────────
|
|
312
|
+
@staticmethod
|
|
313
|
+
def field(name: str, value_type: ValueType = ValueType.Text, *, lookup_id: bool = False) -> Field:
|
|
314
|
+
return Field(name, value_type, lookup_id=lookup_id)
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def text(name: str) -> Field:
|
|
318
|
+
return Field(name, ValueType.Text)
|
|
319
|
+
|
|
320
|
+
@staticmethod
|
|
321
|
+
def number(name: str) -> Field:
|
|
322
|
+
return Field(name, ValueType.Number)
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def integer(name: str) -> Field:
|
|
326
|
+
return Field(name, ValueType.Integer)
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def date(name: str) -> Field:
|
|
330
|
+
return Field(name, ValueType.DateTime)
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def boolean(name: str) -> Field:
|
|
334
|
+
return Field(name, ValueType.Boolean)
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def lookup(name: str, *, id: bool = False) -> Field: # noqa: A002
|
|
338
|
+
return Field(name, ValueType.Integer, lookup_id=id)
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def user(name: str) -> Field:
|
|
342
|
+
return Field(name, ValueType.Integer, lookup_id=True)
|
|
343
|
+
|
|
344
|
+
# ── Comparisons (by field name, Field or FieldRef) ───────────
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _compare(op: str, field: Any, value: Any, value_type: Optional[ValueType] = None) -> Comparison:
|
|
347
|
+
return Comparison(op, _as_field_ref(field), _as_value(value, value_type))
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def eq(field: Any, value: Any) -> Comparison:
|
|
351
|
+
return Caml._compare("Eq", field, value)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def neq(field: Any, value: Any) -> Comparison:
|
|
355
|
+
return Caml._compare("Neq", field, value)
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def gt(field: Any, value: Any) -> Comparison:
|
|
359
|
+
return Caml._compare("Gt", field, value)
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def geq(field: Any, value: Any) -> Comparison:
|
|
363
|
+
return Caml._compare("Geq", field, value)
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def lt(field: Any, value: Any) -> Comparison:
|
|
367
|
+
return Caml._compare("Lt", field, value)
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def leq(field: Any, value: Any) -> Comparison:
|
|
371
|
+
return Caml._compare("Leq", field, value)
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def begins_with(field: Any, value: Any) -> Comparison:
|
|
375
|
+
return Caml._compare("BeginsWith", field, value)
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def contains(field: Any, value: Any) -> Comparison:
|
|
379
|
+
return Caml._compare("Contains", field, value)
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def not_contains(field: Any, value: Any) -> Comparison:
|
|
383
|
+
return Caml._compare("NotContains", field, value)
|
|
384
|
+
|
|
385
|
+
@staticmethod
|
|
386
|
+
def includes(field: Any, value: Any) -> Comparison:
|
|
387
|
+
return Caml._compare("Includes", field, value)
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def not_includes(field: Any, value: Any) -> Comparison:
|
|
391
|
+
return Caml._compare("NotIncludes", field, value)
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def is_null(field: Any) -> IsNull:
|
|
395
|
+
return IsNull(_as_field_ref(field))
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def is_not_null(field: Any) -> IsNull:
|
|
399
|
+
return IsNull(_as_field_ref(field), negate=True)
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def in_(field: Any, values: Iterable[Any], value_type: Optional[ValueType] = None) -> In:
|
|
403
|
+
return In(_as_field_ref(field), [_as_value(value, value_type) for value in values])
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def membership(field: Any, type: str = "CurrentUserGroups") -> Membership: # noqa: A002
|
|
407
|
+
return Membership(_as_field_ref(field), type)
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def date_ranges_overlap(field: Any, value: Any) -> DateRangesOverlap:
|
|
411
|
+
return DateRangesOverlap(_as_field_ref(field), _as_value(value))
|
|
412
|
+
|
|
413
|
+
# ── Logical joins ────────────────────────────────────────────
|
|
414
|
+
@staticmethod
|
|
415
|
+
def and_(*exprs: CamlExpr) -> CamlExpr:
|
|
416
|
+
return _fold(And, exprs)
|
|
417
|
+
|
|
418
|
+
@staticmethod
|
|
419
|
+
def or_(*exprs: CamlExpr) -> CamlExpr:
|
|
420
|
+
return _fold(Or, exprs)
|
|
421
|
+
|
|
422
|
+
@staticmethod
|
|
423
|
+
def not_(expr: CamlExpr) -> Not:
|
|
424
|
+
return Not(expr)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Typed CAML ``<FieldRef>`` nodes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from xml.sax.saxutils import quoteattr
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FieldRef:
|
|
10
|
+
"""A CAML ``<FieldRef>`` element.
|
|
11
|
+
|
|
12
|
+
``Ascending``/``Collapse`` apply inside ``<OrderBy>``/``<GroupBy>``;
|
|
13
|
+
``LookupId``/``RefType``/``List`` support lookup and join clauses.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
name: str,
|
|
19
|
+
*,
|
|
20
|
+
ascending: Optional[bool] = None,
|
|
21
|
+
collapse: Optional[bool] = None,
|
|
22
|
+
nullable: Optional[bool] = None,
|
|
23
|
+
lookup_id: bool = False,
|
|
24
|
+
ref_type: Optional[str] = None,
|
|
25
|
+
list_alias: Optional[str] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.name = name
|
|
28
|
+
self.ascending = ascending
|
|
29
|
+
self.collapse = collapse
|
|
30
|
+
self.nullable = nullable
|
|
31
|
+
self.lookup_id = lookup_id
|
|
32
|
+
self.ref_type = ref_type
|
|
33
|
+
self.list_alias = list_alias
|
|
34
|
+
|
|
35
|
+
def asc(self) -> "FieldRef":
|
|
36
|
+
"""A copy sorted ascending (for ``<OrderBy>``)."""
|
|
37
|
+
return self._copy(ascending=True)
|
|
38
|
+
|
|
39
|
+
def desc(self) -> "FieldRef":
|
|
40
|
+
"""A copy sorted descending (for ``<OrderBy>``)."""
|
|
41
|
+
return self._copy(ascending=False)
|
|
42
|
+
|
|
43
|
+
def _copy(self, **overrides) -> "FieldRef":
|
|
44
|
+
return FieldRef(
|
|
45
|
+
self.name,
|
|
46
|
+
ascending=overrides.get("ascending", self.ascending),
|
|
47
|
+
collapse=overrides.get("collapse", self.collapse),
|
|
48
|
+
nullable=overrides.get("nullable", self.nullable),
|
|
49
|
+
lookup_id=self.lookup_id,
|
|
50
|
+
ref_type=self.ref_type,
|
|
51
|
+
list_alias=self.list_alias,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def to_xml(self) -> str:
|
|
55
|
+
attrs = [f"Name={quoteattr(self.name)}"]
|
|
56
|
+
if self.ascending is not None:
|
|
57
|
+
attrs.append(f'Ascending="{str(self.ascending).upper()}"')
|
|
58
|
+
if self.collapse is not None:
|
|
59
|
+
attrs.append(f'Collapse="{str(self.collapse).upper()}"')
|
|
60
|
+
if self.nullable is not None:
|
|
61
|
+
attrs.append(f'Nullable="{str(self.nullable).upper()}"')
|
|
62
|
+
if self.lookup_id:
|
|
63
|
+
attrs.append('LookupId="TRUE"')
|
|
64
|
+
if self.ref_type is not None:
|
|
65
|
+
attrs.append(f"RefType={quoteattr(self.ref_type)}")
|
|
66
|
+
if self.list_alias is not None:
|
|
67
|
+
attrs.append(f"List={quoteattr(self.list_alias)}")
|
|
68
|
+
return f"<FieldRef {' '.join(attrs)}/>"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""CAML guards for the SharePoint **list view threshold**.
|
|
2
|
+
|
|
3
|
+
CAML parsing lives on :class:`~office365.sharepoint.listitems.caml.query.CamlQuery`
|
|
4
|
+
(``is_paged``/``field_refs``); this module owns the policy that turns it into
|
|
5
|
+
warnings and the columns worth indexing. It is a plain function API (not a
|
|
6
|
+
decorator), so it stays explicit and easy to test in isolation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import warnings
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from office365.sharepoint.listitems.caml.query import CamlQuery
|
|
15
|
+
from office365.sharepoint.thresholds import LIST_VIEW_THRESHOLD, SAFE_PAGE_SIZE
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def indexing_candidates(query: CamlQuery) -> set[str]:
|
|
19
|
+
"""Field names referenced by the query that are worth indexing (``ID`` excluded)."""
|
|
20
|
+
return set(query.index_candidates)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def threshold_warnings(
|
|
24
|
+
query: CamlQuery,
|
|
25
|
+
*,
|
|
26
|
+
item_count: Optional[int] = None,
|
|
27
|
+
threshold: int = LIST_VIEW_THRESHOLD,
|
|
28
|
+
) -> list[str]:
|
|
29
|
+
"""Warnings for a CAML query that may exceed the list view threshold.
|
|
30
|
+
|
|
31
|
+
Empty when the query is paged, when the list is known to be at/below the
|
|
32
|
+
threshold, or when an unpaged query references no columns (a plain
|
|
33
|
+
``create_all_items_query`` on a list whose size is unknown).
|
|
34
|
+
"""
|
|
35
|
+
if query.is_paged:
|
|
36
|
+
return []
|
|
37
|
+
if item_count is not None and item_count <= threshold:
|
|
38
|
+
return []
|
|
39
|
+
names = sorted(indexing_candidates(query))
|
|
40
|
+
if not names and item_count is None:
|
|
41
|
+
return []
|
|
42
|
+
fields = f" on column(s) {', '.join(repr(name) for name in names)}" if names else ""
|
|
43
|
+
hint = (
|
|
44
|
+
f"index them with ensure_indexed(...) (e.g. lst.ensure_indexed({names[0]!r}).execute_query())"
|
|
45
|
+
if names
|
|
46
|
+
else "index the filtered/sorted columns with ensure_indexed(...)"
|
|
47
|
+
)
|
|
48
|
+
return [
|
|
49
|
+
f"This CAML query is not paged and may exceed the SharePoint list view threshold "
|
|
50
|
+
f"({threshold:,} items){fields}. Pass page_size={SAFE_PAGE_SIZE} to get_items(), add "
|
|
51
|
+
f"RowLimit Paged='TRUE' and iterate, or {hint} to avoid throttling."
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def warn_if_unpaged(
|
|
56
|
+
query: CamlQuery,
|
|
57
|
+
*,
|
|
58
|
+
item_count: Optional[int] = None,
|
|
59
|
+
threshold: int = LIST_VIEW_THRESHOLD,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Emit a :class:`UserWarning` per issue from :func:`threshold_warnings`."""
|
|
62
|
+
for message in threshold_warnings(query, item_count=item_count, threshold=threshold):
|
|
63
|
+
warnings.warn(message, stacklevel=3)
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Iterator, Tuple
|
|
4
6
|
|
|
5
7
|
from office365.runtime.client_value import ClientValue
|
|
8
|
+
from office365.sharepoint.fields.builtin_field_name import SPBuiltInFieldName
|
|
6
9
|
from office365.sharepoint.listitems.collection_position import (
|
|
7
10
|
ListItemCollectionPosition,
|
|
8
11
|
)
|
|
9
12
|
from office365.sharepoint.types.resource_path import ResourcePath
|
|
10
13
|
from office365.sharepoint.views.scope import ViewScope
|
|
11
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from office365.sharepoint.listitems.caml.builder import QueryBuilder
|
|
17
|
+
|
|
18
|
+
_FIELD_REF_RE = re.compile(r"<FieldRef\s+Name=['\"]([^'\"]+)['\"]", re.IGNORECASE)
|
|
19
|
+
|
|
12
20
|
|
|
13
21
|
@dataclass
|
|
14
22
|
class CamlQuery(ClientValue):
|
|
@@ -31,6 +39,23 @@ class CamlQuery(ClientValue):
|
|
|
31
39
|
AllowIncrementalResults: bool = True
|
|
32
40
|
FolderServerRelativePath: ResourcePath | None = None
|
|
33
41
|
|
|
42
|
+
def __post_init__(self) -> None:
|
|
43
|
+
# The expression AST (set by QueryBuilder.build()); not serialized.
|
|
44
|
+
self._expr: "QueryBuilder | None" = None
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def builder() -> "QueryBuilder":
|
|
48
|
+
"""A fluent builder for this query (``ViewFields``/``Where``/``OrderBy``/...)."""
|
|
49
|
+
from office365.sharepoint.listitems.caml.builder import QueryBuilder
|
|
50
|
+
|
|
51
|
+
return QueryBuilder()
|
|
52
|
+
|
|
53
|
+
def __iter__(self) -> Iterator[Tuple[str, Any]]:
|
|
54
|
+
"""Yield serializable properties (skips private attrs such as the AST)."""
|
|
55
|
+
for name, value in vars(self).items():
|
|
56
|
+
if not name.startswith("_"):
|
|
57
|
+
yield name, value
|
|
58
|
+
|
|
34
59
|
@staticmethod
|
|
35
60
|
def parse(query_expr: str, scope: ViewScope = ViewScope.DefaultValue) -> CamlQuery:
|
|
36
61
|
"""Creates a CamlQuery object from a query expression
|
|
@@ -51,15 +76,59 @@ class CamlQuery(ClientValue):
|
|
|
51
76
|
@staticmethod
|
|
52
77
|
def create_all_folders_query() -> CamlQuery:
|
|
53
78
|
"""Constructs a query to return folder objects"""
|
|
54
|
-
qry_text =
|
|
79
|
+
qry_text = (
|
|
80
|
+
f'<Where><Eq><FieldRef Name="{SPBuiltInFieldName.FSObjType}" /><Value Type="Integer">1</Value></Eq></Where>'
|
|
81
|
+
)
|
|
55
82
|
return CamlQuery.parse(qry_text, ViewScope.DefaultValue)
|
|
56
83
|
|
|
57
84
|
@staticmethod
|
|
58
85
|
def create_all_files_query() -> CamlQuery:
|
|
59
86
|
"""Constructs a query to return file objects"""
|
|
60
|
-
qry_text =
|
|
87
|
+
qry_text = (
|
|
88
|
+
f'<Where><Eq><FieldRef Name="{SPBuiltInFieldName.FSObjType}" /><Value Type="Integer">0</Value></Eq></Where>'
|
|
89
|
+
)
|
|
61
90
|
return CamlQuery.parse(qry_text, ViewScope.DefaultValue)
|
|
62
91
|
|
|
92
|
+
@property
|
|
93
|
+
def is_paged(self) -> bool:
|
|
94
|
+
"""Whether the query uses server-driven paging (``RowLimit Paged='TRUE'``)."""
|
|
95
|
+
if self._expr is not None:
|
|
96
|
+
return self._expr.is_paged
|
|
97
|
+
xml = self.ViewXml or ""
|
|
98
|
+
return "Paged='TRUE'" in xml or 'Paged="TRUE"' in xml
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def order_by_fields(self) -> list[str]:
|
|
102
|
+
"""The field names in ``<OrderBy>``, in order (empty when unsorted).
|
|
103
|
+
|
|
104
|
+
Used to build the ``ListItemCollectionPosition`` paging token so a
|
|
105
|
+
multi-page CAML query continues where the previous page stopped.
|
|
106
|
+
"""
|
|
107
|
+
if self._expr is not None:
|
|
108
|
+
return [ref.name for ref in self._expr._order_by]
|
|
109
|
+
match = re.search(r"<OrderBy>(.*?)</OrderBy>", self.ViewXml or "", re.DOTALL | re.IGNORECASE)
|
|
110
|
+
return _FIELD_REF_RE.findall(match.group(1)) if match else []
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def field_refs(self) -> set[str]:
|
|
114
|
+
"""The field names referenced by the query.
|
|
115
|
+
|
|
116
|
+
Uses the expression AST when the query was produced by the builder, else
|
|
117
|
+
falls back to scanning the raw ``ViewXml``.
|
|
118
|
+
"""
|
|
119
|
+
if self._expr is not None:
|
|
120
|
+
return self._expr.field_refs
|
|
121
|
+
return {match.group(1) for match in _FIELD_REF_RE.finditer(self.ViewXml or "")}
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def index_candidates(self) -> list[str]:
|
|
125
|
+
"""Fields the query filters/sorts on that are worth indexing (``ID`` excluded).
|
|
126
|
+
|
|
127
|
+
Feed these to :meth:`~office365.sharepoint.lists.list.List.ensure_indexed`
|
|
128
|
+
so a large-list query on them is not throttled by the list view threshold.
|
|
129
|
+
"""
|
|
130
|
+
return sorted(self.field_refs - {"ID"})
|
|
131
|
+
|
|
63
132
|
def __repr__(self):
|
|
64
133
|
return self.ViewXml or ""
|
|
65
134
|
|