Skip to content

Commit

Permalink
fix: Rename Rule.rule_id to Rule.id. Update API to require full rule …
Browse files Browse the repository at this point in the history
…objects, and to set and unset Rule.id automatically. Blacken.
  • Loading branch information
ecederstrand committed Mar 3, 2024
1 parent 2ac9216 commit 3f7ead2
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 114 deletions.
59 changes: 25 additions & 34 deletions exchangelib/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
from .autodiscover import Autodiscovery
from .configuration import Configuration
from .credentials import ACCESS_TYPES, DELEGATE, IMPERSONATION
from .errors import (
ErrorInvalidArgument,
ErrorItemNotFound,
InvalidEnumValue,
InvalidTypeError,
ResponseMessageError,
UnknownTimeZone,
)
from .errors import ErrorItemNotFound, InvalidEnumValue, InvalidTypeError, ResponseMessageError, UnknownTimeZone
from .ewsdatetime import UTC, EWSTimeZone
from .fields import FieldPath, TextField
from .folders import (
Expand Down Expand Up @@ -764,44 +757,42 @@ def rules(self):
return list(GetInboxRules(account=self).call())

def create_rule(self, rule: Rule):
"""Create an Inbox rule in a user's mailbox in the Exchange store.
"""Create an Inbox rule.
:param rule: The rule to create with display_name as the key at least.
:return: The rule ID of the created rule. If failed, raise an error.
:param rule: The rule to create. Must have at least 'display_name' set.
:return: None if success, else raises an error.
"""
create_rule_service = CreateInboxRule(account=self)
create_rule_service.get(rule=rule, remove_outlook_rule_blob=True)
CreateInboxRule(account=self).get(rule=rule, remove_outlook_rule_blob=True)
# After creating the rule, query all rules,
# find the rule that was just created, and return its ID.
rules = GetInboxRules(account=self).call()
for i in rules:
if i.display_name == rule.display_name:
return i.rule_id
raise ResponseMessageError(f"Failed to create rule ({rule.display_name})!")
try:
rule.id = {i.display_name: i for i in GetInboxRules(account=self).call()}[rule.display_name].id
except KeyError:
raise ResponseMessageError(f"Failed to create rule ({rule.display_name})!")

def set_rule(self, rule: Rule):
"""Modify an Inbox rule in a user's mailbox in the Exchange store.
"""Modify an Inbox rule.
:param rule: The rule to set with rule_id as the key at least.
:return: None if success, else raise an error.
:param rule: The rule to set. Must have an ID.
:return: None if success, else raises an error.
"""
return SetInboxRule(account=self).get(rule=rule)
SetInboxRule(account=self).get(rule=rule)

def delete_rule(self, rule: Rule):
"""Delete an Inbox rule in a user's mailbox in the Exchange store.
"""Delete an Inbox rule.
:param rule: The rule to delete with rule_id or display_name as the key at least.
:return: None if success, else raise an error.
:param rule: The rule to delete. Must have ID or 'display_name'.
:return: None if success, else raises an error.
"""
if rule.rule_id:
return DeleteInboxRule(account=self).get(rule_id=rule.rule_id)
if rule.display_name:
rules = GetInboxRules(account=self).call()
for _rule in rules:
if _rule.display_name == rule.display_name:
return DeleteInboxRule(account=self).get(rule_id=_rule.rule_id)
raise ErrorItemNotFound(f"rule ({rule.display_name}) not found in the mailbox!")
raise ErrorInvalidArgument("rule.rule_id or rule.display_name is required!")
if not rule.id:
if not rule.display_name:
raise ValueError("Rule must have ID or display_name")
try:
rule = {i.display_name: i for i in GetInboxRules(account=self).call()}[rule.display_name]
except KeyError:
raise ErrorItemNotFound(f"No rule with name {rule.display_name!r}")
DeleteInboxRule(account=self).get(rule=rule)
rule.id = None

def subscribe_to_pull(self, event_types=None, watermark=None, timeout=60):
"""Create a pull subscription.
Expand Down
61 changes: 22 additions & 39 deletions exchangelib/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -2153,8 +2153,7 @@ class WithinDateRange(EWSElement):
ELEMENT_NAME = "DateRange"
NAMESPACE = MNS

start_date_time = DateTimeField(
field_uri="StartDateTime", is_required=True)
start_date_time = DateTimeField(field_uri="StartDateTime", is_required=True)
end_date_time = DateTimeField(field_uri="EndDateTime", is_required=True)


Expand All @@ -2179,16 +2178,12 @@ class Conditions(EWSElement):
categories = CharListField(field_uri="Categories")
contains_body_strings = CharListField(field_uri="ContainsBodyStrings")
contains_header_strings = CharListField(field_uri="ContainsHeaderStrings")
contains_recipient_strings = CharListField(
field_uri="ContainsRecipientStrings")
contains_recipient_strings = CharListField(field_uri="ContainsRecipientStrings")
contains_sender_strings = CharListField(field_uri="ContainsSenderStrings")
contains_subject_or_body_strings = CharListField(
field_uri="ContainsSubjectOrBodyStrings")
contains_subject_strings = CharListField(
field_uri="ContainsSubjectStrings")
contains_subject_or_body_strings = CharListField(field_uri="ContainsSubjectOrBodyStrings")
contains_subject_strings = CharListField(field_uri="ContainsSubjectStrings")
flagged_for_action = FlaggedForActionField(field_uri="FlaggedForAction")
from_addresses = EWSElementField(
value_cls=Mailbox, field_uri="FromAddresses")
from_addresses = EWSElementField(value_cls=Mailbox, field_uri="FromAddresses")
from_connected_accounts = CharListField(field_uri="FromConnectedAccounts")
has_attachments = BooleanField(field_uri="HasAttachments")
importance = ImportanceField(field_uri="Importance")
Expand All @@ -2208,15 +2203,12 @@ class Conditions(EWSElement):
not_sent_to_me = BooleanField(field_uri="NotSentToMe")
sent_cc_me = BooleanField(field_uri="SentCcMe")
sent_only_to_me = BooleanField(field_uri="SentOnlyToMe")
sent_to_addresses = EWSElementField(
value_cls=Mailbox, field_uri="SentToAddresses")
sent_to_addresses = EWSElementField(value_cls=Mailbox, field_uri="SentToAddresses")
sent_to_me = BooleanField(field_uri="SentToMe")
sent_to_or_cc_me = BooleanField(field_uri="SentToOrCcMe")
sensitivity = SensitivityField(field_uri="Sensitivity")
within_date_range = EWSElementField(
value_cls=WithinDateRange, field_uri="WithinDateRange")
within_size_range = EWSElementField(
value_cls=WithinSizeRange, field_uri="WithinSizeRange")
within_date_range = EWSElementField(value_cls=WithinDateRange, field_uri="WithinDateRange")
within_size_range = EWSElementField(value_cls=WithinSizeRange, field_uri="WithinSizeRange")


class Exceptions(Conditions):
Expand All @@ -2233,8 +2225,7 @@ class CopyToFolder(EWSElement):
NAMESPACE = MNS

folder_id = EWSElementField(value_cls=FolderId, field_uri="FolderId")
distinguished_folder_id = EWSElementField(
value_cls=DistinguishedFolderId, field_uri="DistinguishedFolderId")
distinguished_folder_id = EWSElementField(value_cls=DistinguishedFolderId, field_uri="DistinguishedFolderId")


class MoveToFolder(CopyToFolder):
Expand All @@ -2250,24 +2241,19 @@ class Actions(EWSElement):
NAMESPACE = TNS

assign_categories = CharListField(field_uri="AssignCategories")
copy_to_folder = EWSElementField(
value_cls=CopyToFolder, field_uri="CopyToFolder")
copy_to_folder = EWSElementField(value_cls=CopyToFolder, field_uri="CopyToFolder")
delete = BooleanField(field_uri="Delete")
forward_as_attachment_to_recipients = EWSElementField(
value_cls=Mailbox, field_uri="ForwardAsAttachmentToRecipients")
forward_to_recipients = EWSElementField(
value_cls=Mailbox, field_uri="ForwardToRecipients")
value_cls=Mailbox, field_uri="ForwardAsAttachmentToRecipients"
)
forward_to_recipients = EWSElementField(value_cls=Mailbox, field_uri="ForwardToRecipients")
mark_importance = ImportanceField(field_uri="MarkImportance")
mark_as_read = BooleanField(field_uri="MarkAsRead")
move_to_folder = EWSElementField(
value_cls=MoveToFolder, field_uri="MoveToFolder")
move_to_folder = EWSElementField(value_cls=MoveToFolder, field_uri="MoveToFolder")
permanent_delete = BooleanField(field_uri="PermanentDelete")
redirect_to_recipients = EWSElementField(
value_cls=Mailbox, field_uri="RedirectToRecipients")
send_sms_alert_to_recipients = EWSElementField(
value_cls=Mailbox, field_uri="SendSMSAlertToRecipients")
server_reply_with_message = EWSElementField(
value_cls=ItemId, field_uri="ServerReplyWithMessage")
redirect_to_recipients = EWSElementField(value_cls=Mailbox, field_uri="RedirectToRecipients")
send_sms_alert_to_recipients = EWSElementField(value_cls=Mailbox, field_uri="SendSMSAlertToRecipients")
server_reply_with_message = EWSElementField(value_cls=ItemId, field_uri="ServerReplyWithMessage")
stop_processing_rules = BooleanField(field_uri="StopProcessingRules")


