@backtomyfuture/exchange-cli-darwin-arm64 0.1.8 → 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,309 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import zoneinfo
|
|
4
|
+
|
|
5
|
+
import tzlocal
|
|
6
|
+
|
|
7
|
+
from .errors import InvalidTypeError, NaiveDateTimeNotAllowed, UnknownTimeZone
|
|
8
|
+
from .winzone import IANA_TO_MS_TIMEZONE_MAP, MS_TIMEZONE_TO_IANA_MAP
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EWSDate(datetime.date):
|
|
14
|
+
"""Extends the normal date implementation to satisfy EWS."""
|
|
15
|
+
|
|
16
|
+
__slots__ = "_year", "_month", "_day", "_hashcode"
|
|
17
|
+
|
|
18
|
+
def ewsformat(self):
|
|
19
|
+
"""ISO 8601 format to satisfy xs:date as interpreted by EWS. Example: 2009-01-15."""
|
|
20
|
+
return self.isoformat()
|
|
21
|
+
|
|
22
|
+
def __add__(self, other):
|
|
23
|
+
dt = super().__add__(other)
|
|
24
|
+
if isinstance(dt, self.__class__):
|
|
25
|
+
return dt
|
|
26
|
+
return self.from_date(dt) # We want to return EWSDate objects
|
|
27
|
+
|
|
28
|
+
def __iadd__(self, other):
|
|
29
|
+
return self + other
|
|
30
|
+
|
|
31
|
+
def __sub__(self, other):
|
|
32
|
+
dt = super().__sub__(other)
|
|
33
|
+
if isinstance(dt, datetime.timedelta):
|
|
34
|
+
return dt
|
|
35
|
+
if isinstance(dt, self.__class__):
|
|
36
|
+
return dt
|
|
37
|
+
return self.from_date(dt) # We want to return EWSDate objects
|
|
38
|
+
|
|
39
|
+
def __isub__(self, other):
|
|
40
|
+
return self - other
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def fromordinal(cls, n):
|
|
44
|
+
dt = super().fromordinal(n)
|
|
45
|
+
if isinstance(dt, cls):
|
|
46
|
+
return dt
|
|
47
|
+
return cls.from_date(dt) # We want to return EWSDate objects
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_date(cls, d):
|
|
51
|
+
if type(d) is not datetime.date:
|
|
52
|
+
raise InvalidTypeError("d", d, datetime.date)
|
|
53
|
+
return cls(d.year, d.month, d.day)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_string(cls, date_string):
|
|
57
|
+
# Sometimes, we'll receive a date string with time zone information. Not very useful.
|
|
58
|
+
if date_string.endswith("Z"):
|
|
59
|
+
date_fmt = "%Y-%m-%dZ"
|
|
60
|
+
elif ":" in date_string:
|
|
61
|
+
if "+" in date_string:
|
|
62
|
+
date_fmt = "%Y-%m-%d+%H:%M"
|
|
63
|
+
else:
|
|
64
|
+
date_fmt = "%Y-%m-%d-%H:%M"
|
|
65
|
+
else:
|
|
66
|
+
date_fmt = "%Y-%m-%d"
|
|
67
|
+
d = datetime.datetime.strptime(date_string, date_fmt).date()
|
|
68
|
+
if isinstance(d, cls):
|
|
69
|
+
return d
|
|
70
|
+
return cls.from_date(d) # We want to return EWSDate objects
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class EWSDateTime(datetime.datetime):
|
|
74
|
+
"""Extends the normal datetime implementation to satisfy EWS."""
|
|
75
|
+
|
|
76
|
+
__slots__ = "_year", "_month", "_day", "_hour", "_minute", "_second", "_microsecond", "_tzinfo", "_hashcode"
|
|
77
|
+
|
|
78
|
+
def __new__(cls, *args, **kwargs):
|
|
79
|
+
# pylint: disable=arguments-differ
|
|
80
|
+
|
|
81
|
+
if len(args) == 8:
|
|
82
|
+
tzinfo = args[7]
|
|
83
|
+
else:
|
|
84
|
+
tzinfo = kwargs.get("tzinfo")
|
|
85
|
+
if isinstance(tzinfo, zoneinfo.ZoneInfo):
|
|
86
|
+
# Don't allow pytz or dateutil timezones here. They are not safe to use as direct input for datetime()
|
|
87
|
+
tzinfo = EWSTimeZone.from_timezone(tzinfo)
|
|
88
|
+
if not isinstance(tzinfo, (EWSTimeZone, type(None))):
|
|
89
|
+
raise InvalidTypeError("tzinfo", tzinfo, EWSTimeZone)
|
|
90
|
+
if len(args) == 8:
|
|
91
|
+
args = args[:7] + (tzinfo,)
|
|
92
|
+
else:
|
|
93
|
+
kwargs["tzinfo"] = tzinfo
|
|
94
|
+
return super().__new__(cls, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
def ewsformat(self):
|
|
97
|
+
"""ISO 8601 format to satisfy xs:datetime as interpreted by EWS. Examples:
|
|
98
|
+
* 2009-01-15T13:45:56Z
|
|
99
|
+
* 2009-01-15T13:45:56+01:00
|
|
100
|
+
"""
|
|
101
|
+
if not self.tzinfo:
|
|
102
|
+
raise ValueError(f"{self!r} must be timezone-aware")
|
|
103
|
+
if self.tzinfo.key == "UTC":
|
|
104
|
+
if self.microsecond:
|
|
105
|
+
return self.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
106
|
+
return self.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
107
|
+
return self.isoformat()
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def from_datetime(cls, d):
|
|
111
|
+
if type(d) is not datetime.datetime:
|
|
112
|
+
raise InvalidTypeError("d", d, datetime.datetime)
|
|
113
|
+
if d.tzinfo is None:
|
|
114
|
+
tz = None
|
|
115
|
+
elif isinstance(d.tzinfo, EWSTimeZone):
|
|
116
|
+
tz = d.tzinfo
|
|
117
|
+
else:
|
|
118
|
+
tz = EWSTimeZone.from_timezone(d.tzinfo)
|
|
119
|
+
return cls(d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, tzinfo=tz)
|
|
120
|
+
|
|
121
|
+
def astimezone(self, tz=None):
|
|
122
|
+
if tz is None:
|
|
123
|
+
tz = EWSTimeZone.localzone()
|
|
124
|
+
t = super().astimezone(tz=tz).replace(tzinfo=tz)
|
|
125
|
+
if isinstance(t, self.__class__):
|
|
126
|
+
return t
|
|
127
|
+
return self.from_datetime(t) # We want to return EWSDateTime objects
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def fromisoformat(cls, date_string):
|
|
131
|
+
return cls.from_string(date_string)
|
|
132
|
+
|
|
133
|
+
def __add__(self, other):
|
|
134
|
+
t = super().__add__(other)
|
|
135
|
+
if isinstance(t, self.__class__):
|
|
136
|
+
return t
|
|
137
|
+
return self.from_datetime(t) # We want to return EWSDateTime objects
|
|
138
|
+
|
|
139
|
+
def __iadd__(self, other):
|
|
140
|
+
return self + other
|
|
141
|
+
|
|
142
|
+
def __sub__(self, other):
|
|
143
|
+
t = super().__sub__(other)
|
|
144
|
+
if isinstance(t, datetime.timedelta):
|
|
145
|
+
return t
|
|
146
|
+
if isinstance(t, self.__class__):
|
|
147
|
+
return t
|
|
148
|
+
return self.from_datetime(t) # We want to return EWSDateTime objects
|
|
149
|
+
|
|
150
|
+
def __isub__(self, other):
|
|
151
|
+
return self - other
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_string(cls, date_string):
|
|
155
|
+
# Parses several common datetime formats and returns time zone aware EWSDateTime objects
|
|
156
|
+
if date_string.endswith("Z"):
|
|
157
|
+
# UTC datetime
|
|
158
|
+
return super().strptime(date_string, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC)
|
|
159
|
+
if len(date_string) == 19:
|
|
160
|
+
# This is probably a naive datetime. Don't allow this, but signal caller with an appropriate error
|
|
161
|
+
local_dt = super().strptime(date_string, "%Y-%m-%dT%H:%M:%S")
|
|
162
|
+
raise NaiveDateTimeNotAllowed(local_dt)
|
|
163
|
+
# This is probably a datetime value with time zone information. This comes in the form '+/-HH:MM'.
|
|
164
|
+
aware_dt = datetime.datetime.fromisoformat(date_string).astimezone(UTC).replace(tzinfo=UTC)
|
|
165
|
+
if isinstance(aware_dt, cls):
|
|
166
|
+
return aware_dt
|
|
167
|
+
return cls.from_datetime(aware_dt)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def fromtimestamp(cls, t, tz=None):
|
|
171
|
+
dt = super().fromtimestamp(t, tz=tz)
|
|
172
|
+
if isinstance(dt, cls):
|
|
173
|
+
return dt
|
|
174
|
+
return cls.from_datetime(dt) # We want to return EWSDateTime objects
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def utcfromtimestamp(cls, t):
|
|
178
|
+
dt = super().utcfromtimestamp(t)
|
|
179
|
+
if isinstance(dt, cls):
|
|
180
|
+
return dt
|
|
181
|
+
return cls.from_datetime(dt) # We want to return EWSDateTime objects
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def now(cls, tz=None):
|
|
185
|
+
t = super().now(tz=tz)
|
|
186
|
+
if isinstance(t, cls):
|
|
187
|
+
return t
|
|
188
|
+
return cls.from_datetime(t) # We want to return EWSDateTime objects
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def utcnow(cls):
|
|
192
|
+
t = super().utcnow()
|
|
193
|
+
if isinstance(t, cls):
|
|
194
|
+
return t
|
|
195
|
+
return cls.from_datetime(t) # We want to return EWSDateTime objects
|
|
196
|
+
|
|
197
|
+
def date(self):
|
|
198
|
+
d = super().date()
|
|
199
|
+
if isinstance(d, EWSDate):
|
|
200
|
+
return d
|
|
201
|
+
return EWSDate.from_date(d) # We want to return EWSDate objects
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class EWSTimeZone(zoneinfo.ZoneInfo):
|
|
205
|
+
"""Represents a time zone as expected by the EWS TimezoneContext / TimezoneDefinition XML element, and returned by
|
|
206
|
+
services.GetServerTimeZones.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
IANA_TO_MS_MAP = IANA_TO_MS_TIMEZONE_MAP
|
|
210
|
+
MS_TO_IANA_MAP = MS_TIMEZONE_TO_IANA_MAP
|
|
211
|
+
|
|
212
|
+
def __new__(cls, *args, **kwargs):
|
|
213
|
+
try:
|
|
214
|
+
instance = super().__new__(cls, *args, **kwargs)
|
|
215
|
+
except zoneinfo.ZoneInfoNotFoundError as e:
|
|
216
|
+
raise UnknownTimeZone(e.args[0])
|
|
217
|
+
try:
|
|
218
|
+
instance.ms_id = cls.IANA_TO_MS_MAP[instance.key][0]
|
|
219
|
+
except KeyError:
|
|
220
|
+
raise UnknownTimeZone(f"No Windows timezone name found for timezone {instance.key!r}")
|
|
221
|
+
|
|
222
|
+
# We don't need the Windows long-format time zone name in long format. It's used in time zone XML elements, but
|
|
223
|
+
# EWS happily accepts empty strings. For a full list of time zones supported by the target server, including
|
|
224
|
+
# long-format names, see output of services.GetServerTimeZones(account.protocol).call()
|
|
225
|
+
instance.ms_name = ""
|
|
226
|
+
return instance
|
|
227
|
+
|
|
228
|
+
def __hash__(self):
|
|
229
|
+
return hash(self.key)
|
|
230
|
+
|
|
231
|
+
def __eq__(self, other):
|
|
232
|
+
# Microsoft time zones are less granular than IANA, so an EWSTimeZone created from 'Europe/Copenhagen' may
|
|
233
|
+
# return from the server as 'Europe/Copenhagen'. We're catering for Microsoft here, so base equality on the
|
|
234
|
+
# Microsoft time zone ID.
|
|
235
|
+
if not isinstance(other, self.__class__):
|
|
236
|
+
return NotImplemented
|
|
237
|
+
return self.ms_id == other.ms_id
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_ms_id(cls, ms_id):
|
|
241
|
+
# Create a time zone instance from a Microsoft time zone ID. This is lossy because there is not a 1:1
|
|
242
|
+
# translation from MS time zone ID to IANA time zone.
|
|
243
|
+
try:
|
|
244
|
+
return cls(cls.MS_TO_IANA_MAP[ms_id])
|
|
245
|
+
except KeyError:
|
|
246
|
+
if "/" in ms_id:
|
|
247
|
+
# EWS sometimes returns an ID that has a region/location format, e.g. 'Europe/Copenhagen'. Try the
|
|
248
|
+
# string unaltered.
|
|
249
|
+
return cls(ms_id)
|
|
250
|
+
raise UnknownTimeZone(f"Windows timezone ID {ms_id!r} is unknown by CLDR")
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def from_pytz(cls, tz):
|
|
254
|
+
return cls(tz.zone)
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def from_datetime(cls, tz):
|
|
258
|
+
"""Convert from a standard library `datetime.timezone` instance."""
|
|
259
|
+
return cls(tz.tzname(None))
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_dateutil(cls, tz):
|
|
263
|
+
# Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
|
|
264
|
+
# don't contain enough information to reliably match them with a CLDR time zone.
|
|
265
|
+
if hasattr(tz, "_filename"):
|
|
266
|
+
key = "/".join(tz._filename.split("/")[-2:])
|
|
267
|
+
return cls(key)
|
|
268
|
+
return cls(tz.tzname(datetime.datetime.now()))
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def from_zoneinfo(cls, tz):
|
|
272
|
+
return cls(tz.key)
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def from_timezone(cls, tz):
|
|
276
|
+
# Support multiple tzinfo implementations. We could use isinstance(), but then we'd have to have pytz
|
|
277
|
+
# and dateutil as dependencies for this package.
|
|
278
|
+
tz_module = tz.__class__.__module__.split(".")[0]
|
|
279
|
+
try:
|
|
280
|
+
return {
|
|
281
|
+
cls.__module__.split(".")[0]: lambda z: z,
|
|
282
|
+
"datetime": cls.from_datetime,
|
|
283
|
+
"dateutil": cls.from_dateutil,
|
|
284
|
+
"pytz": cls.from_pytz,
|
|
285
|
+
"zoneinfo": cls.from_zoneinfo,
|
|
286
|
+
"pytz_deprecation_shim": lambda z: cls.from_timezone(z.unwrap_shim()),
|
|
287
|
+
}[tz_module](tz)
|
|
288
|
+
except KeyError:
|
|
289
|
+
raise TypeError(f"Unsupported tzinfo type: {tz!r}")
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def localzone(cls):
|
|
293
|
+
try:
|
|
294
|
+
tz = tzlocal.get_localzone()
|
|
295
|
+
except zoneinfo.ZoneInfoNotFoundError:
|
|
296
|
+
# Older versions of tzlocal will raise a pytz exception. Let's not depend on pytz just for that.
|
|
297
|
+
raise UnknownTimeZone("Failed to guess local timezone")
|
|
298
|
+
# Handle both old and new versions of tzlocal that may return pytz or zoneinfo objects, respectively
|
|
299
|
+
return cls.from_timezone(tz)
|
|
300
|
+
|
|
301
|
+
def fromutc(self, dt):
|
|
302
|
+
t = super().fromutc(dt)
|
|
303
|
+
if isinstance(t, EWSDateTime):
|
|
304
|
+
return t
|
|
305
|
+
return EWSDateTime.from_datetime(t) # We want to return EWSDateTime objects
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
UTC = EWSTimeZone("UTC")
|
|
309
|
+
UTC_NOW = lambda: EWSDateTime.now(tz=UTC) # noqa: E731
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from .errors import InvalidEnumValue
|
|
6
|
+
from .ewsdatetime import EWSDateTime
|
|
7
|
+
from .properties import EWSElement, ExtendedFieldURI
|
|
8
|
+
from .util import (
|
|
9
|
+
TNS,
|
|
10
|
+
add_xml_child,
|
|
11
|
+
create_element,
|
|
12
|
+
get_xml_attr,
|
|
13
|
+
get_xml_attrs,
|
|
14
|
+
is_iterable,
|
|
15
|
+
set_xml_value,
|
|
16
|
+
value_to_xml_text,
|
|
17
|
+
xml_text_to_value,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExtendedProperty(EWSElement):
|
|
24
|
+
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/extendedproperty"""
|
|
25
|
+
|
|
26
|
+
ELEMENT_NAME = "ExtendedProperty"
|
|
27
|
+
|
|
28
|
+
# Enum values: https://docs.microsoft.com/en-us/dotnet/api/exchangewebservices.distinguishedpropertysettype
|
|
29
|
+
DISTINGUISHED_SETS = {
|
|
30
|
+
"Address",
|
|
31
|
+
"Appointment",
|
|
32
|
+
"CalendarAssistant",
|
|
33
|
+
"Common",
|
|
34
|
+
"InternetHeaders",
|
|
35
|
+
"Meeting",
|
|
36
|
+
"PublicStrings",
|
|
37
|
+
"Sharing",
|
|
38
|
+
"Task",
|
|
39
|
+
"UnifiedMessaging",
|
|
40
|
+
}
|
|
41
|
+
# Enum values: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/extendedfielduri
|
|
42
|
+
# The following types cannot be used for setting or getting (see docs) and are thus not very useful here:
|
|
43
|
+
# 'Error'
|
|
44
|
+
# 'Null'
|
|
45
|
+
# 'Object'
|
|
46
|
+
# 'ObjectArray'
|
|
47
|
+
PROPERTY_TYPES = {
|
|
48
|
+
"ApplicationTime",
|
|
49
|
+
"Binary",
|
|
50
|
+
"BinaryArray",
|
|
51
|
+
"Boolean",
|
|
52
|
+
"CLSID",
|
|
53
|
+
"CLSIDArray",
|
|
54
|
+
"Currency",
|
|
55
|
+
"CurrencyArray",
|
|
56
|
+
"Double",
|
|
57
|
+
"DoubleArray",
|
|
58
|
+
"Float",
|
|
59
|
+
"FloatArray",
|
|
60
|
+
"Integer",
|
|
61
|
+
"IntegerArray",
|
|
62
|
+
"Long",
|
|
63
|
+
"LongArray",
|
|
64
|
+
"Short",
|
|
65
|
+
"ShortArray",
|
|
66
|
+
"SystemTime",
|
|
67
|
+
"SystemTimeArray",
|
|
68
|
+
"String",
|
|
69
|
+
"StringArray",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Translation table between common distinguished_property_set_id and property_set_id values. See
|
|
73
|
+
# https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/commonly-used-property-sets
|
|
74
|
+
# ID values must be lowercase.
|
|
75
|
+
DISTINGUISHED_SET_NAME_TO_ID_MAP = {
|
|
76
|
+
"Address": "00062004-0000-0000-c000-000000000046",
|
|
77
|
+
"AirSync": "71035549-0739-4dcb-9163-00f0580dbbdf",
|
|
78
|
+
"Appointment": "00062002-0000-0000-c000-000000000046",
|
|
79
|
+
"Common": "00062008-0000-0000-c000-000000000046",
|
|
80
|
+
"InternetHeaders": "00020386-0000-0000-c000-000000000046",
|
|
81
|
+
"Log": "0006200a-0000-0000-c000-000000000046",
|
|
82
|
+
"Mapi": "00020328-0000-0000-c000-000000000046",
|
|
83
|
+
"Meeting": "6ed8da90-450b-101b-98da-00aa003f1305",
|
|
84
|
+
"Messaging": "41f28f13-83f4-4114-a584-eedb5a6b0bff",
|
|
85
|
+
"Note": "0006200e-0000-0000-c000-000000000046",
|
|
86
|
+
"PostRss": "00062041-0000-0000-c000-000000000046",
|
|
87
|
+
"PublicStrings": "00020329-0000-0000-c000-000000000046",
|
|
88
|
+
"Remote": "00062014-0000-0000-c000-000000000046",
|
|
89
|
+
"Report": "00062013-0000-0000-c000-000000000046",
|
|
90
|
+
"Sharing": "00062040-0000-0000-c000-000000000046",
|
|
91
|
+
"Task": "00062003-0000-0000-c000-000000000046",
|
|
92
|
+
"UnifiedMessaging": "4442858e-a9e3-4e80-b900-317a210cc15b",
|
|
93
|
+
}
|
|
94
|
+
DISTINGUISHED_SET_ID_TO_NAME_MAP = {v: k for k, v in DISTINGUISHED_SET_NAME_TO_ID_MAP.items()}
|
|
95
|
+
|
|
96
|
+
distinguished_property_set_id = None
|
|
97
|
+
property_set_id = None
|
|
98
|
+
property_tag = None # hex integer (e.g. 0x8000) or string ('0x8000')
|
|
99
|
+
property_name = None
|
|
100
|
+
property_id = None # integer as hex-formatted int (e.g. 0x8000) or normal int (32768)
|
|
101
|
+
property_type = ""
|
|
102
|
+
|
|
103
|
+
__slots__ = ("value",)
|
|
104
|
+
|
|
105
|
+
def __init__(self, *args, **kwargs):
|
|
106
|
+
if not kwargs:
|
|
107
|
+
# Allow to set attributes without keyword
|
|
108
|
+
kwargs = dict(zip(self._slots_keys, args))
|
|
109
|
+
self.value = kwargs.pop("value")
|
|
110
|
+
super().__init__(**kwargs)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def validate_cls(cls):
|
|
114
|
+
# Validate values of class attributes and their interdependencies
|
|
115
|
+
cls._validate_distinguished_property_set_id()
|
|
116
|
+
cls._validate_property_set_id()
|
|
117
|
+
cls._validate_property_tag()
|
|
118
|
+
cls._validate_property_name()
|
|
119
|
+
cls._validate_property_id()
|
|
120
|
+
cls._validate_property_type()
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def _validate_distinguished_property_set_id(cls):
|
|
124
|
+
if cls.distinguished_property_set_id:
|
|
125
|
+
if any([cls.property_set_id, cls.property_tag]):
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"When 'distinguished_property_set_id' is set, 'property_set_id' and 'property_tag' must be None"
|
|
128
|
+
)
|
|
129
|
+
if not any([cls.property_id, cls.property_name]):
|
|
130
|
+
raise ValueError(
|
|
131
|
+
"When 'distinguished_property_set_id' is set, 'property_id' or 'property_name' must also be set"
|
|
132
|
+
)
|
|
133
|
+
if cls.distinguished_property_set_id not in cls.DISTINGUISHED_SETS:
|
|
134
|
+
raise InvalidEnumValue(
|
|
135
|
+
"distinguished_property_set_id", cls.distinguished_property_set_id, cls.DISTINGUISHED_SETS
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def _validate_property_set_id(cls):
|
|
140
|
+
if cls.property_set_id:
|
|
141
|
+
if any([cls.distinguished_property_set_id, cls.property_tag]):
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"When 'property_set_id' is set, 'distinguished_property_set_id' and 'property_tag' must be None"
|
|
144
|
+
)
|
|
145
|
+
if not any([cls.property_id, cls.property_name]):
|
|
146
|
+
raise ValueError("When 'property_set_id' is set, 'property_id' or 'property_name' must also be set")
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def _validate_property_tag(cls):
|
|
150
|
+
if cls.property_tag:
|
|
151
|
+
if any([cls.distinguished_property_set_id, cls.property_set_id, cls.property_name, cls.property_id]):
|
|
152
|
+
raise ValueError("When 'property_tag' is set, only 'property_type' must be set")
|
|
153
|
+
if 0x8000 <= cls.property_tag_as_int() <= 0xFFFE:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"'property_tag' value {cls.property_tag_as_hex()!r} is reserved for custom properties"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def _validate_property_name(cls):
|
|
160
|
+
if cls.property_name:
|
|
161
|
+
if any([cls.property_id, cls.property_tag]):
|
|
162
|
+
raise ValueError("When 'property_name' is set, 'property_id' and 'property_tag' must be None")
|
|
163
|
+
if not any([cls.distinguished_property_set_id, cls.property_set_id]):
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"When 'property_name' is set, 'distinguished_property_set_id' or 'property_set_id' must also be set"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def _validate_property_id(cls):
|
|
170
|
+
if cls.property_id:
|
|
171
|
+
if any([cls.property_name, cls.property_tag]):
|
|
172
|
+
raise ValueError("When 'property_id' is set, 'property_name' and 'property_tag' must be None")
|
|
173
|
+
if not any([cls.distinguished_property_set_id, cls.property_set_id]):
|
|
174
|
+
raise ValueError(
|
|
175
|
+
"When 'property_id' is set, 'distinguished_property_set_id' or 'property_set_id' must also be set"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def _validate_property_type(cls):
|
|
180
|
+
if cls.property_type not in cls.PROPERTY_TYPES:
|
|
181
|
+
raise InvalidEnumValue("property_type", cls.property_type, cls.PROPERTY_TYPES)
|
|
182
|
+
|
|
183
|
+
def clean(self, version=None):
|
|
184
|
+
self.validate_cls()
|
|
185
|
+
python_type = self.python_type()
|
|
186
|
+
if self.is_array_type():
|
|
187
|
+
if not is_iterable(self.value):
|
|
188
|
+
raise TypeError(f"Field {self.__class__.__name__!r} value {self.value!r} must be of type {list}")
|
|
189
|
+
for v in self.value:
|
|
190
|
+
if not isinstance(v, python_type):
|
|
191
|
+
raise TypeError(f"Field {self.__class__.__name__!r} list value {v!r} must be of type {python_type}")
|
|
192
|
+
else:
|
|
193
|
+
if not isinstance(self.value, python_type):
|
|
194
|
+
raise TypeError(f"Field {self.__class__.__name__!r} value {self.value!r} must be of type {python_type}")
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def _normalize_obj(cls, obj):
|
|
198
|
+
# Sometimes, EWS will helpfully translate a 'distinguished_property_set_id' value to a 'property_set_id' value
|
|
199
|
+
# and vice versa. Align these values on an ExtendedFieldURI instance.
|
|
200
|
+
try:
|
|
201
|
+
obj.property_set_id = cls.DISTINGUISHED_SET_NAME_TO_ID_MAP[obj.distinguished_property_set_id]
|
|
202
|
+
except KeyError:
|
|
203
|
+
with suppress(KeyError):
|
|
204
|
+
obj.distinguished_property_set_id = cls.DISTINGUISHED_SET_ID_TO_NAME_MAP[obj.property_set_id]
|
|
205
|
+
return obj
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def is_property_instance(cls, elem):
|
|
209
|
+
"""Return whether an 'ExtendedProperty' element matches the definition for this class. Extended property fields
|
|
210
|
+
do not have a name, so we must match on the cls.property_* attributes to match a field in the request with a
|
|
211
|
+
field in the response.
|
|
212
|
+
"""
|
|
213
|
+
# We can't use ExtendedFieldURI.from_xml(). It clears the XML element, but we may not want to consume it here.
|
|
214
|
+
kwargs = {
|
|
215
|
+
f.name: f.from_xml(elem=elem.find(ExtendedFieldURI.response_tag()), account=None)
|
|
216
|
+
for f in ExtendedFieldURI.FIELDS
|
|
217
|
+
}
|
|
218
|
+
xml_obj = ExtendedFieldURI(**kwargs)
|
|
219
|
+
cls_obj = cls.as_object()
|
|
220
|
+
return cls._normalize_obj(cls_obj) == cls._normalize_obj(xml_obj)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def from_xml(cls, elem, account):
|
|
224
|
+
# Gets value of this specific ExtendedProperty from a list of 'ExtendedProperty' XML elements
|
|
225
|
+
python_type = cls.python_type()
|
|
226
|
+
if cls.is_array_type():
|
|
227
|
+
values = elem.find(f"{{{TNS}}}Values")
|
|
228
|
+
return [
|
|
229
|
+
xml_text_to_value(value=val, value_type=python_type) for val in get_xml_attrs(values, f"{{{TNS}}}Value")
|
|
230
|
+
]
|
|
231
|
+
extended_field_value = xml_text_to_value(value=get_xml_attr(elem, f"{{{TNS}}}Value"), value_type=python_type)
|
|
232
|
+
if python_type == str and not extended_field_value:
|
|
233
|
+
# For string types, we want to return the empty string instead of None if the element was
|
|
234
|
+
# actually found, but there was no XML value. For other types, it would be more problematic
|
|
235
|
+
# to make that distinction, e.g. return False for bool, 0 for int, etc.
|
|
236
|
+
return ""
|
|
237
|
+
return extended_field_value
|
|
238
|
+
|
|
239
|
+
def to_xml(self, version):
|
|
240
|
+
if self.is_array_type():
|
|
241
|
+
values = create_element("t:Values")
|
|
242
|
+
for v in self.value:
|
|
243
|
+
add_xml_child(values, "t:Value", v)
|
|
244
|
+
return values
|
|
245
|
+
return set_xml_value(create_element("t:Value"), self.value, version=version)
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def is_array_type(cls):
|
|
249
|
+
return cls.property_type.endswith("Array")
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def property_tag_as_int(cls):
|
|
253
|
+
if isinstance(cls.property_tag, str):
|
|
254
|
+
return int(cls.property_tag, base=16)
|
|
255
|
+
return cls.property_tag
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def property_tag_as_hex(cls):
|
|
259
|
+
return hex(cls.property_tag) if isinstance(cls.property_tag, int) else cls.property_tag
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def python_type(cls):
|
|
263
|
+
# Return the best equivalent for a Python type for the property type of this class
|
|
264
|
+
base_type = cls.property_type[:-5] if cls.is_array_type() else cls.property_type
|
|
265
|
+
return {
|
|
266
|
+
"ApplicationTime": Decimal,
|
|
267
|
+
"Binary": bytes,
|
|
268
|
+
"Boolean": bool,
|
|
269
|
+
"CLSID": str,
|
|
270
|
+
"Currency": int,
|
|
271
|
+
"Double": Decimal,
|
|
272
|
+
"Float": Decimal,
|
|
273
|
+
"Integer": int,
|
|
274
|
+
"Long": int,
|
|
275
|
+
"Short": int,
|
|
276
|
+
"SystemTime": EWSDateTime,
|
|
277
|
+
"String": str,
|
|
278
|
+
}[base_type]
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def as_object(cls):
|
|
282
|
+
# Return an object we can use to match with the incoming object from XML
|
|
283
|
+
return ExtendedFieldURI(
|
|
284
|
+
distinguished_property_set_id=cls.distinguished_property_set_id,
|
|
285
|
+
property_set_id=cls.property_set_id.lower() if cls.property_set_id else None,
|
|
286
|
+
property_tag=cls.property_tag_as_hex(),
|
|
287
|
+
property_name=cls.property_name,
|
|
288
|
+
property_id=value_to_xml_text(cls.property_id) if cls.property_id else None,
|
|
289
|
+
property_type=cls.property_type,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ExternId(ExtendedProperty):
|
|
294
|
+
"""This is a custom extended property defined by us. It's useful for synchronization purposes, to attach a unique ID
|
|
295
|
+
from an external system.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
property_set_id = "c11ff724-aa03-4555-9952-8fa248a11c3e" # This is arbitrary. We just want a unique UUID.
|
|
299
|
+
property_name = "External ID"
|
|
300
|
+
property_type = "String"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class Flag(ExtendedProperty):
|
|
304
|
+
"""This property returns None for Not Flagged messages, 1 for Completed messages and 2 for Flagged messages.
|
|
305
|
+
|
|
306
|
+
For a description of each status, see:
|
|
307
|
+
https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxoflag/eda9fd25-6407-4cec-9e62-26e4f9d6a098
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
property_tag = 0x1090
|
|
311
|
+
property_type = "Integer"
|