Skip to content

Commit

Permalink
Bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
ecederstrand committed Oct 4, 2021
1 parent d3e39a2 commit 7f7ad41
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 165 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ Change Log
HEAD
----

4.5.2
-----
- Make `FileAttachment.fp` a proper `BytesIO` implementation
- Add missing `CalendarItem.recurrence_id` field
- Add `SingleFolderQuerySet.resolve()` to aid accessing a folder shared by a different account:
```python
from exchangelib import Account
from exchangelib.folders import Calendar, SingleFolderQuerySet
from exchangelib.properties import DistinguishedFolderId, Mailbox

account = Account(primary_smtp_address="[email protected]", ...)
shared_calendar = SingleFolderQuerySet(account=account, folder=DistinguishedFolderId(
id=Calendar.DISTINGUISHED_FOLDER_ID,
mailbox=Mailbox(email_address="[email protected]")
)).resolve()
```
- Minor bugfixes


4.5.1
-----
- Support updating items in `Account.upload()`. Previously, only insert was supported.
Expand Down
185 changes: 101 additions & 84 deletions docs/exchangelib/attachments.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ <h1 class="title">Module <code>exchangelib.attachments</code></h1>
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">import logging
<pre><code class="python">import io
import logging
import mimetypes
from io import BytesIO

from .fields import BooleanField, TextField, IntegerField, URIField, DateTimeField, EWSElementField, Base64Field, \
ItemField, IdField, FieldPath
Expand Down Expand Up @@ -246,43 +246,41 @@ <h1 class="title">Module <code>exchangelib.attachments</code></h1>
return cls(**kwargs)


class FileAttachmentIO(BytesIO):
class FileAttachmentIO(io.RawIOBase):
&#34;&#34;&#34;A BytesIO where the stream of data comes from the GetAttachment service.&#34;&#34;&#34;

def __init__(self, *args, **kwargs):
self._attachment = kwargs.pop(&#39;attachment&#39;)
super().__init__(*args, **kwargs)
def __init__(self, attachment):
self._attachment = attachment
self._overflow = None

def readable(self):
return True

@property
def closed(self):
return self._stream is None

def readinto(self, b):
buf_size = len(b) # We can&#39;t return more than l bytes
try:
chunk = self._overflow or next(self._stream)
except StopIteration:
return 0
else:
output, self._overflow = chunk[:buf_size], chunk[buf_size:]
b[:len(output)] = output
return len(output)

def __enter__(self):
self._stream = GetAttachment(account=self._attachment.parent_item.account).stream_file_content(
attachment_id=self._attachment.attachment_id
)
self._overflow = b&#39;&#39;
return self
self._overflow = None
return io.BufferedReader(self, buffer_size=io.DEFAULT_BUFFER_SIZE)

def __exit__(self, *args, **kwargs):
self._stream = None
self._overflow = None

def read(self, size=-1):
if size &lt; 0:
# Return everything
return b&#39;&#39;.join(self._stream)
# Return only &#39;size&#39; bytes
buffer = [self._overflow]
read_size = len(self._overflow)
while True:
if read_size &gt;= size:
break
try:
next_chunk = next(self._stream)
except StopIteration:
break
buffer.append(next_chunk)
read_size += len(next_chunk)
res = b&#39;&#39;.join(buffer)
self._overflow = res[size:]
return res[:size]</code></pre>
self._overflow = None</code></pre>
</details>
</section>
<section>
Expand Down Expand Up @@ -792,90 +790,107 @@ <h3>Inherited members</h3>
</dd>
<dt id="exchangelib.attachments.FileAttachmentIO"><code class="flex name class">
<span>class <span class="ident">FileAttachmentIO</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>attachment)</span>
</code></dt>
<dd>
<div class="desc"><p>A BytesIO where the stream of data comes from the GetAttachment service.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class FileAttachmentIO(BytesIO):
<pre><code class="python">class FileAttachmentIO(io.RawIOBase):
&#34;&#34;&#34;A BytesIO where the stream of data comes from the GetAttachment service.&#34;&#34;&#34;