Expand All @@ -2277,7 +2263,7 @@ class Rule(EWSElement):
ELEMENT_NAME = "Rule"
NAMESPACE = TNS

rule_id = CharField(field_uri="RuleId")
id = CharField(field_uri="RuleId")
display_name = CharField(field_uri="DisplayName")
priority = IntegerField(field_uri="Priority")
is_enabled = BooleanField(field_uri="IsEnabled")
Expand Down Expand Up @@ -2321,7 +2307,7 @@ class DeleteRuleOperation(EWSElement):
ELEMENT_NAME = "DeleteRuleOperation"
NAMESPACE = TNS

rule_id = CharField(field_uri="RuleId")
id = CharField(field_uri="RuleId")


class Operations(EWSElement):
Expand All @@ -2330,9 +2316,6 @@ class Operations(EWSElement):
ELEMENT_NAME = "Operations"
NAMESPACE = MNS

create_rule_operation = EWSElementField(
value_cls=CreateRuleOperation)
set_rule_operation = EWSElementField(
value_cls=SetRuleOperation)
delete_rule_operation = EWSElementField(
value_cls=DeleteRuleOperation)
create_rule_operation = EWSElementField(value_cls=CreateRuleOperation)
set_rule_operation = EWSElementField(value_cls=SetRuleOperation)
delete_rule_operation = EWSElementField(value_cls=DeleteRuleOperation)
2 changes: 1 addition & 1 deletion exchangelib/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@
"GetInboxRules",
"CreateInboxRule",
"SetInboxRule",
"DeleteInboxRule"
"DeleteInboxRule",
]
65 changes: 26 additions & 39 deletions exchangelib/services/inbox_rules.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Any, Generator, Optional, Union

