Skip to content

Commit

Permalink
Feature: 🚀 Add InboxRule implement (#1272)
Browse files Browse the repository at this point in the history
* Feature(services): 🚀 Add InboxRule implement

Fixes #233

Co-authored-by: ecederstrand <[email protected]>
  • Loading branch information
a76yyyy and ecederstrand authored Mar 3, 2024
1 parent c233fa2 commit 7401f51
Show file tree
Hide file tree
Showing 14 changed files with 595 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
ignore = E203, W503
max-line-length = 120
170 changes: 132 additions & 38 deletions docs/index.md

Large diffs are not rendered by default.

51 changes: 49 additions & 2 deletions exchangelib/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .autodiscover import Autodiscovery
from .configuration import Configuration
from .credentials import ACCESS_TYPES, DELEGATE, IMPERSONATION
from .errors import InvalidEnumValue, InvalidTypeError, 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 @@ -56,23 +56,27 @@
)
from .folders.collections import PullSubscription, PushSubscription, StreamingSubscription
from .items import ALL_OCCURRENCES, AUTO_RESOLVE, HARD_DELETE, ID_ONLY, SAVE_ONLY, SEND_TO_NONE
from .properties import EWSElement, Mailbox, SendingAs
from .properties import EWSElement, Mailbox, Rule, SendingAs
from .protocol import Protocol
from .queryset import QuerySet
from .services import (
ArchiveItem,
CopyItem,
CreateInboxRule,
CreateItem,
DeleteInboxRule,
DeleteItem,
ExportItems,
GetDelegate,
GetInboxRules,
GetItem,
GetMailTips,
GetPersona,
GetUserOofSettings,
MarkAsJunk,
MoveItem,
SendItem,
SetInboxRule,
SetUserOofSettings,
SubscribeToPull,
SubscribeToPush,
Expand Down Expand Up @@ -747,6 +751,49 @@ def delegates(self):
"""Return a list of DelegateUser objects representing the delegates that are set on this account."""
return list(GetDelegate(account=self).call(user_ids=None, include_permissions=True))

@property
def rules(self):
"""Return a list of Rule objects representing the rules that are set on this account."""
return list(GetInboxRules(account=self).call())

def create_rule(self, rule: Rule):
"""Create an Inbox rule.
:param rule: The rule to create. Must have at least 'display_name' set.
:return: None if success, else raises an error.
"""
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.
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.
:param rule: The rule to set. Must have an ID.
:return: None if success, else raises an error.
"""
SetInboxRule(account=self).get(rule=rule)

def delete_rule(self, rule: Rule):
"""Delete an Inbox rule.
:param rule: The rule to delete. Must have ID or 'display_name'.
:return: None if success, else raises an error.
"""
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
1 change: 1 addition & 0 deletions exchangelib/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
for ad-hoc access e.g. granted manually by the user.
See https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/impersonation-and-ews-in-exchange
"""

import abc
import logging
from threading import RLock
Expand Down
1 change: 1 addition & 0 deletions exchangelib/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Stores errors specific to this package, and mirrors all the possible errors that EWS can return."""

from urllib.parse import urlparse


Expand Down
62 changes: 62 additions & 0 deletions exchangelib/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1723,3 +1723,65 @@ def from_xml(self, elem, account):
continue
events.append(value_cls.from_xml(elem=event, account=account))
return events or self.default


FLAG_ACTION_CHOICES = [
Choice("Any"),
Choice("Call"),
Choice("DoNotForward"),
Choice("FollowUp"),
Choice("FYI"),
Choice("Forward"),
Choice("NoResponseNecessary"),
Choice("Read"),
Choice("Reply"),
Choice("ReplyToAll"),
Choice("Review"),
]


class FlaggedForActionField(ChoiceField):
"""
A field specifies the flag for action value that
must appear on incoming messages in order for the condition
or exception to apply.
"""

def __init__(self, *args, **kwargs):
kwargs["choices"] = FLAG_ACTION_CHOICES
super().__init__(*args, **kwargs)


IMPORTANCE_CHOICES = [
Choice("Low"),
Choice("Normal"),
Choice("High"),
]


class ImportanceField(ChoiceField):
"""
A field that describes the importance of an item or
the aggregated importance of all items in a conversation
in the current folder.
"""

def __init__(self, *args, **kwargs):
kwargs["choices"] = IMPORTANCE_CHOICES
super().__init__(*args, **kwargs)


SENSITIVITY_CHOICES = [
Choice("Normal"),
Choice("Personal"),
Choice("Private"),
Choice("Confidential"),
]


class SensitivityField(ChoiceField):
"""A field that indicates the sensitivity level of an item."""

def __init__(self, *args, **kwargs):
kwargs["choices"] = SENSITIVITY_CHOICES
super().__init__(*args, **kwargs)
179 changes: 179 additions & 0 deletions exchangelib/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@
ExtendedPropertyField,
Field,
FieldPath,
FlaggedForActionField,
FreeBusyStatusField,
GenericEventListField,
IdElementField,
IdField,
ImportanceField,
IntegerField,
InvalidField,
InvalidFieldForVersion,
Expand All @@ -48,6 +50,7 @@
RecipientAddressField,
ReferenceItemIdField,
RoutingTypeField,
SensitivityField,
SubField,
TextField,
TimeDeltaField,
Expand Down Expand Up @@ -2140,3 +2143,179 @@ def from_xml(cls, elem, account):
user_settings_errors=user_settings_errors,
user_settings=user_settings,
)


class WithinDateRange(EWSElement):
"""MSDN:
https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/withindaterange
"""

ELEMENT_NAME = "DateRange"
NAMESPACE = MNS

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


class WithinSizeRange(EWSElement):
"""MSDN:
https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/withinsizerange
"""

ELEMENT_NAME = "SizeRange"
NAMESPACE = MNS

minimum_size = IntegerField(field_uri="MinimumSize", is_required=True)
maximum_size = IntegerField(field_uri="MaximumSize", is_required=True)


class Conditions(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/conditions"""

ELEMENT_NAME = "Conditions"
NAMESPACE = TNS

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_sender_strings = CharListField(field_uri="ContainsSenderStrings")
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_connected_accounts = CharListField(field_uri="FromConnectedAccounts")
has_attachments = BooleanField(field_uri="HasAttachments")
importance = ImportanceField(field_uri="Importance")
is_approval_request = BooleanField(field_uri="IsApprovalRequest")
is_automatic_forward = BooleanField(field_uri="IsAutomaticForward")
is_automatic_reply = BooleanField(field_uri="IsAutomaticReply")
is_encrypted = BooleanField(field_uri="IsEncrypted")
is_meeting_request = BooleanField(field_uri="IsMeetingRequest")
is_meeting_response = BooleanField(field_uri="IsMeetingResponse")
is_ndr = BooleanField(field_uri="IsNDR")
is_permission_controlled = BooleanField(field_uri="IsPermissionControlled")
is_read_receipt = BooleanField(field_uri="IsReadReceipt")
is_signed = BooleanField(field_uri="IsSigned")
is_voicemail = BooleanField(field_uri="IsVoicemail")
item_classes = CharListField(field_uri="ItemClasses")
message_classifications = CharListField(field_uri="MessageClassifications")
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_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")


class Exceptions(Conditions):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/exceptions"""

ELEMENT_NAME = "Exceptions"
NAMESPACE = TNS


class CopyToFolder(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/copytofolder"""

ELEMENT_NAME = "CopyToFolder"
NAMESPACE = MNS

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


class MoveToFolder(CopyToFolder):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/movetofolder"""

ELEMENT_NAME = "MoveToFolder"


class Actions(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/actions"""

ELEMENT_NAME = "Actions"
NAMESPACE = TNS

assign_categories = CharListField(field_uri="AssignCategories")
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")
mark_importance = ImportanceField(field_uri="MarkImportance")
mark_as_read = BooleanField(field_uri="MarkAsRead")
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")
stop_processing_rules = BooleanField(field_uri="StopProcessingRules")


class Rule(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/rule-ruletype"""

ELEMENT_NAME = "Rule"
NAMESPACE = TNS

id = CharField(field_uri="RuleId")
display_name = CharField(field_uri="DisplayName")
priority = IntegerField(field_uri="Priority")
is_enabled = BooleanField(field_uri="IsEnabled")
is_not_supported = BooleanField(field_uri="IsNotSupported")
is_in_error = BooleanField(field_uri="IsInError")
conditions = EWSElementField(value_cls=Conditions)
exceptions = EWSElementField(value_cls=Exceptions)
actions = EWSElementField(value_cls=Actions)


class InboxRules(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/inboxrules"""

ELEMENT_NAME = "InboxRules"
NAMESPACE = MNS

rule = EWSElementListField(value_cls=Rule)


class CreateRuleOperation(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/createruleoperation"""

ELEMENT_NAME = "CreateRuleOperation"
NAMESPACE = TNS

rule = EWSElementField(value_cls=Rule)


class SetRuleOperation(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/setruleoperation"""

ELEMENT_NAME = "SetRuleOperation"
NAMESPACE = TNS

rule = EWSElementField(value_cls=Rule)


class DeleteRuleOperation(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/deleteruleoperation"""

ELEMENT_NAME = "DeleteRuleOperation"
NAMESPACE = TNS

id = CharField(field_uri="RuleId")


class Operations(EWSElement):
"""MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/operations"""

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)
1 change: 1 addition & 0 deletions exchangelib/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Protocols should be accessed through an Account, and are either created from a default Configuration or autodiscovered
when creating an Account.
"""

import abc
import datetime
import logging
Expand Down
5 changes: 5 additions & 0 deletions exchangelib/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from .get_user_configuration import GetUserConfiguration
from .get_user_oof_settings import GetUserOofSettings
from .get_user_settings import GetUserSettings
from .inbox_rules import CreateInboxRule, DeleteInboxRule, GetInboxRules, SetInboxRule
from .mark_as_junk import MarkAsJunk
from .move_folder import MoveFolder
from .move_item import MoveItem
Expand Down Expand Up @@ -109,4 +110,8 @@
"UpdateItem",
"UpdateUserConfiguration",
"UploadItems",
"GetInboxRules",
"CreateInboxRule",
"SetInboxRule",
"DeleteInboxRule",
]
Loading

0 comments on commit 7401f51

Please sign in to comment.