Skip to content

Commit

Permalink
Bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
ecederstrand committed Dec 7, 2021
1 parent 38b9e2d commit 2794d71
Show file tree
Hide file tree
Showing 19 changed files with 343 additions and 174 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Change Log

HEAD
----


4.6.2
-----

- Fix filtering on array-type extended properties.
- Exceptions in `GetStreamingEvents` responses are now raised.
- Support affinity cookies for pull and streaming subscriptions.
Expand Down
16 changes: 11 additions & 5 deletions docs/exchangelib/account.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ <h1 class="title">Module <code>exchangelib.account</code></h1>

from cached_property import threaded_cached_property

from .autodiscover import discover
from .autodiscover import Autodiscovery
from .configuration import Configuration
from .credentials import DELEGATE, IMPERSONATION, ACCESS_TYPES
from .errors import UnknownTimeZone
Expand Down Expand Up @@ -143,9 +143,9 @@ <h1 class="title">Module <code>exchangelib.account</code></h1>
credentials = config.credentials
else:
retry_policy, auth_type = None, None
self.ad_response, self.protocol = discover(
self.ad_response, self.protocol = Autodiscovery(
email=primary_smtp_address, credentials=credentials, auth_type=auth_type, retry_policy=retry_policy
)
).discover()
primary_smtp_address = self.ad_response.autodiscover_smtp_address
else:
if not config:
Expand All @@ -156,6 +156,9 @@ <h1 class="title">Module <code>exchangelib.account</code></h1>
# Other ways of identifying the account can be added later
self.identity = Identity(primary_smtp_address=primary_smtp_address)

# For maintaining affinity in e.g. subscriptions
self.affinity_cookie = None

# We may need to override the default server version on a per-account basis because Microsoft may report one
# server version up-front but delegate account requests to an older backend server.
self.version = self.protocol.version
Expand Down Expand Up @@ -752,9 +755,9 @@ <h2 class="section-title" id="header-classes">Classes</h2>
credentials = config.credentials
else:
retry_policy, auth_type = None, None
self.ad_response, self.protocol = discover(
self.ad_response, self.protocol = Autodiscovery(
email=primary_smtp_address, credentials=credentials, auth_type=auth_type, retry_policy=retry_policy
)
).discover()
primary_smtp_address = self.ad_response.autodiscover_smtp_address
else:
if not config:
Expand All @@ -765,6 +768,9 @@ <h2 class="section-title" id="header-classes">Classes</h2>
# Other ways of identifying the account can be added later
self.identity = Identity(primary_smtp_address=primary_smtp_address)

# For maintaining affinity in e.g. subscriptions
self.affinity_cookie = None

# We may need to override the default server version on a per-account basis because Microsoft may report one
# server version up-front but delegate account requests to an older backend server.
self.version = self.protocol.version
Expand Down
46 changes: 43 additions & 3 deletions docs/exchangelib/fields.html
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ <h1 class="title">Module <code>exchangelib.fields</code></h1>
))

def supported_choices(self, version):
return [c.value for c in self.choices if c.supports_version(version)]
return tuple(c.value for c in self.choices if c.supports_version(version))


FREE_BUSY_CHOICES = [Choice(&#39;Free&#39;), Choice(&#39;Tentative&#39;), Choice(&#39;Busy&#39;), Choice(&#39;OOF&#39;), Choice(&#39;NoData&#39;),
Expand Down Expand Up @@ -1393,6 +1393,10 @@ <h1 class="title">Module <code>exchangelib.fields</code></h1>
return hash(self.name)


class ExtendedPropertyListField(ExtendedPropertyField):
is_list = True


class ItemField(FieldURIField):
@property
def value_cls(self):
Expand Down Expand Up @@ -2704,7 +2708,7 @@ <h3>Methods</h3>
))

