Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Erik Cederstrand committed Aug 30, 2023
2 parents a8c2779 + b6d9e82 commit 5937a48
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 29 deletions.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fails to install.
## Setup and connecting

First, specify your credentials. Username is usually in `WINDOMAIN\username` format,
where `WINDOMAIN is` the name of the Windows Domain your username is connected
where `WINDOMAIN` is the name of the Windows Domain your username is connected
to, but some servers also accept usernames in PrimarySMTPAddress
('[email protected]') format (Office365 requires it). UPN format is also
supported, if your server expects that.
Expand Down
22 changes: 14 additions & 8 deletions exchangelib/fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc
import datetime
import logging
import warnings
from contextlib import suppress
from decimal import Decimal, InvalidOperation
from importlib import import_module
Expand Down Expand Up @@ -739,16 +740,21 @@ def clean(self, value, version=None):
def from_xml(self, elem, account):
field_elem = elem.find(self.response_tag())
if field_elem is not None:
ms_id = field_elem.get("Id")
ms_name = field_elem.get("Name")
tz_id = field_elem.get("Id") or field_elem.get("Name")
try:
return self.value_cls.from_ms_id(ms_id or ms_name)
return self.value_cls.from_ms_id(tz_id)
except UnknownTimeZone:
log.warning(
"Cannot convert value '%s' on field '%s' to type %s (unknown timezone ID)",
(ms_id or ms_name),
self.name,
self.value_cls,
warnings.warn(
f"""\
Cannot convert value {tz_id!r} on field {self.name!r} to type {self.value_cls.__name__!r} (unknown timezone ID).
You can fix this by adding a custom entry into the timezone translation map:
from exchangelib.winzone import MS_TIMEZONE_TO_IANA_MAP, CLDR_TO_MS_TIMEZONE_MAP
# Replace "Some_Region/Some_Location" with a reasonable value from CLDR_TO_MS_TIMEZONE_MAP.keys()
MS_TIMEZONE_TO_IANA_MAP[{tz_id!r}] = "Some_Region/Some_Location"
# Your code here"""
)
return None
return self.default
Expand Down
18 changes: 12 additions & 6 deletions exchangelib/items/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def send_and_save(

@require_id
def create_reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None, author=None):
if to_recipients is None:
if not to_recipients:
if not self.author:
raise ValueError("'to_recipients' must be set when message has no 'author'")
to_recipients = [self.author]
Expand All @@ -141,17 +141,23 @@ def reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recip

@require_id
def create_reply_all(self, subject, body, author=None):
to_recipients = list(self.to_recipients) if self.to_recipients else []
me = MailboxField().clean(self.account.primary_smtp_address.lower())
to_recipients = set(self.to_recipients or [])
to_recipients.discard(me)
cc_recipients = set(self.cc_recipients or [])
cc_recipients.discard(me)
bcc_recipients = set(self.bcc_recipients or [])
bcc_recipients.discard(me)
if self.author:
to_recipients.append(self.author)
to_recipients.add(self.author)
return ReplyAllToItem(
account=self.account,
reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
subject=subject,
new_body=body,
to_recipients=to_recipients,
cc_recipients=self.cc_recipients,
bcc_recipients=self.bcc_recipients,
to_recipients=list(to_recipients),
cc_recipients=list(cc_recipients),
bcc_recipients=list(bcc_recipients),
author=author,
)

Expand Down
20 changes: 19 additions & 1 deletion tests/test_field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import warnings
from collections import namedtuple
from decimal import Decimal

Expand Down Expand Up @@ -176,7 +177,24 @@ def test_garbage_input(self):
</Envelope>"""
elem = to_xml(payload).find(f"{{{TNS}}}Item")
field = TimeZoneField("foo", field_uri="item:Foo", default="DUMMY")
self.assertEqual(field.from_xml(elem=elem, account=account), None)

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
tz = field.from_xml(elem=elem, account=account)
self.assertEqual(tz, None)
self.assertEqual(
str(w[0].message),
"""\
Cannot convert value 'THIS_IS_GARBAGE' on field 'foo' to type 'EWSTimeZone' (unknown timezone ID).
You can fix this by adding a custom entry into the timezone translation map:
from exchangelib.winzone import MS_TIMEZONE_TO_IANA_MAP, CLDR_TO_MS_TIMEZONE_MAP
# Replace "Some_Region/Some_Location" with a reasonable value from CLDR_TO_MS_TIMEZONE_MAP.keys()
MS_TIMEZONE_TO_IANA_MAP['THIS_IS_GARBAGE'] = "Some_Region/Some_Location"
# Your code here""",
)