def __init__(self, *args, **kwargs):
self._attachment = kwargs.pop(&#39;attachment&#39;)
super().__init__(*args, **kwargs)
def __init__(self, attachment):
self._attachment = attachment
self._overflow = None

def readable(self):
return True

@property
def closed(self):
return self._stream is None

def readinto(self, b):
buf_size = len(b) # We can&#39;t return more than l bytes
try:
chunk = self._overflow or next(self._stream)
except StopIteration:
return 0
else:
output, self._overflow = chunk[:buf_size], chunk[buf_size:]
b[:len(output)] = output
return len(output)

def __enter__(self):
self._stream = GetAttachment(account=self._attachment.parent_item.account).stream_file_content(
attachment_id=self._attachment.attachment_id
)
self._overflow = b&#39;&#39;
return self
self._overflow = None
return io.BufferedReader(self, buffer_size=io.DEFAULT_BUFFER_SIZE)

def __exit__(self, *args, **kwargs):
self._stream = None
self._overflow = None

def read(self, size=-1):
if size &lt; 0:
# Return everything
return b&#39;&#39;.join(self._stream)
# Return only &#39;size&#39; bytes
buffer = [self._overflow]
read_size = len(self._overflow)
while True:
if read_size &gt;= size:
break
try:
next_chunk = next(self._stream)
except StopIteration:
break
buffer.append(next_chunk)
read_size += len(next_chunk)
res = b&#39;&#39;.join(buffer)
self._overflow = res[size:]
return res[:size]</code></pre>
self._overflow = None</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li>_io.BytesIO</li>
<li>_io._BufferedIOBase</li>
<li>io.RawIOBase</li>
<li>_io._RawIOBase</li>
<li>io.IOBase</li>
<li>_io._IOBase</li>
</ul>
<h3>Instance variables</h3>
<dl>
<dt id="exchangelib.attachments.FileAttachmentIO.closed"><code class="name">var <span class="ident">closed</span></code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def closed(self):
return self._stream is None</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="exchangelib.attachments.FileAttachmentIO.read"><code class="name flex">
<span>def <span class="ident">read</span></span>(<span>self, size=-1)</span>
<dt id="exchangelib.attachments.FileAttachmentIO.readable"><code class="name flex">
<span>def <span class="ident">readable</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Read at most size bytes, returned as a bytes object.</p>
<p>If the size argument is negative, read until EOF is reached.
Return an empty bytes object at EOF.</p></div>
<div class="desc"><p>Return whether object was opened for reading.</p>
<p>If False, read() will raise OSError.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def read(self, size=-1):
if size &lt; 0:
# Return everything
return b&#39;&#39;.join(self._stream)
# Return only &#39;size&#39; bytes
buffer = [self._overflow]
read_size = len(self._overflow)
while True:
if read_size &gt;= size:
break
try:
next_chunk = next(self._stream)
except StopIteration:
break
buffer.append(next_chunk)
read_size += len(next_chunk)
res = b&#39;&#39;.join(buffer)
self._overflow = res[size:]
return res[:size]</code></pre>
<pre><code class="python">def readable(self):
return True</code></pre>
</details>
</dd>
<dt id="exchangelib.attachments.FileAttachmentIO.readinto"><code class="name flex">
<span>def <span class="ident">readinto</span></span>(<span>self, b)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def readinto(self, b):
buf_size = len(b) # We can&#39;t return more than l bytes
try:
chunk = self._overflow or next(self._stream)
except StopIteration:
return 0
else:
output, self._overflow = chunk[:buf_size], chunk[buf_size:]
b[:len(output)] = output
return len(output)</code></pre>
</details>
</dd>
</dl>
Expand Down Expand Up @@ -1077,7 +1092,9 @@ <h4><code><a title="exchangelib.attachments.FileAttachment" href="#exchangelib.a
<li>
<h4><code><a title="exchangelib.attachments.FileAttachmentIO" href="#exchangelib.attachments.FileAttachmentIO">FileAttachmentIO</a></code></h4>
<ul class="">
<li><code><a title="exchangelib.attachments.FileAttachmentIO.read" href="#exchangelib.attachments.FileAttachmentIO.read">read</a></code></li>
<li><code><a title="exchangelib.attachments.FileAttachmentIO.closed" href="#exchangelib.attachments.FileAttachmentIO.closed">closed</a></code></li>
<li><code><a title="exchangelib.attachments.FileAttachmentIO.readable" href="#exchangelib.attachments.FileAttachmentIO.readable">readable</a></code></li>
<li><code><a title="exchangelib.attachments.FileAttachmentIO.readinto" href="#exchangelib.attachments.FileAttachmentIO.readinto">readinto</a></code></li>
</ul>
</li>
<li>
Expand Down
42 changes: 18 additions & 24 deletions docs/exchangelib/folders/collections.html
Original file line number Diff line number Diff line change
Expand Up @@ -322,25 +322,22 @@ <h1 class="title">Module <code>exchangelib.folders.collections</code></h1>
has_non_roots = True
return RootOfHierarchy if has_roots else Folder

def _get_default_item_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Associated&#39; traversal depth.
unique_depths = {f.DEFAULT_ITEM_TRAVERSAL_DEPTH for f in self.folders}
def _get_default_traversal_depth(self, traversal_attr):
unique_depths = {getattr(f, traversal_attr) for f in self.folders}
if len(unique_depths) == 1:
return unique_depths.pop()
raise ValueError(
&#39;Folders in this collection do not have a common DEFAULT_ITEM_TRAVERSAL_DEPTH value. You need to &#39;
&#39;define an explicit traversal depth with QuerySet.depth() (values: %s)&#39; % unique_depths
&#39;Folders in this collection do not have a common %s value. You need to define an explicit traversal depth&#39;
&#39;with QuerySet.depth() (values: %s)&#39; % (traversal_attr, unique_depths)
)

def _get_default_item_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Associated&#39; traversal depth.
return self._get_default_traversal_depth(&#39;DEFAULT_ITEM_TRAVERSAL_DEPTH&#39;)

def _get_default_folder_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Deep&#39; traversal depth.
unique_depths = {f.DEFAULT_FOLDER_TRAVERSAL_DEPTH for f in self.folders}
if len(unique_depths) == 1:
return unique_depths.pop()
raise ValueError(
&#39;Folders in this collection do not have a common DEFAULT_FOLDER_TRAVERSAL_DEPTH value. You need to &#39;
&#39;define an explicit traversal depth with FolderQuerySet.depth() (values: %s)&#39; % unique_depths
)
return self._get_default_traversal_depth(&#39;DEFAULT_FOLDER_TRAVERSAL_DEPTH&#39;)

def resolve(self):
# Looks up the folders or folder IDs in the collection and returns full Folder instances with all fields set.
Expand Down Expand Up @@ -809,25 +806,22 @@ <h2 class="section-title" id="header-classes">Classes</h2>
has_non_roots = True
return RootOfHierarchy if has_roots else Folder

def _get_default_item_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Associated&#39; traversal depth.
unique_depths = {f.DEFAULT_ITEM_TRAVERSAL_DEPTH for f in self.folders}
def _get_default_traversal_depth(self, traversal_attr):
unique_depths = {getattr(f, traversal_attr) for f in self.folders}
if len(unique_depths) == 1:
return unique_depths.pop()
raise ValueError(
&#39;Folders in this collection do not have a common DEFAULT_ITEM_TRAVERSAL_DEPTH value. You need to &#39;
&#39;define an explicit traversal depth with QuerySet.depth() (values: %s)&#39; % unique_depths
&#39;Folders in this collection do not have a common %s value. You need to define an explicit traversal depth&#39;
&#39;with QuerySet.depth() (values: %s)&#39; % (traversal_attr, unique_depths)
)

def _get_default_item_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Associated&#39; traversal depth.
return self._get_default_traversal_depth(&#39;DEFAULT_ITEM_TRAVERSAL_DEPTH&#39;)

def _get_default_folder_traversal_depth(self):
# When searching folders, some folders require &#39;Shallow&#39; and others &#39;Deep&#39; traversal depth.
unique_depths = {f.DEFAULT_FOLDER_TRAVERSAL_DEPTH for f in self.folders}
if len(unique_depths) == 1:
return unique_depths.pop()
raise ValueError(
&#39;Folders in this collection do not have a common DEFAULT_FOLDER_TRAVERSAL_DEPTH value. You need to &#39;
&#39;define an explicit traversal depth with FolderQuerySet.depth() (values: %s)&#39; % unique_depths
)
return self._get_default_traversal_depth(&#39;DEFAULT_FOLDER_TRAVERSAL_DEPTH&#39;)

def resolve(self):
# Looks up the folders or folder IDs in the collection and returns full Folder instances with all fields set.
Expand Down
Loading

0 comments on commit 7f7ad41

Please sign in to comment.