def supported_choices(self, version):
return [c.value for c in self.choices if c.supports_version(version)]</code></pre>
return tuple(c.value for c in self.choices if c.supports_version(version))</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
Expand Down Expand Up @@ -2760,7 +2764,7 @@ <h3>Methods</h3>
<span>Expand source code</span>
</summary>
<pre><code class="python">def supported_choices(self, version):
return [c.value for c in self.choices if c.supports_version(version)]</code></pre>
return tuple(c.value for c in self.choices if c.supports_version(version))</code></pre>
</details>
</dd>
</dl>
Expand Down Expand Up @@ -4097,6 +4101,10 @@ <h3>Ancestors</h3>
<ul class="hlist">
<li><a title="exchangelib.fields.Field" href="#exchangelib.fields.Field">Field</a></li>
</ul>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="exchangelib.fields.ExtendedPropertyListField" href="#exchangelib.fields.ExtendedPropertyListField">ExtendedPropertyListField</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="exchangelib.fields.ExtendedPropertyField.clean"><code class="name flex">
Expand Down Expand Up @@ -4183,6 +4191,32 @@ <h3>Methods</h3>
</dd>
</dl>
</dd>
<dt id="exchangelib.fields.ExtendedPropertyListField"><code class="flex name class">
<span>class <span class="ident">ExtendedPropertyListField</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Holds information related to an item field.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ExtendedPropertyListField(ExtendedPropertyField):
is_list = True</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="exchangelib.fields.ExtendedPropertyField" href="#exchangelib.fields.ExtendedPropertyField">ExtendedPropertyField</a></li>
<li><a title="exchangelib.fields.Field" href="#exchangelib.fields.Field">Field</a></li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="exchangelib.fields.ExtendedPropertyListField.is_list"><code class="name">var <span class="ident">is_list</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="exchangelib.fields.Field"><code class="flex name class">
<span>class <span class="ident">Field</span></span>
<span>(</span><span>name=None, is_required=False, is_required_after_save=False, is_read_only=False, is_read_only_after_send=False, is_searchable=True, is_attribute=False, default=None, supported_from=None, deprecated_from=None)</span>
Expand Down Expand Up @@ -7326,6 +7360,12 @@ <h4><code><a title="exchangelib.fields.ExtendedPropertyField" href="#exchangelib
</ul>
</li>
<li>
<h4><code><a title="exchangelib.fields.ExtendedPropertyListField" href="#exchangelib.fields.ExtendedPropertyListField">ExtendedPropertyListField</a></code></h4>
<ul class="">
<li><code><a title="exchangelib.fields.ExtendedPropertyListField.is_list" href="#exchangelib.fields.ExtendedPropertyListField.is_list">is_list</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="exchangelib.fields.Field" href="#exchangelib.fields.Field">Field</a></code></h4>
<ul class="two-column">
<li><code><a title="exchangelib.fields.Field.clean" href="#exchangelib.fields.Field.clean">clean</a></code></li>
Expand Down
40 changes: 20 additions & 20 deletions docs/exchangelib/folders/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ <h1 class="title">Module <code>exchangelib.folders.base</code></h1>
from .collections import FolderCollection, SyncCompleted
from .queryset import SingleFolderQuerySet, SHALLOW as SHALLOW_FOLDERS, DEEP as DEEP_FOLDERS
from ..errors import ErrorAccessDenied, ErrorFolderNotFound, ErrorCannotEmptyFolder, ErrorCannotDeleteObject, \
ErrorDeleteDistinguishedFolder, ErrorInvalidSubscription, ErrorNoPublicFolderReplicaAvailable, ErrorItemNotFound
ErrorDeleteDistinguishedFolder, ErrorNoPublicFolderReplicaAvailable, ErrorItemNotFound
from ..fields import IntegerField, CharField, FieldPath, EffectiveRightsField, PermissionSetField, EWSElementField, \
Field, IdElementField, InvalidField
from ..items import CalendarItem, RegisterMixIn, ITEM_CLASSES, DELETE_TYPE_CHOICES, HARD_DELETE, \
Expand All @@ -46,7 +46,7 @@ <h1 class="title">Module <code>exchangelib.folders.base</code></h1>
CreateUserConfiguration, UpdateUserConfiguration, DeleteUserConfiguration, SubscribeToPush, SubscribeToPull, \
Unsubscribe, GetEvents, GetStreamingEvents, MoveFolder
from ..services.get_user_configuration import ALL
from ..util import TNS, require_id
from ..util import TNS, require_id, is_iterable
from ..version import Version, EXCHANGE_2007_SP1, EXCHANGE_2010

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -683,11 +683,11 @@ <h1 class="title">Module <code>exchangelib.folders.base</code></h1>
if not notification.more_events:
break

def get_streaming_events(self, subscription_id, connection_timeout=1, max_notifications_returned=None):
def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None):
&#34;&#34;&#34;Get events since the subscription was created, in streaming mode. This method will block as many minutes
as specified by &#39;connection_timeout&#39;.

:param subscription_id: A subscription ID as acquired by .subscribe_to_streaming()
:param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming()
:param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout
is reached.
:param max_notifications_returned: If specified, will exit after receiving this number of notifications
Expand All @@ -699,16 +699,16 @@ <h1 class="title">Module <code>exchangelib.folders.base</code></h1>
# Add 60 seconds to the timeout, to allow us to always get the final message containing ConnectionStatus=Closed
request_timeout = connection_timeout*60 + 60
svc = GetStreamingEvents(account=self.account, timeout=request_timeout)
subscription_ids = subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) \
else [subscription_id_or_ids]
for i, notification in enumerate(
svc.call(subscription_ids=[subscription_id], connection_timeout=connection_timeout),
svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout),
start=1
):
yield notification
if max_notifications_returned and i &gt;= max_notifications_returned:
svc.stop_streaming()
break
if svc.error_subscription_ids:
raise ErrorInvalidSubscription(&#39;Invalid subscription IDs: %s&#39; % svc.error_subscription_ids)

def __floordiv__(self, other):
&#34;&#34;&#34;Support the some_folder // &#39;child_folder&#39; // &#39;child_of_child_folder&#39; navigation syntax.
Expand Down Expand Up @@ -1568,11 +1568,11 @@ <h2 class="section-title" id="header-classes">Classes</h2>
if not notification.more_events:
break

def get_streaming_events(self, subscription_id, connection_timeout=1, max_notifications_returned=None):
def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None):
&#34;&#34;&#34;Get events since the subscription was created, in streaming mode. This method will block as many minutes
as specified by &#39;connection_timeout&#39;.