from ..errors import ErrorInvalidOperation
from ..fields import CharField
from ..properties import CreateRuleOperation, DeleteRuleOperation, InboxRules, Operations, Rule, SetRuleOperation
from ..util import MNS, add_xml_child, create_element, get_xml_attr, set_xml_value
from ..version import EXCHANGE_2010
Expand All @@ -18,9 +17,7 @@ class GetInboxRules(EWSAccountService):
SERVICE_NAME = "GetInboxRules"
supported_from = EXCHANGE_2010
element_container_name = InboxRules.response_tag()
ERRORS_TO_CATCH_IN_RESPONSE = EWSAccountService.ERRORS_TO_CATCH_IN_RESPONSE + (
ErrorInvalidOperation,
)
ERRORS_TO_CATCH_IN_RESPONSE = EWSAccountService.ERRORS_TO_CATCH_IN_RESPONSE + (ErrorInvalidOperation,)

def call(self, mailbox: Optional[str] = None) -> Generator[Union[Rule, Exception, None], Any, None]:
if not mailbox:
Expand All @@ -34,7 +31,7 @@ def _elem_to_obj(self, elem):

def get_payload(self, mailbox):
payload = create_element(f"m:{self.SERVICE_NAME}")
add_xml_child(payload, 'm:MailboxSmtpAddress', mailbox)
add_xml_child(payload, "m:MailboxSmtpAddress", mailbox)
return payload