def test_versioned_field(self):
field = TextField("foo", field_uri="bar", supported_from=EXCHANGE_2010)
Expand Down
5 changes: 3 additions & 2 deletions tests/test_items/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,11 @@ def get_random_update_kwargs(self, item, insert_kwargs):
update_kwargs["end"] = (update_kwargs["end"] + datetime.timedelta(days=1)).date()
return update_kwargs

def get_test_item(self, folder=None, categories=None):
def get_test_item(self, categories=None, **kwargs):
folder = kwargs.pop("folder", self.test_folder)
item_kwargs = self.get_random_insert_kwargs()
item_kwargs["categories"] = categories or self.categories
return self.ITEM_CLASS(folder=folder or self.test_folder, **item_kwargs)
return self.ITEM_CLASS(account=self.account, folder=folder, **item_kwargs)

def get_test_folder(self, folder=None):
return self.FOLDER_CLASS(parent=folder or self.test_folder, name=get_random_string(8))
Expand Down
24 changes: 13 additions & 11 deletions tests/test_items/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def get_incoming_message(self, subject):

def test_send(self):
# Test that we can send (only) Message items
item = self.get_test_item()
item.folder = None
item = self.get_test_item(folder=None)
item.send()
self.assertIsNone(item.id)
self.assertIsNone(item.changekey)
Expand All @@ -52,8 +51,7 @@ def test_send_pre_2013(self):
self.assertIsNone(item.changekey)

def test_send_no_copy(self):
item = self.get_test_item()
item.folder = None
item = self.get_test_item(folder=None)
item.send(save_copy=False)
self.assertIsNone(item.id)
self.assertIsNone(item.changekey)
Expand Down Expand Up @@ -98,8 +96,7 @@ def test_send_and_copy_to_folder(self):

def test_reply(self):
# Test that we can reply to a Message item. EWS only allows items that have been sent to receive a reply
item = self.get_test_item()
item.folder = None
item = self.get_test_item(folder=None)
item.send() # get_test_item() sets the to_recipients to the test account
sent_item = self.get_incoming_message(item.subject)
new_subject = (f"Re: {sent_item.subject}")[:255]
Expand All @@ -109,7 +106,6 @@ def test_reply(self):
def test_create_reply(self):
# Test that we can save a reply without sending it
item = self.get_test_item(folder=None)
item.folder = None
item.send()
sent_item = self.get_incoming_message(item.subject)
new_subject = (f"Re: {sent_item.subject}")[:255]
Expand All @@ -133,19 +129,26 @@ def test_reply_all(self):
with self.assertRaises(TypeError) as e:
ReplyToItem(account="XXX")
self.assertEqual(e.exception.args[0], "'account' 'XXX' must be of type <class 'exchangelib.account.Account'>")

# Test that to_recipients only has one entry even when we are both the sender and the receiver
item = self.get_test_item(folder=None)
item.id, item.changekey = 123, 456
reply_item = item.create_reply_all(subject="", body="")
self.assertEqual(reply_item.to_recipients, item.to_recipients)
self.assertEqual(reply_item.to_recipients, item.to_recipients)
self.assertEqual(reply_item.to_recipients, item.to_recipients)

# Test that we can reply-all a Message item. EWS only allows items that have been sent to receive a reply
item = self.get_test_item(folder=None)
item.folder = None
item.send()
sent_item = self.get_incoming_message(item.subject)
new_subject = (f"Re: {sent_item.subject}")[:255]
new_subject = f"Re: {sent_item.subject}"[:255]
sent_item.reply_all(subject=new_subject, body="Hello reply")
self.assertEqual(self.account.sent.filter(subject=new_subject).count(), 1)

def test_forward(self):
# Test that we can forward a Message item. EWS only allows items that have been sent to receive a reply
item = self.get_test_item(folder=None)
item.folder = None
item.send()
sent_item = self.get_incoming_message(item.subject)
new_subject = (f"Re: {sent_item.subject}")[:255]
Expand All @@ -155,7 +158,6 @@ def test_forward(self):
def test_create_forward(self):
# Test that we can forward a Message item. EWS only allows items that have been sent to receive a reply
item = self.get_test_item(folder=None)
item.folder = None
item.send()
sent_item = self.get_incoming_message(item.subject)
new_subject = (f"Re: {sent_item.subject}")[:255]
Expand Down

0 comments on commit 5937a48

Please sign in to comment.