:param subscription_id: A subscription ID as acquired by .subscribe_to_streaming()
:param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming()
:param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout
is reached.
:param max_notifications_returned: If specified, will exit after receiving this number of notifications
Expand All @@ -1584,16 +1584,16 @@ <h2 class="section-title" id="header-classes">Classes</h2>
# Add 60 seconds to the timeout, to allow us to always get the final message containing ConnectionStatus=Closed
request_timeout = connection_timeout*60 + 60
svc = GetStreamingEvents(account=self.account, timeout=request_timeout)
subscription_ids = subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) \
else [subscription_id_or_ids]
for i, notification in enumerate(
svc.call(subscription_ids=[subscription_id], connection_timeout=connection_timeout),
svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout),
start=1
):
yield notification
if max_notifications_returned and i &gt;= max_notifications_returned:
svc.stop_streaming()
break
if svc.error_subscription_ids:
raise ErrorInvalidSubscription(&#39;Invalid subscription IDs: %s&#39; % svc.error_subscription_ids)

def __floordiv__(self, other):
&#34;&#34;&#34;Support the some_folder // &#39;child_folder&#39; // &#39;child_of_child_folder&#39; navigation syntax.
Expand Down Expand Up @@ -2192,12 +2192,12 @@ <h3>Methods</h3>
</details>
</dd>
<dt id="exchangelib.folders.base.BaseFolder.get_streaming_events"><code class="name flex">
<span>def <span class="ident">get_streaming_events</span></span>(<span>self, subscription_id, connection_timeout=1, max_notifications_returned=None)</span>
<span>def <span class="ident">get_streaming_events</span></span>(<span>self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Get events since the subscription was created, in streaming mode. This method will block as many minutes
as specified by 'connection_timeout'.</p>
<p>:param subscription_id: A subscription ID as acquired by .subscribe_to_streaming()
<p>:param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming()
:param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout
is reached.
:param max_notifications_returned: If specified, will exit after receiving this number of notifications
Expand All @@ -2208,11 +2208,11 @@ <h3>Methods</h3>
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_streaming_events(self, subscription_id, connection_timeout=1, max_notifications_returned=None):
<pre><code class="python">def get_streaming_events(self, subscription_id_or_ids, connection_timeout=1, max_notifications_returned=None):
&#34;&#34;&#34;Get events since the subscription was created, in streaming mode. This method will block as many minutes
as specified by &#39;connection_timeout&#39;.

:param subscription_id: A subscription ID as acquired by .subscribe_to_streaming()
:param subscription_id_or_ids: A subscription ID, or list of IDs, as acquired by .subscribe_to_streaming()
:param connection_timeout: Timeout of the connection, in minutes. The connection is closed after this timeout
is reached.
:param max_notifications_returned: If specified, will exit after receiving this number of notifications
Expand All @@ -2224,16 +2224,16 @@ <h3>Methods</h3>
# Add 60 seconds to the timeout, to allow us to always get the final message containing ConnectionStatus=Closed
request_timeout = connection_timeout*60 + 60
svc = GetStreamingEvents(account=self.account, timeout=request_timeout)
subscription_ids = subscription_id_or_ids if is_iterable(subscription_id_or_ids, generators_allowed=True) \
else [subscription_id_or_ids]
for i, notification in enumerate(
svc.call(subscription_ids=[subscription_id], connection_timeout=connection_timeout),
svc.call(subscription_ids=subscription_ids, connection_timeout=connection_timeout),
start=1
):
yield notification
if max_notifications_returned and i &gt;= max_notifications_returned:
svc.stop_streaming()
break
if svc.error_subscription_ids:
raise ErrorInvalidSubscription(&#39;Invalid subscription IDs: %s&#39; % svc.error_subscription_ids)</code></pre>
break</code></pre>
</details>
</dd>
<dt id="exchangelib.folders.base.BaseFolder.get_user_configuration"><code class="name flex">
Expand Down
4 changes: 2 additions & 2 deletions docs/exchangelib/folders/collections.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ <h1 class="title">Module <code>exchangelib.folders.collections</code></h1>
@threaded_cached_property
def folders(self):
# Resolve the list of folders, in case it&#39;s a generator
return list(self._folders)
return tuple(self._folders)

def __len__(self):
return len(self.folders)
Expand Down Expand Up @@ -553,7 +553,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
@threaded_cached_property
def folders(self):
# Resolve the list of folders, in case it&#39;s a generator
return list(self._folders)
return tuple(self._folders)

def __len__(self):
return len(self.folders)
Expand Down
Loading

0 comments on commit 2794d71

Please sign in to comment.