@backtomyfuture/exchange-cli-darwin-arm64 0.1.9 → 0.1.10
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.
- package/bin/_internal/Python +0 -0
- package/bin/_internal/Python.framework/Python +0 -0
- package/bin/_internal/Python.framework/Resources/Info.plist +3 -3
- package/bin/_internal/Python.framework/Versions/3.12/Python +0 -0
- package/bin/_internal/Python.framework/Versions/{3.14 → 3.12}/Resources/Info.plist +3 -3
- package/bin/_internal/Python.framework/Versions/Current/Python +0 -0
- package/bin/_internal/Python.framework/Versions/Current/Resources/Info.plist +3 -3
- package/bin/_internal/_cffi_backend.cpython-312-darwin.so +0 -0
- package/bin/_internal/ada92cb5d92a588d1b93__mypyc.cpython-312-darwin.so +0 -0
- package/bin/_internal/base_library.zip +0 -0
- package/bin/_internal/certifi/cacert.pem +0 -631
- package/bin/_internal/charset_normalizer/{md.cpython-314-darwin.so → cd.cpython-312-darwin.so} +0 -0
- package/bin/_internal/charset_normalizer/{cd.cpython-314-darwin.so → md.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{click-8.3.2.dist-info → click-8.4.2.dist-info}/METADATA +1 -1
- package/bin/_internal/click-8.4.2.dist-info/RECORD +40 -0
- package/bin/_internal/cryptography/hazmat/bindings/_rust.abi3.so +0 -0
- package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/METADATA +7 -36
- package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/RECORD +137 -122
- package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/WHEEL +1 -1
- package/bin/_internal/cryptography-48.0.1.dist-info/sboms/cryptography-rust.cyclonedx.json +1362 -0
- package/bin/_internal/cryptography-48.0.1.dist-info/sboms/sbom.json +43 -0
- package/bin/_internal/exchangelib/__init__.py +120 -0
- package/bin/_internal/exchangelib/account.py +896 -0
- package/bin/_internal/exchangelib/attachments.py +268 -0
- package/bin/_internal/exchangelib/autodiscover/__init__.py +24 -0
- package/bin/_internal/exchangelib/autodiscover/cache.py +150 -0
- package/bin/_internal/exchangelib/autodiscover/discovery.py +491 -0
- package/bin/_internal/exchangelib/autodiscover/protocol.py +58 -0
- package/bin/_internal/exchangelib/configuration.py +106 -0
- package/bin/_internal/exchangelib/credentials.py +319 -0
- package/bin/_internal/exchangelib/errors.py +1710 -0
- package/bin/_internal/exchangelib/ewsdatetime.py +309 -0
- package/bin/_internal/exchangelib/extended_properties.py +311 -0
- package/bin/_internal/exchangelib/fields.py +1842 -0
- package/bin/_internal/exchangelib/folders/__init__.py +249 -0
- package/bin/_internal/exchangelib/folders/base.py +954 -0
- package/bin/_internal/exchangelib/folders/collections.py +576 -0
- package/bin/_internal/exchangelib/folders/known_folders.py +856 -0
- package/bin/_internal/exchangelib/folders/queryset.py +187 -0
- package/bin/_internal/exchangelib/folders/roots.py +390 -0
- package/bin/_internal/exchangelib/indexed_properties.py +96 -0
- package/bin/_internal/exchangelib/items/__init__.py +147 -0
- package/bin/_internal/exchangelib/items/base.py +241 -0
- package/bin/_internal/exchangelib/items/calendar_item.py +466 -0
- package/bin/_internal/exchangelib/items/contact.py +263 -0
- package/bin/_internal/exchangelib/items/item.py +413 -0
- package/bin/_internal/exchangelib/items/message.py +201 -0
- package/bin/_internal/exchangelib/items/post.py +38 -0
- package/bin/_internal/exchangelib/items/task.py +139 -0
- package/bin/_internal/exchangelib/properties.py +2364 -0
- package/bin/_internal/exchangelib/protocol.py +823 -0
- package/bin/_internal/exchangelib/queryset.py +691 -0
- package/bin/_internal/exchangelib/recurrence.py +349 -0
- package/bin/_internal/exchangelib/restriction.py +568 -0
- package/bin/_internal/exchangelib/services/__init__.py +117 -0
- package/bin/_internal/exchangelib/services/archive_item.py +33 -0
- package/bin/_internal/exchangelib/services/common.py +983 -0
- package/bin/_internal/exchangelib/services/convert_id.py +45 -0
- package/bin/_internal/exchangelib/services/copy_item.py +7 -0
- package/bin/_internal/exchangelib/services/create_attachment.py +34 -0
- package/bin/_internal/exchangelib/services/create_folder.py +45 -0
- package/bin/_internal/exchangelib/services/create_item.py +97 -0
- package/bin/_internal/exchangelib/services/create_user_configuration.py +19 -0
- package/bin/_internal/exchangelib/services/delete_attachment.py +28 -0
- package/bin/_internal/exchangelib/services/delete_folder.py +21 -0
- package/bin/_internal/exchangelib/services/delete_item.py +51 -0
- package/bin/_internal/exchangelib/services/delete_user_configuration.py +19 -0
- package/bin/_internal/exchangelib/services/empty_folder.py +25 -0
- package/bin/_internal/exchangelib/services/expand_dl.py +21 -0
- package/bin/_internal/exchangelib/services/export_items.py +27 -0
- package/bin/_internal/exchangelib/services/find_folder.py +80 -0
- package/bin/_internal/exchangelib/services/find_item.py +112 -0
- package/bin/_internal/exchangelib/services/find_people.py +106 -0
- package/bin/_internal/exchangelib/services/get_attachment.py +117 -0
- package/bin/_internal/exchangelib/services/get_delegate.py +45 -0
- package/bin/_internal/exchangelib/services/get_events.py +39 -0
- package/bin/_internal/exchangelib/services/get_folder.py +65 -0
- package/bin/_internal/exchangelib/services/get_item.py +41 -0
- package/bin/_internal/exchangelib/services/get_mail_tips.py +43 -0
- package/bin/_internal/exchangelib/services/get_persona.py +31 -0
- package/bin/_internal/exchangelib/services/get_room_lists.py +21 -0
- package/bin/_internal/exchangelib/services/get_rooms.py +21 -0
- package/bin/_internal/exchangelib/services/get_searchable_mailboxes.py +48 -0
- package/bin/_internal/exchangelib/services/get_server_time_zones.py +39 -0
- package/bin/_internal/exchangelib/services/get_streaming_events.py +102 -0
- package/bin/_internal/exchangelib/services/get_user_availability.py +67 -0
- package/bin/_internal/exchangelib/services/get_user_configuration.py +43 -0
- package/bin/_internal/exchangelib/services/get_user_oof_settings.py +40 -0
- package/bin/_internal/exchangelib/services/get_user_settings.py +87 -0
- package/bin/_internal/exchangelib/services/inbox_rules.py +104 -0
- package/bin/_internal/exchangelib/services/mark_as_junk.py +27 -0
- package/bin/_internal/exchangelib/services/move_folder.py +27 -0
- package/bin/_internal/exchangelib/services/move_item.py +28 -0
- package/bin/_internal/exchangelib/services/resolve_names.py +104 -0
- package/bin/_internal/exchangelib/services/send_item.py +26 -0
- package/bin/_internal/exchangelib/services/send_notification.py +43 -0
- package/bin/_internal/exchangelib/services/set_user_oof_settings.py +34 -0
- package/bin/_internal/exchangelib/services/subscribe.py +125 -0
- package/bin/_internal/exchangelib/services/sync_folder_hierarchy.py +95 -0
- package/bin/_internal/exchangelib/services/sync_folder_items.py +77 -0
- package/bin/_internal/exchangelib/services/unsubscribe.py +21 -0
- package/bin/_internal/exchangelib/services/update_folder.py +158 -0
- package/bin/_internal/exchangelib/services/update_item.py +113 -0
- package/bin/_internal/exchangelib/services/update_user_configuration.py +17 -0
- package/bin/_internal/exchangelib/services/upload_items.py +47 -0
- package/bin/_internal/exchangelib/settings.py +88 -0
- package/bin/_internal/exchangelib/transport.py +214 -0
- package/bin/_internal/exchangelib/util.py +894 -0
- package/bin/_internal/exchangelib/version.py +320 -0
- package/bin/_internal/exchangelib/winzone.py +662 -0
- package/bin/_internal/exchangelib-5.6.0.dist-info/INSTALLER +1 -0
- package/bin/_internal/exchangelib-5.6.0.dist-info/METADATA +77 -0
- package/bin/_internal/exchangelib-5.6.0.dist-info/RECORD +184 -0
- package/bin/_internal/exchangelib-5.6.0.dist-info/licenses/LICENSE +21 -0
- package/bin/_internal/exchangelib-5.6.0.dist-info/top_level.txt +1 -0
- package/bin/_internal/libcrypto.3.dylib +0 -0
- package/bin/_internal/libssl.3.dylib +0 -0
- package/bin/_internal/lxml/_elementpath.cpython-312-darwin.so +0 -0
- package/bin/_internal/lxml/builder.cpython-312-darwin.so +0 -0
- package/bin/_internal/lxml/{etree.cpython-314-darwin.so → etree.cpython-312-darwin.so} +0 -0
- package/bin/_internal/lxml/html/_difflib.cpython-312-darwin.so +0 -0
- package/bin/_internal/lxml/html/diff.cpython-312-darwin.so +0 -0
- package/bin/_internal/lxml/{objectify.cpython-314-darwin.so → objectify.cpython-312-darwin.so} +0 -0
- package/bin/_internal/lxml/sax.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_asyncio.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/termios.cpython-314-darwin.so → python3.12/lib-dynload/_bisect.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_sha2.cpython-314-darwin.so → python3.12/lib-dynload/_blake2.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_bz2.cpython-314-darwin.so → python3.12/lib-dynload/_bz2.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_codecs_cn.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_cn.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_codecs_hk.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_hk.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_interpqueues.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_iso2022.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_codecs_jp.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_jp.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_codecs_kr.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_kr.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_codecs_tw.cpython-314-darwin.so → python3.12/lib-dynload/_codecs_tw.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_statistics.cpython-314-darwin.so → python3.12/lib-dynload/_contextvars.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_csv.cpython-314-darwin.so → python3.12/lib-dynload/_csv.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_ctypes.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_datetime.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_dbm.cpython-314-darwin.so → python3.12/lib-dynload/_dbm.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_decimal.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_elementtree.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_hashlib.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_heapq.cpython-314-darwin.so → python3.12/lib-dynload/_heapq.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_json.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_lzma.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_md5.cpython-314-darwin.so → python3.12/lib-dynload/_md5.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_multibytecodec.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_multiprocessing.cpython-314-darwin.so → python3.12/lib-dynload/_multiprocessing.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_opcode.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_pickle.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_bisect.cpython-314-darwin.so → python3.12/lib-dynload/_posixshmem.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_posixsubprocess.cpython-314-darwin.so → python3.12/lib-dynload/_posixsubprocess.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_queue.cpython-314-darwin.so → python3.12/lib-dynload/_queue.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_random.cpython-314-darwin.so → python3.12/lib-dynload/_random.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_scproxy.cpython-314-darwin.so → python3.12/lib-dynload/_scproxy.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_sha1.cpython-314-darwin.so → python3.12/lib-dynload/_sha1.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_sha2.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_blake2.cpython-314-darwin.so → python3.12/lib-dynload/_sha3.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_socket.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/_ssl.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_posixshmem.cpython-314-darwin.so → python3.12/lib-dynload/_statistics.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/_struct.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_uuid.cpython-314-darwin.so → python3.12/lib-dynload/_uuid.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_zoneinfo.cpython-314-darwin.so → python3.12/lib-dynload/_zoneinfo.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/array.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/binascii.cpython-314-darwin.so → python3.12/lib-dynload/binascii.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/fcntl.cpython-314-darwin.so → python3.12/lib-dynload/fcntl.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/grp.cpython-314-darwin.so → python3.12/lib-dynload/grp.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/math.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/_sha3.cpython-314-darwin.so → python3.12/lib-dynload/mmap.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/pyexpat.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/readline.cpython-312-darwin.so +0 -0
- package/bin/_internal/{python3.14/lib-dynload/resource.cpython-314-darwin.so → python3.12/lib-dynload/resource.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/select.cpython-314-darwin.so → python3.12/lib-dynload/select.cpython-312-darwin.so} +0 -0
- package/bin/_internal/{python3.14/lib-dynload/syslog.cpython-314-darwin.so → python3.12/lib-dynload/syslog.cpython-312-darwin.so} +0 -0
- package/bin/_internal/python3.12/lib-dynload/termios.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/unicodedata.cpython-312-darwin.so +0 -0
- package/bin/_internal/python3.12/lib-dynload/zlib.cpython-312-darwin.so +0 -0
- package/bin/_internal/tzdata/zoneinfo/Africa/Casablanca +0 -0
- package/bin/_internal/tzdata/zoneinfo/Africa/El_Aaiun +0 -0
- package/bin/_internal/tzdata/zoneinfo/America/Edmonton +0 -0
- package/bin/_internal/tzdata/zoneinfo/America/Vancouver +0 -0
- package/bin/_internal/tzdata/zoneinfo/America/Yellowknife +0 -0
- package/bin/_internal/tzdata/zoneinfo/Asia/Ho_Chi_Minh +0 -0
- package/bin/_internal/tzdata/zoneinfo/Asia/Saigon +0 -0
- package/bin/_internal/tzdata/zoneinfo/Canada/Mountain +0 -0
- package/bin/_internal/tzdata/zoneinfo/Canada/Pacific +0 -0
- package/bin/_internal/tzdata/zoneinfo/leapseconds +4 -4
- package/bin/_internal/tzdata/zoneinfo/tzdata.zi +11 -131
- package/bin/_internal/tzdata/zoneinfo/zone.tab +2 -2
- package/bin/_internal/tzdata/zoneinfo/zone1970.tab +2 -2
- package/bin/_internal/tzdata/zoneinfo/zonenow.tab +8 -2
- package/bin/exchange-cli +0 -0
- package/package.json +10 -7
- package/bin/_internal/81d243bd2c585b0f4821__mypyc.cpython-314-darwin.so +0 -0
- package/bin/_internal/Python.framework/Versions/3.14/Python +0 -0
- package/bin/_internal/_cffi_backend.cpython-314-darwin.so +0 -0
- package/bin/_internal/click-8.3.2.dist-info/RECORD +0 -40
- package/bin/_internal/liblzma.5.dylib +0 -0
- package/bin/_internal/libmpdec.4.dylib +0 -0
- package/bin/_internal/libzstd.1.dylib +0 -0
- package/bin/_internal/lxml/_elementpath.cpython-314-darwin.so +0 -0
- package/bin/_internal/lxml/builder.cpython-314-darwin.so +0 -0
- package/bin/_internal/lxml/html/_difflib.cpython-314-darwin.so +0 -0
- package/bin/_internal/lxml/html/diff.cpython-314-darwin.so +0 -0
- package/bin/_internal/lxml/sax.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_asyncio.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_codecs_iso2022.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_ctypes.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_decimal.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_elementtree.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_hashlib.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_hmac.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_interpreters.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_json.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_lzma.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_multibytecodec.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_pickle.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_socket.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_ssl.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_struct.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/_zstd.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/array.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/math.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/mmap.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/pyexpat.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/readline.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/unicodedata.cpython-314-darwin.so +0 -0
- package/bin/_internal/python3.14/lib-dynload/zlib.cpython-314-darwin.so +0 -0
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/INSTALLER +0 -1
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/METADATA +0 -133
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/RECORD +0 -21
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/REQUESTED +0 -0
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/licenses/LICENSE +0 -73
- package/bin/_internal/setuptools/_vendor/importlib_metadata-8.7.1.dist-info/top_level.txt +0 -1
- /package/bin/_internal/{click-8.3.2.dist-info → click-8.4.2.dist-info}/INSTALLER +0 -0
- /package/bin/_internal/{click-8.3.2.dist-info → click-8.4.2.dist-info}/WHEEL +0 -0
- /package/bin/_internal/{click-8.3.2.dist-info → click-8.4.2.dist-info}/licenses/LICENSE.txt +0 -0
- /package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/INSTALLER +0 -0
- /package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/licenses/LICENSE +0 -0
- /package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/licenses/LICENSE.APACHE +0 -0
- /package/bin/_internal/{cryptography-46.0.7.dist-info → cryptography-48.0.1.dist-info}/licenses/LICENSE.BSD +0 -0
- /package/bin/_internal/{setuptools/_vendor/importlib_metadata-8.7.1.dist-info → exchangelib-5.6.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from ..errors import InvalidEnumValue
|
|
2
|
+
from ..properties import Notification
|
|
3
|
+
from ..util import MNS, create_element
|
|
4
|
+
from .common import EWSService, add_xml_child
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SendNotification(EWSService):
|
|
8
|
+
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/sendnotification
|
|
9
|
+
|
|
10
|
+
This service is implemented backwards compared to other services. We use it to parse the XML body of push
|
|
11
|
+
notifications we receive on the callback URL defined in a push subscription, and to create responses to these
|
|
12
|
+
push notifications.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
SERVICE_NAME = "SendNotification"
|
|
16
|
+
OK = "OK"
|
|
17
|
+
UNSUBSCRIBE = "Unsubscribe"
|
|
18
|
+
STATUS_CHOICES = (OK, UNSUBSCRIBE)
|
|
19
|
+
|
|
20
|
+
def ok_payload(self):
|
|
21
|
+
return self.wrap(content=self.get_payload(status=self.OK))
|
|
22
|
+
|
|
23
|
+
def unsubscribe_payload(self):
|
|
24
|
+
return self.wrap(content=self.get_payload(status=self.UNSUBSCRIBE))
|
|
25
|
+
|
|
26
|
+
def _elem_to_obj(self, elem):
|
|
27
|
+
return Notification.from_xml(elem=elem, account=None)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _response_tag(cls):
|
|
31
|
+
"""Return the name of the element containing the service response."""
|
|
32
|
+
return f"{{{MNS}}}{cls.SERVICE_NAME}"
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def _get_elements_in_container(cls, container):
|
|
36
|
+
return container.findall(Notification.response_tag())
|
|
37
|
+
|
|
38
|
+
def get_payload(self, status):
|
|
39
|
+
if status not in self.STATUS_CHOICES:
|
|
40
|
+
raise InvalidEnumValue("status", status, self.STATUS_CHOICES)
|
|
41
|
+
payload = create_element(f"m:{self.SERVICE_NAME}Result")
|
|
42
|
+
add_xml_child(payload, "m:SubscriptionStatus", status)
|
|
43
|
+
return payload
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ..errors import InvalidTypeError
|
|
2
|
+
from ..properties import AvailabilityMailbox, Mailbox
|
|
3
|
+
from ..settings import OofSettings
|
|
4
|
+
from ..util import MNS, create_element, set_xml_value
|
|
5
|
+
from .common import EWSAccountService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SetUserOofSettings(EWSAccountService):
|
|
9
|
+
"""Set automatic replies for the specified mailbox.
|
|
10
|
+
MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/setuseroofsettings-operation
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
SERVICE_NAME = "SetUserOofSettings"
|
|
14
|
+
returns_elements = False
|
|
15
|
+
|
|
16
|
+
def call(self, oof_settings, mailbox):
|
|
17
|
+
if not isinstance(oof_settings, OofSettings):
|
|
18
|
+
raise InvalidTypeError("oof_settings", oof_settings, OofSettings)
|
|
19
|
+
if not isinstance(mailbox, Mailbox):
|
|
20
|
+
raise InvalidTypeError("mailbox", mailbox, Mailbox)
|
|
21
|
+
return self._get_elements(payload=self.get_payload(oof_settings=oof_settings, mailbox=mailbox))
|
|
22
|
+
|
|
23
|
+
def get_payload(self, oof_settings, mailbox):
|
|
24
|
+
payload = create_element(f"m:{self.SERVICE_NAME}Request")
|
|
25
|
+
set_xml_value(payload, AvailabilityMailbox.from_mailbox(mailbox), version=self.account.version)
|
|
26
|
+
return set_xml_value(payload, oof_settings, version=self.account.version)
|
|
27
|
+
|
|
28
|
+
def _get_element_container(self, message, name=None):
|
|
29
|
+
message = message.find(self._response_message_tag())
|
|
30
|
+
return super()._get_element_container(message=message, name=name)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def _response_message_tag(cls):
|
|
34
|
+
return f"{{{MNS}}}ResponseMessage"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""The 'Subscribe' service has three different modes - pull, push and streaming - with different signatures. Implement
|
|
2
|
+
as three distinct classes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
|
|
7
|
+
from ..util import MNS, create_element
|
|
8
|
+
from .common import EWSAccountService, add_xml_child, folder_ids_element
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Subscribe(EWSAccountService, metaclass=abc.ABCMeta):
|
|
12
|
+
"""Base class for subscription classes.
|
|
13
|
+
|
|
14
|
+
MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/subscribe-operation"""
|
|
15
|
+
|
|
16
|
+
SERVICE_NAME = "Subscribe"
|
|
17
|
+
EVENT_TYPES = (
|
|
18
|
+
"CopiedEvent",
|
|
19
|
+
"CreatedEvent",
|
|
20
|
+
"DeletedEvent",
|
|
21
|
+
"ModifiedEvent",
|
|
22
|
+
"MovedEvent",
|
|
23
|
+
"NewMailEvent",
|
|
24
|
+
"FreeBusyChangedEvent",
|
|
25
|
+
)
|
|
26
|
+
subscription_request_elem_tag = None
|
|
27
|
+
|
|
28
|
+
def _partial_call(self, payload_func, folders, event_types, **kwargs):
|
|
29
|
+
if set(event_types) - set(self.EVENT_TYPES):
|
|
30
|
+
raise ValueError(f"'event_types' values must consist of values in {self.EVENT_TYPES}")
|
|
31
|
+
return self._elems_to_objs(
|
|
32
|
+
self._get_elements(payload=payload_func(folders=folders, event_types=event_types, **kwargs))
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def _elem_to_obj(self, elem):
|
|
36
|
+
subscription_elem, watermark_elem = elem
|
|
37
|
+
return subscription_elem.text, watermark_elem.text
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def _get_elements_in_container(cls, container):
|
|
41
|
+
return [(container.find(f"{{{MNS}}}SubscriptionId"), container.find(f"{{{MNS}}}Watermark"))]
|
|
42
|
+
|
|
43
|
+
def _partial_payload(self, folders, event_types):
|
|
44
|
+
if folders is None:
|
|
45
|
+
# Interpret this as "all folders"
|
|
46
|
+
request_elem = create_element(self.subscription_request_elem_tag, attrs=dict(SubscribeToAllFolders=True))
|
|
47
|
+
else:
|
|
48
|
+
request_elem = create_element(self.subscription_request_elem_tag)
|
|
49
|
+
folder_ids = folder_ids_element(folders=folders, version=self.account.version, tag="t:FolderIds")
|
|
50
|
+
request_elem.append(folder_ids)
|
|
51
|
+
event_types_elem = create_element("t:EventTypes")
|
|
52
|
+
for event_type in event_types:
|
|
53
|
+
add_xml_child(event_types_elem, "t:EventType", event_type)
|
|
54
|
+
request_elem.append(event_types_elem)
|
|
55
|
+
return request_elem
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SubscribeToPull(Subscribe):
|
|
59
|
+
# https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/pullsubscriptionrequest
|
|
60
|
+
subscription_request_elem_tag = "m:PullSubscriptionRequest"
|
|
61
|
+
prefer_affinity = True
|
|
62
|
+
|
|
63
|
+
def call(self, folders, event_types, watermark, timeout):
|
|
64
|
+
yield from self._partial_call(
|
|
65
|
+
payload_func=self.get_payload,
|
|
66
|
+
folders=folders,
|
|
67
|
+
event_types=event_types,
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
watermark=watermark,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def get_payload(self, folders, event_types, watermark, timeout):
|
|
73
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
74
|
+
request_elem = self._partial_payload(folders=folders, event_types=event_types)
|
|
75
|
+
if watermark:
|
|
76
|
+
add_xml_child(request_elem, "m:Watermark", watermark)
|
|
77
|
+
add_xml_child(request_elem, "t:Timeout", timeout) # In minutes
|
|
78
|
+
payload.append(request_elem)
|
|
79
|
+
return payload
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SubscribeToPush(Subscribe):
|
|
83
|
+
# https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/pushsubscriptionrequest
|
|
84
|
+
subscription_request_elem_tag = "m:PushSubscriptionRequest"
|
|
85
|
+
|
|
86
|
+
def call(self, folders, event_types, watermark, status_frequency, url):
|
|
87
|
+
yield from self._partial_call(
|
|
88
|
+
payload_func=self.get_payload,
|
|
89
|
+
folders=folders,
|
|
90
|
+
event_types=event_types,
|
|
91
|
+
status_frequency=status_frequency,
|
|
92
|
+
url=url,
|
|
93
|
+
watermark=watermark,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get_payload(self, folders, event_types, watermark, status_frequency, url):
|
|
97
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
98
|
+
request_elem = self._partial_payload(folders=folders, event_types=event_types)
|
|
99
|
+
if watermark:
|
|
100
|
+
add_xml_child(request_elem, "m:Watermark", watermark)
|
|
101
|
+
add_xml_child(request_elem, "t:StatusFrequency", status_frequency) # In minutes
|
|
102
|
+
add_xml_child(request_elem, "t:URL", url)
|
|
103
|
+
payload.append(request_elem)
|
|
104
|
+
return payload
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SubscribeToStreaming(Subscribe):
|
|
108
|
+
# https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/streamingsubscriptionrequest
|
|
109
|
+
subscription_request_elem_tag = "m:StreamingSubscriptionRequest"
|
|
110
|
+
prefer_affinity = True
|
|
111
|
+
|
|
112
|
+
def call(self, folders, event_types):
|
|
113
|
+
yield from self._partial_call(payload_func=self.get_payload, folders=folders, event_types=event_types)
|
|
114
|
+
|
|
115
|
+
def _elem_to_obj(self, elem):
|
|
116
|
+
return elem.text
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def _get_elements_in_container(cls, container):
|
|
120
|
+
return [container.find(f"{{{MNS}}}SubscriptionId")]
|
|
121
|
+
|
|
122
|
+
def get_payload(self, folders, event_types):
|
|
123
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
124
|
+
payload.append(self._partial_payload(folders=folders, event_types=event_types))
|
|
125
|
+
return payload
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from ..properties import FolderId
|
|
5
|
+
from ..util import MNS, TNS, create_element, xml_text_to_value
|
|
6
|
+
from .common import EWSAccountService, add_xml_child, folder_ids_element, parse_folder_elem, shape_element
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SyncFolder(EWSAccountService, metaclass=abc.ABCMeta):
|
|
12
|
+
"""Base class for SyncFolderHierarchy and SyncFolderItems."""
|
|
13
|
+
|
|
14
|
+
element_container_name = f"{{{MNS}}}Changes"
|
|
15
|
+
# Change types
|
|
16
|
+
CREATE = "create"
|
|
17
|
+
UPDATE = "update"
|
|
18
|
+
DELETE = "delete"
|
|
19
|
+
CHANGE_TYPES = (CREATE, UPDATE, DELETE)
|
|
20
|
+
shape_tag = None
|
|
21
|
+
last_in_range_name = None
|
|
22
|
+
change_types_map = {
|
|
23
|
+
f"{{{TNS}}}Create": CREATE,
|
|
24
|
+
f"{{{TNS}}}Update": UPDATE,
|
|
25
|
+
f"{{{TNS}}}Delete": DELETE,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args, **kwargs):
|
|
29
|
+
# These values are reset and set each time call() is consumed
|
|
30
|
+
self.sync_state = None
|
|
31
|
+
self.includes_last_item_in_range = None
|
|
32
|
+
super().__init__(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def _get_element_container(self, message, name=None):
|
|
35
|
+
self.sync_state = message.find(f"{{{MNS}}}SyncState").text
|
|
36
|
+
log.debug("Sync state is: %s", self.sync_state)
|
|
37
|
+
self.includes_last_item_in_range = xml_text_to_value(message.find(self.last_in_range_name).text, bool)
|
|
38
|
+
log.debug("Includes last item in range: %s", self.includes_last_item_in_range)
|
|
39
|
+
return super()._get_element_container(message=message, name=name)
|
|
40
|
+
|
|
41
|
+
def _partial_get_payload(self, folder, shape, additional_fields, sync_state):
|
|
42
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
43
|
+
payload.append(
|
|
44
|
+
shape_element(
|
|
45
|
+
tag=self.shape_tag, shape=shape, additional_fields=additional_fields, version=self.account.version
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
payload.append(folder_ids_element(folders=[folder], version=self.account.version, tag="m:SyncFolderId"))
|
|
49
|
+
if sync_state:
|
|
50
|
+
add_xml_child(payload, "m:SyncState", sync_state)
|
|
51
|
+
return payload
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SyncFolderHierarchy(SyncFolder):
|
|
55
|
+
"""MSDN:
|
|
56
|
+
https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/syncfolderhierarchy-operation
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
SERVICE_NAME = "SyncFolderHierarchy"
|
|
60
|
+
shape_tag = "m:FolderShape"
|
|
61
|
+
last_in_range_name = f"{{{MNS}}}IncludesLastFolderInRange"
|
|
62
|
+
|
|
63
|
+
def __init__(self, *args, **kwargs):
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
65
|
+
self.folder = None # A hack to communicate parsing args to _elems_to_objs()
|
|
66
|
+
|
|
67
|
+
def call(self, folder, shape, additional_fields, sync_state):
|
|
68
|
+
self.sync_state = sync_state
|
|
69
|
+
self.folder = folder
|
|
70
|
+
return self._elems_to_objs(
|
|
71
|
+
self._get_elements(
|
|
72
|
+
payload=self.get_payload(
|
|
73
|
+
folder=folder,
|
|
74
|
+
shape=shape,
|
|
75
|
+
additional_fields=additional_fields,
|
|
76
|
+
sync_state=sync_state,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _elem_to_obj(self, elem):
|
|
82
|
+
change_type = self.change_types_map[elem.tag]
|
|
83
|
+
if change_type == self.DELETE:
|
|
84
|
+
folder = FolderId.from_xml(elem=elem.find(FolderId.response_tag()), account=self.account)
|
|
85
|
+
else:
|
|
86
|
+
# We can't find() the element because we don't know which tag to look for. The change element can
|
|
87
|
+
# contain multiple folder types, each with their own tag.
|
|
88
|
+
folder_elem = elem[0]
|
|
89
|
+
folder = parse_folder_elem(elem=folder_elem, folder=self.folder)
|
|
90
|
+
return change_type, folder
|
|
91
|
+
|
|
92
|
+
def get_payload(self, folder, shape, additional_fields, sync_state):
|
|
93
|
+
return self._partial_get_payload(
|
|
94
|
+
folder=folder, shape=shape, additional_fields=additional_fields, sync_state=sync_state
|
|
95
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from ..errors import InvalidEnumValue, InvalidTypeError
|
|
2
|
+
from ..folders import BaseFolder
|
|
3
|
+
from ..properties import ItemId
|
|
4
|
+
from ..util import MNS, TNS, peek, xml_text_to_value
|
|
5
|
+
from .common import add_xml_child, item_ids_element
|
|
6
|
+
from .sync_folder_hierarchy import SyncFolder
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SyncFolderItems(SyncFolder):
|
|
10
|
+
"""MSDN:
|
|
11
|
+
https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/syncfolderitems-operation
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
SERVICE_NAME = "SyncFolderItems"
|
|
15
|
+
SYNC_SCOPES = (
|
|
16
|
+
"NormalItems",
|
|
17
|
+
"NormalAndAssociatedItems",
|
|
18
|
+
)
|
|
19
|
+
# Extra change type
|
|
20
|
+
READ_FLAG_CHANGE = "read_flag_change"
|
|
21
|
+
CHANGE_TYPES = SyncFolder.CHANGE_TYPES + (READ_FLAG_CHANGE,)
|
|
22
|
+
shape_tag = "m:ItemShape"
|
|
23
|
+
last_in_range_name = f"{{{MNS}}}IncludesLastItemInRange"
|
|
24
|
+
change_types_map = SyncFolder.change_types_map
|
|
25
|
+
change_types_map[f"{{{TNS}}}ReadFlagChange"] = READ_FLAG_CHANGE
|
|
26
|
+
|
|
27
|
+
def call(self, folder, shape, additional_fields, sync_state, ignore, max_changes_returned, sync_scope):
|
|
28
|
+
self.sync_state = sync_state
|
|
29
|
+
if max_changes_returned is None:
|
|
30
|
+
max_changes_returned = 100
|
|
31
|
+
if not isinstance(max_changes_returned, int):
|
|
32
|
+
raise InvalidTypeError("max_changes_returned", max_changes_returned, int)
|
|
33
|
+
if max_changes_returned <= 0:
|
|
34
|
+
raise ValueError(f"'max_changes_returned' {max_changes_returned} must be a positive integer")
|
|
35
|
+
if sync_scope is not None and sync_scope not in self.SYNC_SCOPES:
|
|
36
|
+
raise InvalidEnumValue("sync_scope", sync_scope, self.SYNC_SCOPES)
|
|
37
|
+
return self._elems_to_objs(
|
|
38
|
+
self._get_elements(
|
|
39
|
+
payload=self.get_payload(
|
|
40
|
+
folder=folder,
|
|
41
|
+
shape=shape,
|
|
42
|
+
additional_fields=additional_fields,
|
|
43
|
+
sync_state=sync_state,
|
|
44
|
+
ignore=ignore,
|
|
45
|
+
max_changes_returned=max_changes_returned,
|
|
46
|
+
sync_scope=sync_scope,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def _elem_to_obj(self, elem):
|
|
52
|
+
change_type = self.change_types_map[elem.tag]
|
|
53
|
+
if change_type == self.READ_FLAG_CHANGE:
|
|
54
|
+
item = (
|
|
55
|
+
ItemId.from_xml(elem=elem.find(ItemId.response_tag()), account=self.account),
|
|
56
|
+
xml_text_to_value(elem.find(f"{{{TNS}}}IsRead").text, bool),
|
|
57
|
+
)
|
|
58
|
+
elif change_type == self.DELETE:
|
|
59
|
+
item = ItemId.from_xml(elem=elem.find(ItemId.response_tag()), account=self.account)
|
|
60
|
+
else:
|
|
61
|
+
# We can't find() the element because we don't know which tag to look for. The change element can
|
|
62
|
+
# contain multiple item types, each with their own tag.
|
|
63
|
+
item_elem = elem[0]
|
|
64
|
+
item = BaseFolder.item_model_from_tag(item_elem.tag).from_xml(elem=item_elem, account=self.account)
|
|
65
|
+
return change_type, item
|
|
66
|
+
|
|
67
|
+
def get_payload(self, folder, shape, additional_fields, sync_state, ignore, max_changes_returned, sync_scope):
|
|
68
|
+
sync_folder_items = self._partial_get_payload(
|
|
69
|
+
folder=folder, shape=shape, additional_fields=additional_fields, sync_state=sync_state
|
|
70
|
+
)
|
|
71
|
+
is_empty, ignore = (True, None) if ignore is None else peek(ignore)
|
|
72
|
+
if not is_empty:
|
|
73
|
+
sync_folder_items.append(item_ids_element(items=ignore, version=self.account.version, tag="m:Ignore"))
|
|
74
|
+
add_xml_child(sync_folder_items, "m:MaxChangesReturned", max_changes_returned)
|
|
75
|
+
if sync_scope:
|
|
76
|
+
add_xml_child(sync_folder_items, "m:SyncScope", sync_scope)
|
|
77
|
+
return sync_folder_items
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ..util import create_element
|
|
2
|
+
from .common import EWSAccountService, add_xml_child
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Unsubscribe(EWSAccountService):
|
|
6
|
+
"""Unsubscribing is only valid for pull and streaming notifications.
|
|
7
|
+
|
|
8
|
+
MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/unsubscribe-operation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SERVICE_NAME = "Unsubscribe"
|
|
12
|
+
returns_elements = False
|
|
13
|
+
prefer_affinity = True
|
|
14
|
+
|
|
15
|
+
def call(self, subscription_id):
|
|
16
|
+
return self._get_elements(payload=self.get_payload(subscription_id=subscription_id))
|
|
17
|
+
|
|
18
|
+
def get_payload(self, subscription_id):
|
|
19
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
20
|
+
add_xml_child(payload, "m:SubscriptionId", subscription_id)
|
|
21
|
+
return payload
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
from ..fields import FieldPath, IndexedField
|
|
4
|
+
from ..properties import FolderId
|
|
5
|
+
from ..util import MNS, create_element, set_xml_value
|
|
6
|
+
from .common import EWSAccountService, parse_folder_elem, to_item_id
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseUpdateService(EWSAccountService, metaclass=abc.ABCMeta):
|
|
10
|
+
"""Base class for UpdateFolder and UpdateItem"""
|
|
11
|
+
|
|
12
|
+
SET_FIELD_ELEMENT_NAME = None
|
|
13
|
+
DELETE_FIELD_ELEMENT_NAME = None
|
|
14
|
+
CHANGE_ELEMENT_NAME = None
|
|
15
|
+
CHANGES_ELEMENT_NAME = None
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def _sorted_fields(target_model, fieldnames):
|
|
19
|
+
# Take a list of fieldnames and return the fields in the order they are mentioned in target_model.FIELDS.
|
|
20
|
+
# Checks that all fieldnames are valid.
|
|
21
|
+
fieldnames_copy = list(fieldnames)
|
|
22
|
+
# Loop over FIELDS and not supported_fields(). Upstream should make sure not to update a non-supported field.
|
|
23
|
+
for f in target_model.FIELDS:
|
|
24
|
+
if f.name in fieldnames_copy:
|
|
25
|
+
fieldnames_copy.remove(f.name)
|
|
26
|
+
yield f
|
|
27
|
+
if fieldnames_copy:
|
|
28
|
+
raise ValueError(f"Field name(s) {fieldnames_copy} are not valid {target_model.__name__!r} fields")
|
|
29
|
+
|
|
30
|
+
def _get_value(self, target, field):
|
|
31
|
+
return field.clean(getattr(target, field.name), version=self.account.version) # Make sure the value is OK
|
|
32
|
+
|
|
33
|
+
def _set_field_elems(self, target_model, field, value):
|
|
34
|
+
if isinstance(field, IndexedField):
|
|
35
|
+
# Generate either set or delete elements for all combinations of labels and subfields
|
|
36
|
+
supported_labels = field.value_cls.get_field_by_fieldname("label").supported_choices(
|
|
37
|
+
version=self.account.version
|
|
38
|
+
)
|
|
39
|
+
seen_labels = set()
|
|
40
|
+
subfields = field.value_cls.supported_fields(version=self.account.version)
|
|
41
|
+
for v in value:
|
|
42
|
+
seen_labels.add(v.label)
|
|
43
|
+
for subfield in subfields:
|
|
44
|
+
field_path = FieldPath(field=field, label=v.label, subfield=subfield)
|
|
45
|
+
subfield_value = getattr(v, subfield.name)
|
|
46
|
+
if not subfield_value:
|
|
47
|
+
# Generate delete elements for blank subfield values
|
|
48
|
+
yield self._delete_field_elem(field_path=field_path)
|
|
49
|
+
else:
|
|
50
|
+
# Generate set elements for non-null subfield values
|
|
51
|
+
yield self._set_field_elem(
|
|
52
|
+
target_model=target_model,
|
|
53
|
+
field_path=field_path,
|
|
54
|
+
value=field.value_cls(**{"label": v.label, subfield.name: subfield_value}),
|
|
55
|
+
)
|
|
56
|
+
# Generate delete elements for all subfields of all labels not mentioned in the list of values
|
|
57
|
+
for label in (label for label in supported_labels if label not in seen_labels):
|
|
58
|
+
for subfield in subfields:
|
|
59
|
+
yield self._delete_field_elem(field_path=FieldPath(field=field, label=label, subfield=subfield))
|
|
60
|
+
else:
|
|
61
|
+
yield self._set_field_elem(target_model=target_model, field_path=FieldPath(field=field), value=value)
|
|
62
|
+
|
|
63
|
+
def _set_field_elem(self, target_model, field_path, value):
|
|
64
|
+
set_field = create_element(self.SET_FIELD_ELEMENT_NAME)
|
|
65
|
+
set_xml_value(set_field, field_path, version=self.account.version)
|
|
66
|
+
folder = create_element(target_model.request_tag())
|
|
67
|
+
field_elem = field_path.field.to_xml(value, version=self.account.version)
|
|
68
|
+
set_xml_value(folder, field_elem, version=self.account.version)
|
|
69
|
+
set_field.append(folder)
|
|
70
|
+
return set_field
|
|
71
|
+
|
|
72
|
+
def _delete_field_elems(self, field):
|
|
73
|
+
for field_path in FieldPath(field=field).expand(version=self.account.version):
|
|
74
|
+
yield self._delete_field_elem(field_path=field_path)
|
|
75
|
+
|
|
76
|
+
def _delete_field_elem(self, field_path):
|
|
77
|
+
delete_folder_field = create_element(self.DELETE_FIELD_ELEMENT_NAME)
|
|
78
|
+
return set_xml_value(delete_folder_field, field_path, version=self.account.version)
|
|
79
|
+
|
|
80
|
+
def _update_elems(self, target, fieldnames):
|
|
81
|
+
target_model = target.__class__
|
|
82
|
+
|
|
83
|
+
for field in self._sorted_fields(target_model=target_model, fieldnames=fieldnames):
|
|
84
|
+
if field.is_read_only:
|
|
85
|
+
raise ValueError(f"{field.name!r} is a read-only field")
|
|
86
|
+
value = self._get_value(target, field)
|
|
87
|
+
|
|
88
|
+
if value is None or (field.is_list and not value):
|
|
89
|
+
# A value of None or [] means we want to remove this field from the item
|
|
90
|
+
if field.is_required or field.is_required_after_save:
|
|
91
|
+
raise ValueError(f"{field.name!r} is a required field and may not be deleted")
|
|
92
|
+
yield from self._delete_field_elems(field)
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
yield from self._set_field_elems(target_model=target_model, field=field, value=value)
|
|
96
|
+
|
|
97
|
+
def _change_elem(self, target, fieldnames):
|
|
98
|
+
if not fieldnames:
|
|
99
|
+
raise ValueError("'fieldnames' must not be empty")
|
|
100
|
+
change = create_element(self.CHANGE_ELEMENT_NAME)
|
|
101
|
+
set_xml_value(change, self._target_elem(target), version=self.account.version)
|
|
102
|
+
updates = create_element("t:Updates")
|
|
103
|
+
for elem in self._update_elems(target=target, fieldnames=fieldnames):
|
|
104
|
+
updates.append(elem)
|
|
105
|
+
change.append(updates)
|
|
106
|
+
return change
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
@abc.abstractmethod
|
|
110
|
+
def _target_elem(target):
|
|
111
|
+
"""Convert the object to update to an XML element"""
|
|
112
|
+
|
|
113
|
+
def _changes_elem(self, target_changes):
|
|
114
|
+
changes = create_element(self.CHANGES_ELEMENT_NAME)
|
|
115
|
+
for target, fieldnames in target_changes:
|
|
116
|
+
if not target.account:
|
|
117
|
+
target.account = self.account
|
|
118
|
+
changes.append(self._change_elem(target=target, fieldnames=fieldnames))
|
|
119
|
+
return changes
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class UpdateFolder(BaseUpdateService):
|
|
123
|
+
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updatefolder-operation"""
|
|
124
|
+
|
|
125
|
+
SERVICE_NAME = "UpdateFolder"
|
|
126
|
+
SET_FIELD_ELEMENT_NAME = "t:SetFolderField"
|
|
127
|
+
DELETE_FIELD_ELEMENT_NAME = "t:DeleteFolderField"
|
|
128
|
+
CHANGE_ELEMENT_NAME = "t:FolderChange"
|
|
129
|
+
CHANGES_ELEMENT_NAME = "m:FolderChanges"
|
|
130
|
+
element_container_name = f"{{{MNS}}}Folders"
|
|
131
|
+
|
|
132
|
+
def __init__(self, *args, **kwargs):
|
|
133
|
+
super().__init__(*args, **kwargs)
|
|
134
|
+
self.folders = [] # A hack to communicate parsing args to _elems_to_objs()
|
|
135
|
+
|
|
136
|
+
def call(self, folders):
|
|
137
|
+
# We can't easily find the correct folder class from the returned XML. Instead, return objects with the same
|
|
138
|
+
# class as the folder instance it was requested with.
|
|
139
|
+
self.folders = list(folders) # Convert to a list, in case 'folders' is a generator. We're iterating twice.
|
|
140
|
+
return self._elems_to_objs(self._chunked_get_elements(self.get_payload, items=self.folders))
|
|
141
|
+
|
|
142
|
+
def _elems_to_objs(self, elems):
|
|
143
|
+
for (folder, _), elem in zip(self.folders, elems):
|
|
144
|
+
if isinstance(elem, Exception):
|
|
145
|
+
yield elem
|
|
146
|
+
continue
|
|
147
|
+
yield parse_folder_elem(elem=elem, folder=folder)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _target_elem(target):
|
|
151
|
+
return to_item_id(target, FolderId)
|
|
152
|
+
|
|
153
|
+
def get_payload(self, folders):
|
|
154
|
+
# Takes a list of (Folder, fieldnames) tuples where 'Folder' is an instance of a subclass of Folder and
|
|
155
|
+
# 'fieldnames' are the attribute names that were updated.
|
|
156
|
+
payload = create_element(f"m:{self.SERVICE_NAME}")
|
|
157
|
+
payload.append(self._changes_elem(target_changes=folders))
|
|
158
|
+
return payload
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from ..errors import InvalidEnumValue
|
|
2
|
+
from ..ewsdatetime import EWSDate
|
|
3
|
+
from ..items import (
|
|
4
|
+
CONFLICT_RESOLUTION_CHOICES,
|
|
5
|
+
MESSAGE_DISPOSITION_CHOICES,
|
|
6
|
+
SEND_MEETING_INVITATIONS_AND_CANCELLATIONS_CHOICES,
|
|
7
|
+
SEND_ONLY,
|
|
8
|
+
CalendarItem,
|
|
9
|
+
Item,
|
|
10
|
+
)
|
|
11
|
+
from ..properties import ItemId
|
|
12
|
+
from ..util import MNS, create_element
|
|
13
|
+
from ..version import EXCHANGE_2013_SP1
|
|
14
|
+
from .common import to_item_id
|
|
15
|
+
from .update_folder import BaseUpdateService
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UpdateItem(BaseUpdateService):
|
|
19
|
+
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateitem-operation"""
|
|
20
|
+
|
|
21
|
+
SERVICE_NAME = "UpdateItem"
|
|
22
|
+
SET_FIELD_ELEMENT_NAME = "t:SetItemField"
|
|
23
|
+
DELETE_FIELD_ELEMENT_NAME = "t:DeleteItemField"
|
|
24
|
+
CHANGE_ELEMENT_NAME = "t:ItemChange"
|
|
25
|
+
CHANGES_ELEMENT_NAME = "m:ItemChanges"
|
|
26
|
+
element_container_name = f"{{{MNS}}}Items"
|
|
27
|
+
|
|
28
|
+
def call(
|
|
29
|
+
self,
|
|
30
|
+
items,
|
|
31
|
+
conflict_resolution,
|
|
32
|
+
message_disposition,
|
|
33
|
+
send_meeting_invitations_or_cancellations,
|
|
34
|
+
suppress_read_receipts,
|
|
35
|
+
):
|
|
36
|
+
if conflict_resolution not in CONFLICT_RESOLUTION_CHOICES:
|
|
37
|
+
raise InvalidEnumValue("conflict_resolution", conflict_resolution, CONFLICT_RESOLUTION_CHOICES)
|
|
38
|
+
if message_disposition not in MESSAGE_DISPOSITION_CHOICES:
|
|
39
|
+
raise InvalidEnumValue("message_disposition", message_disposition, MESSAGE_DISPOSITION_CHOICES)
|
|
40
|
+
if send_meeting_invitations_or_cancellations not in SEND_MEETING_INVITATIONS_AND_CANCELLATIONS_CHOICES:
|
|
41
|
+
raise InvalidEnumValue(
|
|
42
|
+
"send_meeting_invitations_or_cancellations",
|
|
43
|
+
send_meeting_invitations_or_cancellations,
|
|
44
|
+
SEND_MEETING_INVITATIONS_AND_CANCELLATIONS_CHOICES,
|
|
45
|
+
)
|
|
46
|
+
if message_disposition == SEND_ONLY:
|
|
47
|
+
raise ValueError("Cannot send-only existing objects. Use SendItem service instead")
|
|
48
|
+
return self._elems_to_objs(
|
|
49
|
+
self._chunked_get_elements(
|
|
50
|
+
self.get_payload,
|
|
51
|
+
items=items,
|
|
52
|
+
conflict_resolution=conflict_resolution,
|
|
53
|
+
message_disposition=message_disposition,
|
|
54
|
+
send_meeting_invitations_or_cancellations=send_meeting_invitations_or_cancellations,
|
|
55
|
+
suppress_read_receipts=suppress_read_receipts,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _elem_to_obj(self, elem):
|
|
60
|
+
return Item.id_from_xml(elem)
|
|
61
|
+
|
|
62
|
+
def _update_elems(self, target, fieldnames):
|
|
63
|
+
fieldnames_copy = list(fieldnames)
|
|
64
|
+
|
|
65
|
+
if target.__class__ == CalendarItem:
|
|
66
|
+
# For CalendarItem items where we update 'start' or 'end', we want to update internal timezone fields
|
|
67
|
+
target.clean_timezone_fields(version=self.account.version) # Possibly also sets timezone values
|
|
68
|
+
for field_name in ("start", "end"):
|
|
69
|
+
if field_name in fieldnames_copy:
|
|
70
|
+
tz_field_name = target.tz_field_for_field_name(field_name).name
|
|
71
|
+
if tz_field_name not in fieldnames_copy:
|
|
72
|
+
fieldnames_copy.append(tz_field_name)
|
|
73
|
+
|
|
74
|
+
yield from super()._update_elems(target=target, fieldnames=fieldnames_copy)
|
|
75
|
+
|
|
76
|
+
def _get_value(self, target, field):
|
|
77
|
+
value = super()._get_value(target, field)
|
|
78
|
+
|
|
79
|
+
if target.__class__ == CalendarItem:
|
|
80
|
+
# For CalendarItem items where we update 'start' or 'end', we want to send values in the local timezone
|
|
81
|
+
if field.name in ("start", "end"):
|
|
82
|
+
if type(value) is EWSDate:
|
|
83
|
+
# EWS always expects a datetime
|
|
84
|
+
return target.date_to_datetime(field_name=field.name)
|
|
85
|
+
tz_field_name = target.tz_field_for_field_name(field.name).name
|
|
86
|
+
return value.astimezone(getattr(target, tz_field_name))
|
|
87
|
+
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _target_elem(target):
|
|
92
|
+
return to_item_id(target, ItemId)
|
|
93
|
+
|
|
94
|
+
def get_payload(
|
|
95
|
+
self,
|
|
96
|
+
items,
|
|
97
|
+
conflict_resolution,
|
|
98
|
+
message_disposition,
|
|
99
|
+
send_meeting_invitations_or_cancellations,
|
|
100
|
+
suppress_read_receipts,
|
|
101
|
+
):
|
|
102
|
+
# Takes a list of (Item, fieldnames) tuples where 'Item' is an instance of a subclass of Item and 'fieldnames'
|
|
103
|
+
# are the attribute names that were updated.
|
|
104
|
+
attrs = dict(
|
|
105
|
+
ConflictResolution=conflict_resolution,
|
|
106
|
+
MessageDisposition=message_disposition,
|
|
107
|
+
SendMeetingInvitationsOrCancellations=send_meeting_invitations_or_cancellations,
|
|
108
|
+
)
|
|
109
|
+
if self.account.version.build >= EXCHANGE_2013_SP1:
|
|
110
|
+
attrs["SuppressReadReceipts"] = suppress_read_receipts
|
|
111
|
+
payload = create_element(f"m:{self.SERVICE_NAME}", attrs=attrs)
|
|
112
|
+
payload.append(self._changes_elem(target_changes=items))
|
|
113
|
+
return payload
|