def _get_element_container(self, message, name=None):
Expand All @@ -54,36 +51,32 @@ class UpdateInboxRules(EWSAccountService):
UpdateInboxRules is used to create an Inbox rule, to set an Inbox rule, or to delete an Inbox rule.
When you use the UpdateInboxRules operation, Exchange Web Services deletes client-side send rules.
Client-side send rules are stored on the client in the rule Folder Associated Information (FAI) Message and nowhere else.
EWS deletes this rule FAI message by default, based on the expectation that Outlook will recreate it.
However, Outlook can't recreate rules that don't also exist as an extended rule, and client-side send rules don't exist as extended rules.
As a result, these rules are lost. We suggest you consider this when designing your solution.
Client-side send rules are stored on the client in the rule Folder Associated Information (FAI) Message and nowhere
else. EWS deletes this rule FAI message by default, based on the expectation that Outlook will recreate it.
However, Outlook can't recreate rules that don't also exist as an extended rule, and client-side send rules don't
exist as extended rules. As a result, these rules are lost. We suggest you consider this when designing your
solution.
"""

SERVICE_NAME = "UpdateInboxRules"
supported_from = EXCHANGE_2010
ERRORS_TO_CATCH_IN_RESPONSE = EWSAccountService.ERRORS_TO_CATCH_IN_RESPONSE + (
ErrorInvalidOperation,
)
ERRORS_TO_CATCH_IN_RESPONSE = EWSAccountService.ERRORS_TO_CATCH_IN_RESPONSE + (ErrorInvalidOperation,)


class CreateInboxRule(UpdateInboxRules):
"""
MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateinboxrules-operation#updateinboxrules-create-rule-request-example
MSDN:
https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateinboxrules-operation#updateinboxrules-create-rule-request-example
"""

def call(self, rule: Rule, remove_outlook_rule_blob: bool = True):
payload = self.get_payload(
rule=rule, remove_outlook_rule_blob=remove_outlook_rule_blob)
payload = self.get_payload(rule=rule, remove_outlook_rule_blob=remove_outlook_rule_blob)
return self._get_elements(payload=payload)

def get_payload(self, rule: Rule,
remove_outlook_rule_blob: bool = True):
def get_payload(self, rule: Rule, remove_outlook_rule_blob: bool = True):
payload = create_element(f"m:{self.SERVICE_NAME}")
add_xml_child(payload, 'm:RemoveOutlookRuleBlob',
remove_outlook_rule_blob)
operations = Operations(
create_rule_operation=CreateRuleOperation(rule=rule))
add_xml_child(payload, "m:RemoveOutlookRuleBlob", remove_outlook_rule_blob)
operations = Operations(create_rule_operation=CreateRuleOperation(rule=rule))
set_xml_value(payload, operations, version=self.account.version)
return payload

Expand All @@ -95,17 +88,14 @@ class SetInboxRule(UpdateInboxRules):
"""

def call(self, rule: Rule, remove_outlook_rule_blob: bool = True):
payload = self.get_payload(
rule=rule, remove_outlook_rule_blob=remove_outlook_rule_blob)
payload = self.get_payload(rule=rule, remove_outlook_rule_blob=remove_outlook_rule_blob)
return self._get_elements(payload=payload)

def get_payload(self, rule: Rule,
remove_outlook_rule_blob: bool = True):
if not rule.rule_id:
raise ValueError("rule_id cannot be empty")
def get_payload(self, rule: Rule, remove_outlook_rule_blob: bool = True):
if not rule.id:
raise ValueError("Rule must have an ID")
payload = create_element(f"m:{self.SERVICE_NAME}")
add_xml_child(payload, 'm:RemoveOutlookRuleBlob',
remove_outlook_rule_blob)
add_xml_child(payload, "m:RemoveOutlookRuleBlob", remove_outlook_rule_blob)
operations = Operations(set_rule_operation=SetRuleOperation(rule=rule))
set_xml_value(payload, operations, version=self.account.version)
return payload
Expand All @@ -117,18 +107,15 @@ class DeleteInboxRule(UpdateInboxRules):
https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/updateinboxrules-operation#updateinboxrules-delete-rule-request-example
"""

def call(self, rule_id: Union[str, CharField], remove_outlook_rule_blob: bool = True):
payload = self.get_payload(
rule_id=rule_id, remove_outlook_rule_blob=remove_outlook_rule_blob)
def call(self, rule: Rule, remove_outlook_rule_blob: bool = True):
payload = self.get_payload(rule=rule, remove_outlook_rule_blob=remove_outlook_rule_blob)
return self._get_elements(payload=payload)

def get_payload(self, rule_id: str, remove_outlook_rule_blob: bool = True):
if not rule_id:
raise ValueError("rule_id cannot be empty")
def get_payload(self, rule: Rule, remove_outlook_rule_blob: bool = True):
if not rule.id:
raise ValueError("Rule must have an ID")
payload = create_element(f"m:{self.SERVICE_NAME}")
add_xml_child(payload, 'm:RemoveOutlookRuleBlob',
remove_outlook_rule_blob)
operations = Operations(
delete_rule_operation=DeleteRuleOperation(rule_id=rule_id))
add_xml_child(payload, "m:RemoveOutlookRuleBlob", remove_outlook_rule_blob)
operations = Operations(delete_rule_operation=DeleteRuleOperation(id=rule.id))
set_xml_value(payload, operations, version=self.account.version)
return payload
Loading

0 comments on commit 3f7ead2

Please sign in to comment.