Codebase list python-exchangelib / 936970c
Update upstream source from tag 'upstream/4.6.1' Update to upstream version '4.6.1' with Debian dir a50bd8e266164237ee1ba461ee4d471e774a6ac5 Michael Fladischer 2 years ago
25 changed file(s) with 729 addition(s) and 653 deletion(s). Raw diff Collapse all Expand all
2828 needs: pre_job
2929 strategy:
3030 matrix:
31 python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
31 python-version: ['3.7', '3.8', '3.9', '3.10']
3232 include:
3333 # Allow failure on Python dev - e.g. Cython install regularly fails
34 - python-version: 3.10-dev
34 - python-version: "3.11-dev"
3535 allowed_failure: true
3636 max-parallel: 1
3737
5757
5858 - name: Install cutting-edge Cython-based packages on Python dev versions
5959 continue-on-error: ${{ matrix.allowed_failure || false }}
60 if: matrix.python-version == '3.10-dev'
60 if: matrix.python-version == '3.11-dev'
6161 run: |
6262 sudo apt-get install libxml2-dev libxslt1-dev
6363 python -m pip install hg+https://foss.heptapod.net/pypy/cffi
9191 - name: Set up Python
9292 uses: actions/setup-python@v2
9393 with:
94 python-version: '3.9'
94 python-version: '3.10'
9595
9696 - name: Unencrypt secret file
9797 env:
33 HEAD
44 ----
55
6 4.6.1
7 -----
8
9 - Support `tzlocal>=4.1`
10 - Bug fixes for paging in multi-folder requests.
611
712 4.6.0
813 -----
14
915 - Support microsecond precision in `EWSDateTime.ewsformat()`
1016 - Remove usage of the `multiprocessing` module to allow running in AWS Lambda
1117 - Support `tzlocal>=4`
1218
13
1419 4.5.2
1520 -----
16 - Make `FileAttachment.fp` a proper `BytesIO` implementation
17 - Add missing `CalendarItem.recurrence_id` field
18 - Add `SingleFolderQuerySet.resolve()` to aid accessing a folder shared by a different account:
21
22 - Make `FileAttachment.fp` a proper `BytesIO` implementation
23 - Add missing `CalendarItem.recurrence_id` field
24 - Add `SingleFolderQuerySet.resolve()` to aid accessing a folder shared by a different account:
25
1926 ```python
2027 from exchangelib import Account
2128 from exchangelib.folders import Calendar, SingleFolderQuerySet
2734 mailbox=Mailbox(email_address="other_user@example.com")
2835 )).resolve()
2936 ```
37
3038 - Minor bugfixes
3139
32
3340 4.5.1
3441 -----
35 - Support updating items in `Account.upload()`. Previously, only insert was supported.
36 - Fixed types for `Contact.manager_mailbox` and `Contact.direct_reports`.
37 - Support getting `text_body` field on item attachments.
38
42
43 - Support updating items in `Account.upload()`. Previously, only insert was supported.
44 - Fixed types for `Contact.manager_mailbox` and `Contact.direct_reports`.
45 - Support getting `text_body` field on item attachments.
3946
4047 4.5.0
4148 -----
42 - Fixed bug when updating indexed fields on `Contact` items.
43 - Fixed bug preventing parsing of `CalendarPermission` items in the `permission_set` field.
44 - Add support for parsing push notification POST requests sent from the Exchange server
45 to the callback URL.
46
49
50 - Fixed bug when updating indexed fields on `Contact` items.
51 - Fixed bug preventing parsing of `CalendarPermission` items in the `permission_set` field.
52 - Add support for parsing push notification POST requests sent from the Exchange server to the callback URL.
4753
4854 4.4.0
4955 -----
50 - Add `Folder.move()` to move folders to a different parent folder.
51
56
57 - Add `Folder.move()` to move folders to a different parent folder.
5258
5359 4.3.0
5460 -----
55 - Add context managers `Folder.pull_subscription()`, `Folder.push_subscription()` and
56 `Folder.streaming_subscription()` that handle unsubscriptions automatically.
57
61
62 - Add context managers `Folder.pull_subscription()`, `Folder.push_subscription()` and
63 `Folder.streaming_subscription()` that handle unsubscriptions automatically.
5864
5965 4.2.0
6066 -----
61 - Move `util._may_retry_on_error` and and `util._raise_response_errors` to
62 `RetryPolicy.may_retry_on_error` and `RetryPolicy.raise_response_errors`, respectively. This allows
63 for easier customization of the retry logic.
64
67
68 - Move `util._may_retry_on_error` and and `util._raise_response_errors` to
69 `RetryPolicy.may_retry_on_error` and `RetryPolicy.raise_response_errors`, respectively. This allows for easier
70 customization of the retry logic.
6571
6672 4.1.0
6773 -----
68 - Add support for synchronization, subscriptions and notifications. Both pull, push and streaming
69 notifications are supported. See https://ecederstrand.github.io/exchangelib/#synchronization-subscriptions-and-notifications
70
74
75 - Add support for synchronization, subscriptions and notifications. Both pull, push and streaming notifications are
76 supported. See https://ecederstrand.github.io/exchangelib/#synchronization-subscriptions-and-notifications
7177
7278 4.0.0
7379 -----
74 - Add a new `max_connections` option for the `Configuration` class, to increase the session pool size
75 on a per-server, per-credentials basis. Useful when exchangelib is used with threads, where one may
76 wish to increase the number of concurrent connections to the server.
77 - Add `Message.mark_as_junk()` and complementary `QuerySet.mark_as_junk()` methods to mark or un-mark
78 messages as junk email, and optionally move them to the junk folder.
79 - Add support for Master Category Lists, also known as User Configurations. These are custom values
80 that can be assigned to folders. Available via `Folder.get_user_configuration()`.
81 - `Persona` objects as returned by `QuerySet.people()` now support almost all documented fields.
82 - Improved `QuerySet.people()` to call the `GetPersona` service if at least one field is requested that
83 is not supported by the `FindPeople` service.
84 - Removed the internal caching in `QuerySet`. It's not necessary in most use cases for exchangelib,
85 and the memory overhead and complexity is not worth the extra effort. This means that `.iterator()`
86 is now a no-op and marked as deprecated. ATTENTION: If you previously relied on caching of results
87 in `QuerySet`, you need to do you own caching now.
88 - Allow plain `date`, `datetime` and `zoneinfo.ZoneInfo` objects as values for fields and methods. This
89 lowers the barrier for using the library. We still use `EWSDate`, `EWSDateTime` and `EWSTimeZone` for
90 all values returned from the server, but these classes are subclasses of `date`, `datetime` and
91 `zoneinfo.ZoneInfo` objects and instances will behave just like instance of their parent class.
92
80
81 - Add a new `max_connections` option for the `Configuration` class, to increase the session pool size on a per-server,
82 per-credentials basis. Useful when exchangelib is used with threads, where one may wish to increase the number of
83 concurrent connections to the server.
84 - Add `Message.mark_as_junk()` and complementary `QuerySet.mark_as_junk()` methods to mark or un-mark messages as junk
85 email, and optionally move them to the junk folder.
86 - Add support for Master Category Lists, also known as User Configurations. These are custom values that can be assigned
87 to folders. Available via `Folder.get_user_configuration()`.
88 - `Persona` objects as returned by `QuerySet.people()` now support almost all documented fields.
89 - Improved `QuerySet.people()` to call the `GetPersona` service if at least one field is requested that is not supported
90 by the `FindPeople` service.
91 - Removed the internal caching in `QuerySet`. It's not necessary in most use cases for exchangelib, and the memory
92 overhead and complexity is not worth the extra effort. This means that `.iterator()`
93 is now a no-op and marked as deprecated. ATTENTION: If you previously relied on caching of results in `QuerySet`, you
94 need to do you own caching now.
95 - Allow plain `date`, `datetime` and `zoneinfo.ZoneInfo` objects as values for fields and methods. This lowers the
96 barrier for using the library. We still use `EWSDate`, `EWSDateTime` and `EWSTimeZone` for all values returned from
97 the server, but these classes are subclasses of `date`, `datetime` and
98 `zoneinfo.ZoneInfo` objects and instances will behave just like instance of their parent class.
9399
94100 3.3.2
95101 -----
96 - Change Kerberos dependency from `requests_kerberos` to `requests_gssapi`
97 - Let `EWSDateTime.from_datetime()` accept `datetime.datetime` objects with `tzinfo` objects that
98 are `dateutil`, `zoneinfo` and `pytz` instances, in addition to `EWSTimeZone`.
99
102
103 - Change Kerberos dependency from `requests_kerberos` to `requests_gssapi`
104 - Let `EWSDateTime.from_datetime()` accept `datetime.datetime` objects with `tzinfo` objects that are `dateutil`
105 , `zoneinfo` and `pytz` instances, in addition to `EWSTimeZone`.
100106
101107 3.3.1
102108 -----
103 - Allow overriding `dns.resolver.Resolver` class attributes via `Autodiscovery.DNS_RESOLVER_ATTRS`.
104
109
110 - Allow overriding `dns.resolver.Resolver` class attributes via `Autodiscovery.DNS_RESOLVER_ATTRS`.
105111
106112 3.3.0
107113 -----
108 - Switch `EWSTimeZone` to be implemented on top of the new `zoneinfo` module in Python 3.9 instead
109 of `pytz`. `backports.zoneinfo` is used for earlier versions of Python. This means that the
110 `ÈWSTimeZone` methods `timezone()`, `normalize()` and `localize()` methods are now deprecated.
111 - Add `EWSTimeZone.from_dateutil()` to support converting `dateutil.tz` timezones to `EWSTimeZone`.
112 - Dropped support for Python 3.5 which is EOL per September 2020.
113 - Added support for `CalendaItem.appointment_state`, `CalendaItem.conflicting_meetings` and
114 `CalendarItem.adjacent_meetings` fields.
115 - Added support for the `Message.reminder_message_data` field.
116 - Added support for `Contact.manager_mailbox`, `Contact.direct_reports` and `Contact.complete_name` fields.
117 - Added support for `Item.response_objects` field.
118 - Changed `Task.due_date` and `Tas.start_date` fields from datetime to date fields, since the time
119 was being truncated anyway by the server.
120 - Added support for `Task.recurrence` field.
121 - Added read-only support for `Contact.user_smime_certificate` and `Contact.ms_exchange_certificate`.
122 This means that all fields on all item types are now supported.
123
114
115 - Switch `EWSTimeZone` to be implemented on top of the new `zoneinfo` module in Python 3.9 instead of `pytz`
116 . `backports.zoneinfo` is used for earlier versions of Python. This means that the
117 `ÈWSTimeZone` methods `timezone()`, `normalize()` and `localize()` methods are now deprecated.
118 - Add `EWSTimeZone.from_dateutil()` to support converting `dateutil.tz` timezones to `EWSTimeZone`.
119 - Dropped support for Python 3.5 which is EOL per September 2020.
120 - Added support for `CalendaItem.appointment_state`, `CalendaItem.conflicting_meetings` and
121 `CalendarItem.adjacent_meetings` fields.
122 - Added support for the `Message.reminder_message_data` field.
123 - Added support for `Contact.manager_mailbox`, `Contact.direct_reports` and `Contact.complete_name` fields.
124 - Added support for `Item.response_objects` field.
125 - Changed `Task.due_date` and `Tas.start_date` fields from datetime to date fields, since the time was being truncated
126 anyway by the server.
127 - Added support for `Task.recurrence` field.
128 - Added read-only support for `Contact.user_smime_certificate` and `Contact.ms_exchange_certificate`. This means that
129 all fields on all item types are now supported.
124130
125131 3.2.1
126132 -----
127 - Fix bug leading to an exception in `CalendarItem.cancel()`.
128 - Improve stability of `.order_by()` in edge cases where sorting must be done client-side.
129 - Allow increasing the session pool-size dynamically.
130 - Change semantics of `.filter(foo__in=[])` to return an empty result. This was previously undefined
131 behavior. Now we adopt the behaviour of Django in this case. This is still undefined behavior for
132 list-type fields.
133 - Moved documentation to GitHub Pages and auto-documentation generated by `pdoc3`.
134
133
134 - Fix bug leading to an exception in `CalendarItem.cancel()`.
135 - Improve stability of `.order_by()` in edge cases where sorting must be done client-side.
136 - Allow increasing the session pool-size dynamically.
137 - Change semantics of `.filter(foo__in=[])` to return an empty result. This was previously undefined behavior. Now we
138 adopt the behaviour of Django in this case. This is still undefined behavior for list-type fields.
139 - Moved documentation to GitHub Pages and auto-documentation generated by `pdoc3`.
135140
136141 3.2.0
137142 -----
138 - Remove use of `ThreadPool` objects. Threads were used to implement async HTTP requests, but
139 were creating massive memory leaks. Async requests should be reimplemented using a real async
140 HTTP request package, so this is just an emergency fix. This also lowers the default
141 `Protocol.SESSION_POOLSIZE` to 1 because no internal code is running multi-threaded anymore.
142 - All-day calendar items (created as `CalendarItem(is_all_day=True, ...)`) now accept `EWSDate`
143 instances for the `start` and `end` values. Similarly, all-day calendar items fetched from
144 the server now return `start` and `end` values as `EWSDate` instances. In this case, start
145 and end values are inclusive; a one-day event starts and ends on the same `EWSDate` value.
146 - Add support for `RecurringMasterItemId` and `OccurrenceItemId` elements that allow to request
147 the master recurrence from a `CalendarItem` occurrence, and to request a specific occurrence
148 from a `CalendarItem` master recurrence. `CalendarItem.master_recurrence()` and
149 `CalendarItem.occurrence(some_occurrence_index)` methods were added to aid this traversal.
150 `some_occurrence_index` in the last method specifies which item in the list of occurrences to
151 target; `CalendarItem.occurrence(3)` gets the third occurrence in the recurrence.
152 - Change `Contact.birthday` and `Contact.wedding_anniversary` from `EWSDateTime` to `EWSDate`
153 fields. EWS still expects and sends datetime values but has started to reset the time part to
154 11:59. Dates are a better match for these two fields anyway.
155 - Remove support for `len(some_queryset)`. It had the nasty side-effect of forcing
156 `list(some_queryset)` to run the query twice, once for pre-allocating the list via the result
157 of `len(some_queryset)`, and then once more to fetch the results. All occurrences of
158 `len(some_queryset)` can be replaced with `some_queryset.count()`. Unfortunately, there is
159 no way to keep backwards-compatibility for this feature.
160 - Added `Account.identity`, an attribute to contain extra information for impersonation. Setting
161 `Account.identity.upn` or `Account.identity.sid` removes the need for an AD lookup on every request.
162 `upn` will often be the same as `primary_smtp_address`, but it is not guaranteed. If you have
163 access to your organization's AD servers, you can look up these values once and add them to your
164 `Account` object to improve performance of the following requests.
165 - Added support for CBA authentication
166
143
144 - Remove use of `ThreadPool` objects. Threads were used to implement async HTTP requests, but were creating massive
145 memory leaks. Async requests should be reimplemented using a real async HTTP request package, so this is just an
146 emergency fix. This also lowers the default
147 `Protocol.SESSION_POOLSIZE` to 1 because no internal code is running multi-threaded anymore.
148 - All-day calendar items (created as `CalendarItem(is_all_day=True, ...)`) now accept `EWSDate`
149 instances for the `start` and `end` values. Similarly, all-day calendar items fetched from the server now
150 return `start` and `end` values as `EWSDate` instances. In this case, start and end values are inclusive; a one-day
151 event starts and ends on the same `EWSDate` value.
152 - Add support for `RecurringMasterItemId` and `OccurrenceItemId` elements that allow to request the master recurrence
153 from a `CalendarItem` occurrence, and to request a specific occurrence from a `CalendarItem` master
154 recurrence. `CalendarItem.master_recurrence()` and
155 `CalendarItem.occurrence(some_occurrence_index)` methods were added to aid this traversal.
156 `some_occurrence_index` in the last method specifies which item in the list of occurrences to
157 target; `CalendarItem.occurrence(3)` gets the third occurrence in the recurrence.
158 - Change `Contact.birthday` and `Contact.wedding_anniversary` from `EWSDateTime` to `EWSDate`
159 fields. EWS still expects and sends datetime values but has started to reset the time part to 11:59. Dates are a
160 better match for these two fields anyway.
161 - Remove support for `len(some_queryset)`. It had the nasty side-effect of forcing
162 `list(some_queryset)` to run the query twice, once for pre-allocating the list via the result of `len(some_queryset)`,
163 and then once more to fetch the results. All occurrences of
164 `len(some_queryset)` can be replaced with `some_queryset.count()`. Unfortunately, there is no way to keep
165 backwards-compatibility for this feature.
166 - Added `Account.identity`, an attribute to contain extra information for impersonation. Setting
167 `Account.identity.upn` or `Account.identity.sid` removes the need for an AD lookup on every request.
168 `upn` will often be the same as `primary_smtp_address`, but it is not guaranteed. If you have access to your
169 organization's AD servers, you can look up these values once and add them to your
170 `Account` object to improve performance of the following requests.
171 - Added support for CBA authentication
167172
168173 3.1.1
169174 -----
170 - The `max_wait` argument to `FaultTolerance` changed semantics. Previously, it triggered when
171 the delay until the next attempt would exceed this value. It now triggers after the given
172 timespan since the *first* request attempt.
173 - Fixed a bug when pagination is combined with `max_items` (#710)
174 - Other minor bug fixes
175
175
176 - The `max_wait` argument to `FaultTolerance` changed semantics. Previously, it triggered when the delay until the next
177 attempt would exceed this value. It now triggers after the given timespan since the *first* request attempt.
178 - Fixed a bug when pagination is combined with `max_items` (#710)
179 - Other minor bug fixes
176180
177181 3.1.0
178182 -----
179 - Removed the legacy autodiscover implementation.
180 - Added `QuerySet.depth()` to configure item traversal of querysets. Default is `Shallow` except
181 for the `CommonViews` folder where default is `Associated`.
182 - Updating credentials on `Account.protocol` after getting an `UnauthorizedError` now works.
183
183
184 - Removed the legacy autodiscover implementation.
185 - Added `QuerySet.depth()` to configure item traversal of querysets. Default is `Shallow` except for the `CommonViews`
186 folder where default is `Associated`.
187 - Updating credentials on `Account.protocol` after getting an `UnauthorizedError` now works.
184188
185189 3.0.0
186190 -----
187 - The new Autodiscover implementation added in 2.2.0 is now default. To switch back to the old
188 implementation, set the environment variable `EXCHANGELIB_AUTODISCOVER_VERSION=legacy`.
189 - Removed support for Python 2
190
191
192 - The new Autodiscover implementation added in 2.2.0 is now default. To switch back to the old implementation, set the
193 environment variable `EXCHANGELIB_AUTODISCOVER_VERSION=legacy`.
194 - Removed support for Python 2
191195
192196 2.2.0
193197 -----
194 - Added support for specifying a separate retry policy for the autodiscover service endpoint
195 selection. Set via the `exchangelib.autodiscover.legacy.INITIAL_RETRY_POLICY` module variable
196 for the the old autodiscover implementation, and via the
197 `exchangelib.autodiscover.Autodiscovery.INITIAL_RETRY_POLICY` class variable for the new one.
198 - Support the authorization code OAuth 2.0 grant type (see issue #698)
199 - Removed the `RootOfHierarchy.permission_set` field. It was causing too many failures in the wild.
200 - The full autodiscover response containing all contents of the reponse is now available as `Account.ad_response`.
201 - Added a new Autodiscover implementation that is closer to the specification and easier to debug. To switch
202 to the new implementation, set the environment variable `EXCHANGELIB_AUTODISCOVER_VERSION=new`. The old
203 one is still the default if the variable is not set, or set to `EXCHANGELIB_AUTODISCOVER_VERSION=legacy`.
204 - The `Item.mime_content` field was switched back from a string type to a `bytes` type. It turns out trying
205 to decode the data was an error (see issue #709).
206
198
199 - Added support for specifying a separate retry policy for the autodiscover service endpoint selection. Set via
200 the `exchangelib.autodiscover.legacy.INITIAL_RETRY_POLICY` module variable for the the old autodiscover
201 implementation, and via the
202 `exchangelib.autodiscover.Autodiscovery.INITIAL_RETRY_POLICY` class variable for the new one.
203 - Support the authorization code OAuth 2.0 grant type (see issue #698)
204 - Removed the `RootOfHierarchy.permission_set` field. It was causing too many failures in the wild.
205 - The full autodiscover response containing all contents of the reponse is now available as `Account.ad_response`.
206 - Added a new Autodiscover implementation that is closer to the specification and easier to debug. To switch to the new
207 implementation, set the environment variable `EXCHANGELIB_AUTODISCOVER_VERSION=new`. The old one is still the default
208 if the variable is not set, or set to `EXCHANGELIB_AUTODISCOVER_VERSION=legacy`.
209 - The `Item.mime_content` field was switched back from a string type to a `bytes` type. It turns out trying to decode
210 the data was an error (see issue #709).
207211
208212 2.1.1
209213 -----
210 - Bugfix release.
211
214
215 - Bugfix release.
212216
213217 2.1.0
214218 -----
215 - Added support for OAuth 2.0 authentication
216 - Fixed a bug in `RelativeMonthlyPattern` and `RelativeYearlyPattern` where the `weekdays` field was thought to
217 be a list, but is in fact a single value. Renamed the field to `weekday` to reflect the change.
218 - Added support for archiving items to the archive mailbox, if the account has one.
219 - Added support for getting delegate information on an Account, as `Account.delegates`.
220 - Added support for the `ConvertId` service. Available as `Protocol.convert_ids()`.
221
219
220 - Added support for OAuth 2.0 authentication
221 - Fixed a bug in `RelativeMonthlyPattern` and `RelativeYearlyPattern` where the `weekdays` field was thought to be a
222 list, but is in fact a single value. Renamed the field to `weekday` to reflect the change.
223 - Added support for archiving items to the archive mailbox, if the account has one.
224 - Added support for getting delegate information on an Account, as `Account.delegates`.
225 - Added support for the `ConvertId` service. Available as `Protocol.convert_ids()`.
222226
223227 2.0.1
224228 -----
225 - Fixed a bug where version 2.x could not open autodiscover cache files generated by
226 version 1.x packages.
227
229
230 - Fixed a bug where version 2.x could not open autodiscover cache files generated by version 1.x packages.
228231
229232 2.0.0
230233 -----
231 - `Item.mime_content` is now a text field instead of a binary field. Encoding and
232 decoding is done automatically.
233 - The `Item.item_id`, `Folder.folder_id` and `Occurrence.item_id` fields that were renamed
234 to just `id` in 1.12.0, have now been removed.
235 - The `Persona.persona_id` field was replaced with `Persona.id` and `Persona.changekey`, to
236 align with the `Item` and `Folder` classes.
237 - In addition to bulk deleting via a QuerySet (`qs.delete()`), it is now possible to also
238 bulk send, move and copy items in a QuerySet (via `qs.send()`, `qs.move()` and `qs.copy()`,
239 respectively).
240 - SSPI support was added but dependencies are not installed by default since it only works
241 in Win32 environments. Install as `pip install exchangelib[sspi]` to get SSPI support.
242 Install with `pip install exchangelib[complete]` to get both Kerberos and SSPI auth.
243 - The custom `extern_id` field is no longer registered by default. If you require this field,
244 register it manually as part of your setup code on the item types you need:
234
235 - `Item.mime_content` is now a text field instead of a binary field. Encoding and decoding is done automatically.
236 - The `Item.item_id`, `Folder.folder_id` and `Occurrence.item_id` fields that were renamed to just `id` in 1.12.0, have
237 now been removed.
238 - The `Persona.persona_id` field was replaced with `Persona.id` and `Persona.changekey`, to align with the `Item`
239 and `Folder` classes.
240 - In addition to bulk deleting via a QuerySet (`qs.delete()`), it is now possible to also bulk send, move and copy items
241 in a QuerySet (via `qs.send()`, `qs.move()` and `qs.copy()`, respectively).
242 - SSPI support was added but dependencies are not installed by default since it only works in Win32 environments.
243 Install as `pip install exchangelib[sspi]` to get SSPI support. Install with `pip install exchangelib[complete]` to
244 get both Kerberos and SSPI auth.
245 - The custom `extern_id` field is no longer registered by default. If you require this field, register it manually as
246 part of your setup code on the item types you need:
245247
246248 ```python
247249 from exchangelib import CalendarItem, Message, Contact, Task
252254 Contact.register('extern_id', ExternId)
253255 Task.register('extern_id', ExternId)
254256 ```
255 - The `ServiceAccount` class has been removed. If you want fault tolerance, set it in a
256 `Configuration` object:
257 - The `ServiceAccount` class has been removed. If you want fault tolerance, set it in a
258 `Configuration` object:
257259
258260 ```python
259261 from exchangelib import Configuration, Credentials, FaultTolerance
260262 c = Credentials('foo', 'bar')
261263 config = Configuration(credentials=c, retry_policy=FaultTolerance())
262264 ```
263 - It is now possible to use Kerberos and SSPI auth without providing a dummy
264 `Credentials('', '')` object.
265 - The `has_ssl` argument of `Configuration` was removed. If you want to connect to a
266 plain HTTP endpoint, pass the full URL in the `service_endpoint` argument.
267 - We no longer look in `types.xsd` for a hint of which API version the server is running. Instead,
268 we query the service directly, starting with the latest version first.
269
265 - It is now possible to use Kerberos and SSPI auth without providing a dummy
266 `Credentials('', '')` object.
267 - The `has_ssl` argument of `Configuration` was removed. If you want to connect to a plain HTTP endpoint, pass the full
268 URL in the `service_endpoint` argument.
269 - We no longer look in `types.xsd` for a hint of which API version the server is running. Instead, we query the service
270 directly, starting with the latest version first.
270271
271272 1.12.5
272273 ------
273 - Bugfix release.
274
274
275 - Bugfix release.
275276
276277 1.12.4
277278 ------
279
278280 - Fix bug that left out parts of the folder hierarchy when traversing `account.root`.
279 - Fix bug that did not properly find all attachments if an item has a mix of item
280 and file attachments.
281
281 - Fix bug that did not properly find all attachments if an item has a mix of item and file attachments.
282282
283283 1.12.3
284284 ------
285 - Add support for reading and writing `PermissionSet` field on folders.
286 - Add support for Exchange 2019 build IDs.
287
285
286 - Add support for reading and writing `PermissionSet` field on folders.
287 - Add support for Exchange 2019 build IDs.
288288
289289 1.12.2
290290 ------
291 - Add `Protocol.expand_dl()` to get members of a distribution list.
292
291
292 - Add `Protocol.expand_dl()` to get members of a distribution list.
293293
294294 1.12.1
295295 ------
296 - Lower the session pool size automatically in response to ErrorServerBusy and
297 ErrorTooManyObjectsOpened errors from the server.
298 - Unusual slicing and indexing (e.g. `inbox.all()[9000]` and `inbox.all()[9000:9001]`)
299 is now efficient.
300 - Downloading large attachments is now more memory-efficient. We can now stream the file
301 content without ever storing the full file content in memory, using the new
302 `Attachment.fp` context manager.
296
297 - Lower the session pool size automatically in response to ErrorServerBusy and ErrorTooManyObjectsOpened errors from the
298 server.
299 - Unusual slicing and indexing (e.g. `inbox.all()[9000]` and `inbox.all()[9000:9001]`)
300 is now efficient.
301 - Downloading large attachments is now more memory-efficient. We can now stream the file content without ever storing
302 the full file content in memory, using the new
303 `Attachment.fp` context manager.
303304
304305 1.12.0
305306 ------
306307
307 - Add a MAINFEST.in to ensure the LICENSE file gets included + CHANGELOG.md
308 and README.md to sdist tarball
309 - Renamed `Item.item_id`, `Folder.folder_id` and `Occurrence.item_id` to just
310 `Item.id`, `Folder.id` and `Occurrence.id`, respectively. This removes
311 redundancy in the naming and provides consistency. For all classes that
312 have an ID, the ID can now be accessed using the `id` attribute. Backwards
313 compatibility and deprecation warnings were added.
314 - Support folder traversal without creating a full cache of the folder
315 hierarchy first, using the `some_folder // 'sub_folder' // 'leaf'`
316 (double-slash) syntax.
317 - Fix a bug in traversal of public and archive folders. These folder
318 hierarchies are now fully supported.
319 - Fix a bug where the timezone of a calendar item changed when the item was
320 fetched and then saved.
321 - Kerberos support is now optional and Kerberos dependencies are not
322 installed by default. Install as `pip install exchangelib[kerberos]` to get
323 Kerberos support.
308 - Add a MAINFEST.in to ensure the LICENSE file gets included + CHANGELOG.md and README.md to sdist tarball
309 - Renamed `Item.item_id`, `Folder.folder_id` and `Occurrence.item_id` to just
310 `Item.id`, `Folder.id` and `Occurrence.id`, respectively. This removes redundancy in the naming and provides
311 consistency. For all classes that have an ID, the ID can now be accessed using the `id` attribute. Backwards
312 compatibility and deprecation warnings were added.
313 - Support folder traversal without creating a full cache of the folder hierarchy first, using
314 the `some_folder // 'sub_folder' // 'leaf'`
315 (double-slash) syntax.
316 - Fix a bug in traversal of public and archive folders. These folder hierarchies are now fully supported.
317 - Fix a bug where the timezone of a calendar item changed when the item was fetched and then saved.
318 - Kerberos support is now optional and Kerberos dependencies are not installed by default. Install
319 as `pip install exchangelib[kerberos]` to get Kerberos support.
324320
325321 1.11.4
326322 ------
327323
328 - Improve back off handling when receiving `ErrorServerBusy` error messages
329 from the server
330 - Fixed bug where `Account.root` and its children would point to the root
331 folder of the connecting account instead of the target account when
332 connecting to other accounts.
324 - Improve back off handling when receiving `ErrorServerBusy` error messages from the server
325 - Fixed bug where `Account.root` and its children would point to the root folder of the connecting account instead of
326 the target account when connecting to other accounts.
333327
334328 1.11.3
335329 ------
336330
337 - Add experimental Kerberos support. This adds the `pykerberos` package,
338 which needs the following system packages to be installed on Ubuntu/Debian
339 systems: `apt-get install build-essential libssl-dev libffi-dev python-dev libkrb5-dev`.
331 - Add experimental Kerberos support. This adds the `pykerberos` package, which needs the following system packages to be
332 installed on Ubuntu/Debian systems: `apt-get install build-essential libssl-dev libffi-dev python-dev libkrb5-dev`.
340333
341334 1.11.2
342335 ------
343336
344 - Bugfix release
337 - Bugfix release
345338
346339 1.11.1
347340 ------
348341
349 - Bugfix release
342 - Bugfix release
350343
351344 1.11.0
352345 ------
353346
354 - Added `cancel` to `CalendarItem` and `CancelCalendarItem` class to
355 allow cancelling meetings that were set up
356 - Added `accept`, `decline` and `tentatively_accept` to `CalendarItem`
357 as wrapper methods
358 - Added `accept`, `decline` and `tentatively_accept` to
359 `MeetingRequest` to respond to incoming invitations
360 - Added `BaseMeetingItem` (inheriting from `Item`) being used as base
361 for MeetingCancellation, MeetingMessage, MeetingRequest and
362 MeetingResponse
363 - Added `AssociatedCalendarItemId` (property),
364 `AssociatedCalendarItemIdField` and `ReferenceItemIdField`
365 - Added `PostReplyItem`
366 - Removed `Folder.get_folder_by_name()` which has been deprecated
367 since version `1.10.2`.
368 - Added `Item.copy(to_folder=some_folder)` method which copies an item
369 to the given folder and returns the ID of the new item.
370 - We now respect the back off value of an `ErrorServerBusy`
371 server error.
372 - Added support for fetching free/busy availability information ofr a
373 list of accounts.
374 - Added `Message.reply()`, `Message.reply_all()`, and
375 `Message.forward()` methods.
376 - The full search API now works on single folders *and* collections of
377 folders, e.g. `some_folder.glob('foo*').filter()`,
378 `some_folder.children.filter()` and `some_folder.walk().filter()`.
379 - Deprecated `EWSService.CHUNKSIZE` in favor of a per-request
380 chunk\_size available on `Account.bulk_foo()` methods.
381 - Support searching the GAL and other contact folders using
382 `some_contact_folder.people()`.
383 - Deprecated the `page_size` argument for `QuerySet.iterator()` because it
384 was inconsistent with other API methods. You can still set the page size
385 of a queryset like this:
347 - Added `cancel` to `CalendarItem` and `CancelCalendarItem` class to allow cancelling meetings that were set up
348 - Added `accept`, `decline` and `tentatively_accept` to `CalendarItem`
349 as wrapper methods
350 - Added `accept`, `decline` and `tentatively_accept` to
351 `MeetingRequest` to respond to incoming invitations
352 - Added `BaseMeetingItem` (inheriting from `Item`) being used as base for MeetingCancellation, MeetingMessage,
353 MeetingRequest and MeetingResponse
354 - Added `AssociatedCalendarItemId` (property),
355 `AssociatedCalendarItemIdField` and `ReferenceItemIdField`
356 - Added `PostReplyItem`
357 - Removed `Folder.get_folder_by_name()` which has been deprecated since version `1.10.2`.
358 - Added `Item.copy(to_folder=some_folder)` method which copies an item to the given folder and returns the ID of the new
359 item.
360 - We now respect the back off value of an `ErrorServerBusy`
361 server error.
362 - Added support for fetching free/busy availability information ofr a list of accounts.
363 - Added `Message.reply()`, `Message.reply_all()`, and
364 `Message.forward()` methods.
365 - The full search API now works on single folders *and* collections of folders, e.g. `some_folder.glob('foo*').filter()`
366 ,
367 `some_folder.children.filter()` and `some_folder.walk().filter()`.
368 - Deprecated `EWSService.CHUNKSIZE` in favor of a per-request chunk\_size available on `Account.bulk_foo()` methods.
369 - Support searching the GAL and other contact folders using
370 `some_contact_folder.people()`.
371 - Deprecated the `page_size` argument for `QuerySet.iterator()` because it was inconsistent with other API methods. You
372 can still set the page size of a queryset like this:
386373
387374 ```python
388375 qs = a.inbox.filter(...).iterator()
394381 1.10.7
395382 ------
396383
397 - Added support for registering extended properties on folders.
398 - Added support for creating, updating, deleting and emptying folders.
384 - Added support for registering extended properties on folders.
385 - Added support for creating, updating, deleting and emptying folders.
399386
400387 1.10.6
401388 ------
402389
403 - Added support for getting and setting `Account.oof_settings` using
404 the new `OofSettings` class.
405 - Added snake\_case named shortcuts to all distinguished folders on
406 the `Account` model. E.g. `Account.search_folders`.
390 - Added support for getting and setting `Account.oof_settings` using the new `OofSettings` class.
391 - Added snake\_case named shortcuts to all distinguished folders on the `Account` model. E.g. `Account.search_folders`.
407392
408393 1.10.5
409394 ------
410395
411 - Bugfix release
396 - Bugfix release
412397
413398 1.10.4
414399 ------
415400
416 - Added support for most item fields. The remaining ones are mentioned
417 in issue \#203.
401 - Added support for most item fields. The remaining ones are mentioned in issue \#203.
418402
419403 1.10.3
420404 ------
421405
422 - Added an `exchangelib.util.PrettyXmlHandler` log handler which will
423 pretty-print and highlight XML requests and responses.
406 - Added an `exchangelib.util.PrettyXmlHandler` log handler which will pretty-print and highlight XML requests and
407 responses.
424408
425409 1.10.2
426410 ------
427411
428 - Greatly improved folder navigation. See the 'Folders' section in the
429 README
430 - Added deprecation warnings for `Account.folders` and
431 `Folder.get_folder_by_name()`
412 - Greatly improved folder navigation. See the 'Folders' section in the README
413 - Added deprecation warnings for `Account.folders` and
414 `Folder.get_folder_by_name()`
432415
433416 1.10.1
434417 ------
435418
436 - Bugfix release
419 - Bugfix release
437420
438421 1.10.0
439422 ------
440423
441 - Removed the `verify_ssl` argument to `Account`, `discover` and
442 `Configuration`. If you need to disable TLS verification, register a
443 custom `HTTPAdapter` class. A sample adapter class is provided for
444 convenience:
424 - Removed the `verify_ssl` argument to `Account`, `discover` and
425 `Configuration`. If you need to disable TLS verification, register a custom `HTTPAdapter` class. A sample adapter
426 class is provided for convenience:
445427
446428 ```python
447429 from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
451433 1.9.6
452434 -----
453435
454 - Support new Office365 build numbers
436 - Support new Office365 build numbers
455437
456438 1.9.5
457439 -----
458440
459 - Added support for the `effective_rights`field on items and folders.
460 - Added support for custom `requests` transport adapters, to allow
461 proxy support, custom TLS validation etc.
462 - Default value for the `affected_task_occurrences` argument to
463 `Item.move_to_trash()`, `Item.soft_delete()` and `Item.delete()` was
464 changed to `'AllOccurrences'` as a less surprising default when
465 working with simple tasks.
466 - Added `Task.complete()` helper method to mark tasks as complete.
441 - Added support for the `effective_rights`field on items and folders.
442 - Added support for custom `requests` transport adapters, to allow proxy support, custom TLS validation etc.
443 - Default value for the `affected_task_occurrences` argument to
444 `Item.move_to_trash()`, `Item.soft_delete()` and `Item.delete()` was changed to `'AllOccurrences'` as a less
445 surprising default when working with simple tasks.
446 - Added `Task.complete()` helper method to mark tasks as complete.
467447
468448 1.9.4
469449 -----
470450
471 - Added minimal support for the `PostItem` item type
472 - Added support for the `DistributionList` item type
473 - Added support for receiving naive datetimes from the server. They
474 will be localized using the new `default_timezone` attribute on
475 `Account`
476 - Added experimental support for recurring calendar items. See
477 examples in issue \#37.
451 - Added minimal support for the `PostItem` item type
452 - Added support for the `DistributionList` item type
453 - Added support for receiving naive datetimes from the server. They will be localized using the new `default_timezone`
454 attribute on
455 `Account`
456 - Added experimental support for recurring calendar items. See examples in issue \#37.
478457
479458 1.9.3
480459 -----
481460
482 - Improved support for `filter()`, `.only()`, `.order_by()` etc. on
483 indexed properties. It is now possible to specify labels and
484 subfields, e.g.
485 `.filter(phone_numbers=PhoneNumber(label='CarPhone', phone_number='123'))`
486 `.filter(phone_numbers__CarPhone='123')`,
487 `.filter(physical_addresses__Home__street='Elm St. 123')`,
488 .only('physical\_addresses\_\_Home\_\_street')\` etc.
489 - Improved performance of `.order_by()` when sorting on
490 multiple fields.
491 - Implemented QueryString search. You can now filter using an EWS
492 QueryString, e.g. `filter('subject:XXX')`
461 - Improved support for `filter()`, `.only()`, `.order_by()` etc. on indexed properties. It is now possible to specify
462 labels and subfields, e.g.
463 `.filter(phone_numbers=PhoneNumber(label='CarPhone', phone_number='123'))`
464 `.filter(phone_numbers__CarPhone='123')`,
465 `.filter(physical_addresses__Home__street='Elm St. 123')`, .only('physical\_addresses\_\_Home\_\_street')\` etc.
466 - Improved performance of `.order_by()` when sorting on multiple fields.
467 - Implemented QueryString search. You can now filter using an EWS QueryString, e.g. `filter('subject:XXX')`
493468
494469 1.9.2
495470 -----
496471
497 - Added `EWSTimeZone.localzone()` to get the local timezone
498 - Support `some_folder.get(item_id=..., changekey=...)` as a shortcut
499 to get a single item when you know the ID and changekey.
500 - Support attachments on Exchange 2007
472 - Added `EWSTimeZone.localzone()` to get the local timezone
473 - Support `some_folder.get(item_id=..., changekey=...)` as a shortcut to get a single item when you know the ID and
474 changekey.
475 - Support attachments on Exchange 2007
501476
502477 1.9.1
503478 -----
504479
505 - Fixed XML generation for Exchange 2010 and other picky server
506 versions
507 - Fixed timezone localization for `EWSTimeZone` created from a static
508 timezone
480 - Fixed XML generation for Exchange 2010 and other picky server versions
481 - Fixed timezone localization for `EWSTimeZone` created from a static timezone
509482
510483 1.9.0
511484 -----
512485
513 - Expand support for `ExtendedProperty` to include all
514 possible attributes. This required renaming the `property_id`
515 attribute to `property_set_id`.
516 - When using the `Credentials` class, `UnauthorizedError` is now
517 raised if the credentials are wrong.
518 - Add a new `version` attribute to `Configuration`, to force the
519 server version if version guessing does not work. Accepts a
520 `exchangelib.version.Version` object.
521 - Rework bulk operations `Account.bulk_foo()` and `Account.fetch()` to
522 return some exceptions unraised, if it is deemed the exception does
523 not apply to all items. This means that e.g. `fetch()` can return a
524 mix of `` `Item `` and `ErrorItemNotFound` instances, if only some
525 of the requested `ItemId` were valid. Other exceptions will be
526 raised immediately, e.g. `ErrorNonExistentMailbox` because the
527 exception applies to all items. It is the responsibility of the
528 caller to check the type of the returned values.
529 - The `Folder` class has new attributes `total_count`, `unread_count`
530 and `child_folder_count`, and a `refresh()` method to update
531 these values.
532 - The argument to `Account.upload()` was renamed from `upload_data` to
533 just `data`
534 - Support for using a string search expression for `Folder.filter()`
535 was removed. It was a cool idea but using QuerySet chaining and `Q`
536 objects is even cooler and provides the same functionality,
537 and more.
538 - Add support for `reminder_due_by` and
539 `reminder_minutes_before_start` fields on `Item` objects. Submitted
540 by `@vikipha`.
541 - Added a new `ServiceAccount` class which is like `Credentials` but
542 does what `is_service_account` did before. If you need
543 fault-tolerane and used `Credentials(..., is_service_account=True)`
544 before, use `ServiceAccount` now. This also disables fault-tolerance
545 for the `Credentials` class, which is in line with what most
546 users expected.
547 - Added an optional `update_fields` attribute to `save()` to specify
548 only some fields to be updated.
549 - Code in in `folders.py` has been split into multiple files, and some
550 classes will have new import locaions. The most commonly used
551 classes have a shortcut in \_\_init\_\_.py
552 - Added support for the `exists` lookup in filters, e.g.
553 `my_folder.filter(categories__exists=True|False)` to filter on the
554 existence of that field on items in the folder.
555 - When filtering, `foo__in=value` now requires the value to be a list,
556 and `foo__contains` requires the value to be a list if the field
557 itself is a list, e.g. `categories__contains=['a', 'b']`.
558 - Added support for fields and enum entries that are only supported in
559 some EWS versions
560 - Added a new field `Item.text_body` which is a read-only version of
561 HTML body content, where HTML tags are stripped by the server. Only
562 supported from Exchange 2013 and up.
563 - Added a new choice `WorkingElsewhere` to the
564 `CalendarItem.legacy_free_busy_status` enum. Only supported from
565 Exchange 2013 and up.
486 - Expand support for `ExtendedProperty` to include all possible attributes. This required renaming the `property_id`
487 attribute to `property_set_id`.
488 - When using the `Credentials` class, `UnauthorizedError` is now raised if the credentials are wrong.
489 - Add a new `version` attribute to `Configuration`, to force the server version if version guessing does not work.
490 Accepts a
491 `exchangelib.version.Version` object.
492 - Rework bulk operations `Account.bulk_foo()` and `Account.fetch()` to return some exceptions unraised, if it is deemed
493 the exception does not apply to all items. This means that e.g. `fetch()` can return a mix of `` `Item ``
494 and `ErrorItemNotFound` instances, if only some of the requested `ItemId` were valid. Other exceptions will be raised
495 immediately, e.g. `ErrorNonExistentMailbox` because the exception applies to all items. It is the responsibility of
496 the caller to check the type of the returned values.
497 - The `Folder` class has new attributes `total_count`, `unread_count`
498 and `child_folder_count`, and a `refresh()` method to update these values.
499 - The argument to `Account.upload()` was renamed from `upload_data` to just `data`
500 - Support for using a string search expression for `Folder.filter()`
501 was removed. It was a cool idea but using QuerySet chaining and `Q`
502 objects is even cooler and provides the same functionality, and more.
503 - Add support for `reminder_due_by` and
504 `reminder_minutes_before_start` fields on `Item` objects. Submitted by `@vikipha`.
505 - Added a new `ServiceAccount` class which is like `Credentials` but does what `is_service_account` did before. If you
506 need fault-tolerane and used `Credentials(..., is_service_account=True)`
507 before, use `ServiceAccount` now. This also disables fault-tolerance for the `Credentials` class, which is in line
508 with what most users expected.
509 - Added an optional `update_fields` attribute to `save()` to specify only some fields to be updated.
510 - Code in in `folders.py` has been split into multiple files, and some classes will have new import locaions. The most
511 commonly used classes have a shortcut in \_\_init\_\_.py
512 - Added support for the `exists` lookup in filters, e.g.
513 `my_folder.filter(categories__exists=True|False)` to filter on the existence of that field on items in the folder.
514 - When filtering, `foo__in=value` now requires the value to be a list, and `foo__contains` requires the value to be a
515 list if the field itself is a list, e.g. `categories__contains=['a', 'b']`.
516 - Added support for fields and enum entries that are only supported in some EWS versions
517 - Added a new field `Item.text_body` which is a read-only version of HTML body content, where HTML tags are stripped by
518 the server. Only supported from Exchange 2013 and up.
519 - Added a new choice `WorkingElsewhere` to the
520 `CalendarItem.legacy_free_busy_status` enum. Only supported from Exchange 2013 and up.
566521
567522 1.8.1
568523 -----
569524
570 - Fix completely botched `Message.from` field renaming in 1.8.0
571 - Improve performance of QuerySet slicing and indexing. For example,
572 `account.inbox.all()[10]` and `account.inbox.all()[:10]` now only
573 fetch 10 items from the server even though `account.inbox.all()`
574 could contain thousands of messages.
525 - Fix completely botched `Message.from` field renaming in 1.8.0
526 - Improve performance of QuerySet slicing and indexing. For example,
527 `account.inbox.all()[10]` and `account.inbox.all()[:10]` now only fetch 10 items from the server even
528 though `account.inbox.all()`
529 could contain thousands of messages.
575530
576531 1.8.0
577532 -----
578533
579 - Renamed `Message.from` field to `Message.author`. `from` is a Python
580 keyword so `from` could only be accessed as
581 `Getattr(my_essage, 'from')` which is just stupid.
582 - Make `EWSTimeZone` Windows timezone name translation more robust
583 - Add read-only `Message.message_id` which holds the Internet Message
584 Id
585 - Memory and speed improvements when sorting querysets using
586 `order_by()` on a single field.
587 - Allow setting `Mailbox` and `Attendee`-type attributes as plain
588 strings, e.g.:
534 - Renamed `Message.from` field to `Message.author`. `from` is a Python keyword so `from` could only be accessed as
535 `Getattr(my_essage, 'from')` which is just stupid.
536 - Make `EWSTimeZone` Windows timezone name translation more robust
537 - Add read-only `Message.message_id` which holds the Internet Message Id
538 - Memory and speed improvements when sorting querysets using
539 `order_by()` on a single field.
540 - Allow setting `Mailbox` and `Attendee`-type attributes as plain strings, e.g.:
589541
590542 ```python
591543 calendar_item.organizer = 'anne@example.com'
597549 1.7.6
598550 -----
599551
600 - Bugfix release
552 - Bugfix release
601553
602554 1.7.5
603555 -----
604556
605 - `Account.fetch()` and `Folder.fetch()` are now generators. They will
606 do nothing before being evaluated.
607 - Added optional `page_size` attribute to `QuerySet.iterator()` to
608 specify the number of items to return per HTTP request for large
609 query results. Default `page_size` is 100.
610 - Many minor changes to make queries less greedy and return earlier
557 - `Account.fetch()` and `Folder.fetch()` are now generators. They will do nothing before being evaluated.
558 - Added optional `page_size` attribute to `QuerySet.iterator()` to specify the number of items to return per HTTP
559 request for large query results. Default `page_size` is 100.
560 - Many minor changes to make queries less greedy and return earlier
611561
612562 1.7.4
613563 -----
614564
615 - Add Python2 support
565 - Add Python2 support
616566
617567 1.7.3
618568 -----
619569
620 - Implement attachments support. It's now possible to create, delete
621 and get attachments connected to any item type:
570 - Implement attachments support. It's now possible to create, delete and get attachments connected to any item type:
622571
623572 ```python
624573 from exchangelib.folders import FileAttachment, ItemAttachment
649598 item.detach(my_file)
650599 ```
651600
652 Be aware that adding and deleting attachments from items that are
653 already created in Exchange (items that have an `item_id`) will
654 update the `changekey` of the item.
655
656 - Implement `Item.headers` which contains custom Internet
657 message headers. Primarily useful for `Message` objects. Read-only
658 for now.
601 Be aware that adding and deleting attachments from items that are already created in Exchange (items that have
602 an `item_id`) will update the `changekey` of the item.
603
604 - Implement `Item.headers` which contains custom Internet message headers. Primarily useful for `Message` objects.
605 Read-only for now.
659606
660607 1.7.2
661608 -----
662609
663 - Implement the `Contact.physical_addresses` attribute. This is a list
664 of `exchangelib.folders.PhysicalAddress` items.
665 - Implement the `CalendarItem.is_all_day` boolean to create
666 all-day appointments.
667 - Implement `my_folder.export()` and `my_folder.upload()`. Thanks to
668 @SamCB!
669 - Fixed `Account.folders` for non-distinguished folders
670 - Added `Folder.get_folder_by_name()` to make it easier to get
671 sub-folders by name.
672 - Implement `CalendarView` searches as
673 `my_calendar.view(start=..., end=...)`. A view differs from a normal
674 `filter()` in that a view expands recurring items and returns
675 recurring item occurrences that are valid in the time span of
676 the view.
677 - Persistent storage location for autodiscover cache is now platform
678 independent
679 - Implemented custom extended properties. To add support for your own
680 custom property, subclass `exchangelib.folders.ExtendedProperty` and
681 call `register()` on the item class you want to use the extended
682 property with. When you have registered your extended property, you
683 can use it exactly like you would use any other attribute on this
684 item type. If you change your mind, you can remove the extended
685 property again with `deregister()`:
610 - Implement the `Contact.physical_addresses` attribute. This is a list of `exchangelib.folders.PhysicalAddress` items.
611 - Implement the `CalendarItem.is_all_day` boolean to create all-day appointments.
612 - Implement `my_folder.export()` and `my_folder.upload()`. Thanks to @SamCB!
613 - Fixed `Account.folders` for non-distinguished folders
614 - Added `Folder.get_folder_by_name()` to make it easier to get sub-folders by name.
615 - Implement `CalendarView` searches as
616 `my_calendar.view(start=..., end=...)`. A view differs from a normal
617 `filter()` in that a view expands recurring items and returns recurring item occurrences that are valid in the time
618 span of the view.
619 - Persistent storage location for autodiscover cache is now platform independent
620 - Implemented custom extended properties. To add support for your own custom property,
621 subclass `exchangelib.folders.ExtendedProperty` and call `register()` on the item class you want to use the extended
622 property with. When you have registered your extended property, you can use it exactly like you would use any other
623 attribute on this item type. If you change your mind, you can remove the extended property again with `deregister()`:
686624
687625 ```python
688626 class LunchMenu(ExtendedProperty):
696634 CalendarItem.deregister('lunch_menu')
697635 ```
698636
699 - Fixed a bug on folder items where an existing HTML body would be
700 converted to text when calling `save()`. When creating or updating
701 an item body, you can use the two new helper classes
702 `exchangelib.Body` and `exchangelib.HTMLBody` to specify if your
703 body should be saved as HTML or text. E.g.:
637 - Fixed a bug on folder items where an existing HTML body would be converted to text when calling `save()`. When
638 creating or updating an item body, you can use the two new helper classes
639 `exchangelib.Body` and `exchangelib.HTMLBody` to specify if your body should be saved as HTML or text. E.g.:
704640
705641 ```python
706642 item = CalendarItem(...)
716652 1.7.1
717653 -----
718654
719 - Fix bug where fetching items from a folder that can contain multiple
720 item types (e.g. the Deleted Items folder) would only return one
721 item type.
722 - Added `Item.move(to_folder=...)` that moves an item to another
723 folder, and `Item.refresh()` that updates the Item with data
724 from EWS.
725 - Support reverse sort on individual fields in `order_by()`, e.g.
726 `my_folder.all().order_by('subject', '-start')`
727 - `Account.bulk_create()` was added to create items that don't need a
728 folder, e.g. `Message.send()`
729 - `Account.fetch()` was added to fetch items without knowing the
730 containing folder.
731 - Implemented `SendItem` service to send existing messages.
732 - `Folder.bulk_delete()` was moved to `Account.bulk_delete()`
733 - `Folder.bulk_update()` was moved to `Account.bulk_update()` and
734 changed to expect a list of `(Item, fieldnames)` tuples where Item
735 is e.g. a `Message` instance and `fieldnames` is a list of
736 attributes names that need updating. E.g.:
655 - Fix bug where fetching items from a folder that can contain multiple item types (e.g. the Deleted Items folder) would
656 only return one item type.
657 - Added `Item.move(to_folder=...)` that moves an item to another folder, and `Item.refresh()` that updates the Item with
658 data from EWS.
659 - Support reverse sort on individual fields in `order_by()`, e.g.
660 `my_folder.all().order_by('subject', '-start')`
661 - `Account.bulk_create()` was added to create items that don't need a folder, e.g. `Message.send()`
662 - `Account.fetch()` was added to fetch items without knowing the containing folder.
663 - Implemented `SendItem` service to send existing messages.
664 - `Folder.bulk_delete()` was moved to `Account.bulk_delete()`
665 - `Folder.bulk_update()` was moved to `Account.bulk_update()` and changed to expect a list of `(Item, fieldnames)`
666 tuples where Item is e.g. a `Message` instance and `fieldnames` is a list of attributes names that need updating.
667 E.g.:
737668
738669 ```python
739670 items = []
752683 1.7.0
753684 -----
754685
755 - Added the `is_service_account` flag to `Credentials`.
756 `is_service_account=False` disables the fault-tolerant error
757 handling policy and enables immediate failures.
758 - `Configuration` now expects a single `credentials` attribute instead
759 of separate `username` and `password` attributes.
760 - Added support for distinguished folders `Account.trash`,
761 `Account.drafts`, `Account.outbox`, `Account.sent` and
762 `Account.junk`.
763 - Renamed `Folder.find_items()` to `Folder.filter()`
764 - Renamed `Folder.add_items()` to `Folder.bulk_create()`
765 - Renamed `Folder.update_items()` to `Folder.bulk_update()`
766 - Renamed `Folder.delete_items()` to `Folder.bulk_delete()`
767 - Renamed `Folder.get_items()` to `Folder.fetch()`
768 - Made various policies for message saving, meeting invitation
769 sending, conflict resolution, task occurrences and deletion
770 available on `bulk_create()`, `bulk_update()` and `bulk_delete()`.
771 - Added convenience methods `Item.save()`, `Item.delete()`,
772 `Item.soft_delete()`, `Item.move_to_trash()`, and methods
773 `Message.send()` and `Message.send_and_save()` that are specific to
774 `Message` objects. These methods make it easier to create, update
775 and delete single items.
776 - Removed `fetch(.., with_extra=True)` in favor of the more
777 fine-grained `fetch(.., only_fields=[...])`
778 - Added a `QuerySet` class that supports QuerySet-returning methods
779 `filter()`, `exclude()`, `only()`, `order_by()`,
780 `reverse()``values()` and `values_list()` that all allow
781 for chaining. `QuerySet` also has methods `iterator()`, `get()`,
782 `count()`, `exists()` and `delete()`. All these methods behave like
783 their counterparts in Django.
686 - Added the `is_service_account` flag to `Credentials`.
687 `is_service_account=False` disables the fault-tolerant error handling policy and enables immediate failures.
688 - `Configuration` now expects a single `credentials` attribute instead of separate `username` and `password` attributes.
689 - Added support for distinguished folders `Account.trash`,
690 `Account.drafts`, `Account.outbox`, `Account.sent` and
691 `Account.junk`.
692 - Renamed `Folder.find_items()` to `Folder.filter()`
693 - Renamed `Folder.add_items()` to `Folder.bulk_create()`
694 - Renamed `Folder.update_items()` to `Folder.bulk_update()`
695 - Renamed `Folder.delete_items()` to `Folder.bulk_delete()`
696 - Renamed `Folder.get_items()` to `Folder.fetch()`
697 - Made various policies for message saving, meeting invitation sending, conflict resolution, task occurrences and
698 deletion available on `bulk_create()`, `bulk_update()` and `bulk_delete()`.
699 - Added convenience methods `Item.save()`, `Item.delete()`,
700 `Item.soft_delete()`, `Item.move_to_trash()`, and methods
701 `Message.send()` and `Message.send_and_save()` that are specific to
702 `Message` objects. These methods make it easier to create, update and delete single items.
703 - Removed `fetch(.., with_extra=True)` in favor of the more fine-grained `fetch(.., only_fields=[...])`
704 - Added a `QuerySet` class that supports QuerySet-returning methods
705 `filter()`, `exclude()`, `only()`, `order_by()`,
706 `reverse()``values()` and `values_list()` that all allow for chaining. `QuerySet` also has methods `iterator()`
707 , `get()`,
708 `count()`, `exists()` and `delete()`. All these methods behave like their counterparts in Django.
784709
785710 1.6.2
786711 -----
787712
788 - Use of `my_folder.with_extra_fields = True` to get the extra fields
789 in `Item.EXTRA_ITEM_FIELDS` is deprecated (it was a kludge anyway).
790 Instead, use `my_folder.get_items(ids, with_extra=[True, False])`.
791 The default was also changed to `True`, to avoid head-scratching
792 with newcomers.
713 - Use of `my_folder.with_extra_fields = True` to get the extra fields in `Item.EXTRA_ITEM_FIELDS` is deprecated (it was
714 a kludge anyway). Instead, use `my_folder.get_items(ids, with_extra=[True, False])`. The default was also changed
715 to `True`, to avoid head-scratching with newcomers.
793716
794717 1.6.1
795718 -----
796719
797 - Simplify `Q` objects and `Restriction.from_source()` by using Item
798 attribute names in expressions and kwargs instead of EWS
799 FieldURI values. Change `Folder.find_items()` to accept either a
800 search expression, or a list of `Q` objects just like Django
801 `filter()` does. E.g.:
720 - Simplify `Q` objects and `Restriction.from_source()` by using Item attribute names in expressions and kwargs instead
721 of EWS FieldURI values. Change `Folder.find_items()` to accept either a search expression, or a list of `Q` objects
722 just like Django
723 `filter()` does. E.g.:
802724
803725 ```python
804726 ids = account.calendar.find_items(
813735 1.6.0
814736 -----
815737
816 - Complete rewrite of `Folder.find_items()`. The old `start`, `end`,
817 `subject` and `categories` args are deprecated in favor of a Django
818 QuerySet filter() syntax. The supported lookup types are `__gt`,
819 `__lt`, `__gte`, `__lte`, `__range`, `__in`, `__exact`, `__iexact`,
820 `__contains`, `__icontains`, `__contains`, `__icontains`,
821 `__startswith`, `__istartswith`, plus an additional `__not` which
822 translates to `!=`. Additionally, *all* fields on the item are now
823 supported in `Folder.find_items()`.
824
825 **WARNING**: This change is backwards-incompatible! Old uses of
826 `Folder.find_items()` like this:
738 - Complete rewrite of `Folder.find_items()`. The old `start`, `end`,
739 `subject` and `categories` args are deprecated in favor of a Django QuerySet filter() syntax. The supported lookup
740 types are `__gt`,
741 `__lt`, `__gte`, `__lte`, `__range`, `__in`, `__exact`, `__iexact`,
742 `__contains`, `__icontains`, `__contains`, `__icontains`,
743 `__startswith`, `__istartswith`, plus an additional `__not` which translates to `!=`. Additionally, *all* fields on
744 the item are now supported in `Folder.find_items()`.
745
746 **WARNING**: This change is backwards-incompatible! Old uses of
747 `Folder.find_items()` like this:
827748
828749 ```python
829750 ids = account.calendar.find_items(
833754 )
834755 ```
835756
836 must be rewritten like this:
757 must be rewritten like this:
837758
838759 ```python
839760 ids = account.calendar.find_items(
843764 )
844765 ```
845766
846 failing to do so will most likely result in empty or wrong results.
847
848 - Added a `exchangelib.restrictions.Q` class much like Django Q
849 objects that can be used to create even more complex filtering. Q
850 objects must be passed directly to `exchangelib.services.FindItem`.
767 failing to do so will most likely result in empty or wrong results.
768
769 - Added a `exchangelib.restrictions.Q` class much like Django Q objects that can be used to create even more complex
770 filtering. Q objects must be passed directly to `exchangelib.services.FindItem`.
851771
852772 1.3.6
853773 -----
854774
855 - Don't require sequence arguments to `Folder.*_items()` methods to
856 support `len()` (e.g. generators and `map` instances are
857 now supported)
858 - Allow empty sequences as argument to `Folder.*_items()` methods
775 - Don't require sequence arguments to `Folder.*_items()` methods to support `len()` (e.g. generators and `map` instances
776 are now supported)
777 - Allow empty sequences as argument to `Folder.*_items()` methods
859778
860779 1.3.4
861780 -----
862781
863 - Add support for `required_attendees`, `optional_attendees` and
864 `resources` attribute on `folders.CalendarItem`. These are
865 implemented with a new `folders.Attendee` class.
782 - Add support for `required_attendees`, `optional_attendees` and
783 `resources` attribute on `folders.CalendarItem`. These are implemented with a new `folders.Attendee` class.
866784
867785 1.3.3
868786 -----
869787
870 - Add support for `organizer` attribute on `CalendarItem`. Implemented
871 with a new `folders.Mailbox` class.
788 - Add support for `organizer` attribute on `CalendarItem`. Implemented with a new `folders.Mailbox` class.
872789
873790 1.2
874791 ---
875792
876 - Initial import
877
793 - Initial import
794
278278 return cls(tz.zone)
279279
280280 @classmethod
281 def from_datetime(cls, tz):
282 """Convert from a standard library `datetime.timezone` instance."""
283 return cls(tz.tzname(None))
284
285 @classmethod
281286 def from_dateutil(cls, tz):
282287 # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
283288 # don't contain enough information to reliably match them with a CLDR timezone.
299304 return {
300305 cls.__module__.split('.')[0]: lambda z: z,
301306 'backports': cls.from_zoneinfo,
307 'datetime': cls.from_datetime,
302308 'dateutil': cls.from_dateutil,
303309 'pytz': cls.from_pytz,
304310 'zoneinfo': cls.from_zoneinfo,
305 'pytz_deprecation_shim': lambda z: cls.from_zoneinfo(z.unwrap_shim())
311 'pytz_deprecation_shim': lambda z: cls.from_timezone(z.unwrap_shim())
306312 }[tz_module](tz)
307313 except KeyError:
308314 raise TypeError('Unsupported tzinfo type: %r' % tz)
919925 return cls(tz.zone)
920926
921927 @classmethod
928 def from_datetime(cls, tz):
929 """Convert from a standard library `datetime.timezone` instance."""
930 return cls(tz.tzname(None))
931
932 @classmethod
922933 def from_dateutil(cls, tz):
923934 # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
924935 # don't contain enough information to reliably match them with a CLDR timezone.
940951 return {
941952 cls.__module__.split('.')[0]: lambda z: z,
942953 'backports': cls.from_zoneinfo,
954 'datetime': cls.from_datetime,
943955 'dateutil': cls.from_dateutil,
944956 'pytz': cls.from_pytz,
945957 'zoneinfo': cls.from_zoneinfo,
946 'pytz_deprecation_shim': lambda z: cls.from_zoneinfo(z.unwrap_shim())
958 'pytz_deprecation_shim': lambda z: cls.from_timezone(z.unwrap_shim())
947959 }[tz_module](tz)
948960 except KeyError:
949961 raise TypeError('Unsupported tzinfo type: %r' % tz)
10081020 </dl>
10091021 <h3>Static methods</h3>
10101022 <dl>
1023 <dt id="exchangelib.ewsdatetime.EWSTimeZone.from_datetime"><code class="name flex">
1024 <span>def <span class="ident">from_datetime</span></span>(<span>tz)</span>
1025 </code></dt>
1026 <dd>
1027 <div class="desc"><p>Convert from a standard library <code>datetime.timezone</code> instance.</p></div>
1028 <details class="source">
1029 <summary>
1030 <span>Expand source code</span>
1031 </summary>
1032 <pre><code class="python">@classmethod
1033 def from_datetime(cls, tz):
1034 &#34;&#34;&#34;Convert from a standard library `datetime.timezone` instance.&#34;&#34;&#34;
1035 return cls(tz.tzname(None))</code></pre>
1036 </details>
1037 </dd>
10111038 <dt id="exchangelib.ewsdatetime.EWSTimeZone.from_dateutil"><code class="name flex">
10121039 <span>def <span class="ident">from_dateutil</span></span>(<span>tz)</span>
10131040 </code></dt>
10821109 return {
10831110 cls.__module__.split(&#39;.&#39;)[0]: lambda z: z,
10841111 &#39;backports&#39;: cls.from_zoneinfo,
1112 &#39;datetime&#39;: cls.from_datetime,
10851113 &#39;dateutil&#39;: cls.from_dateutil,
10861114 &#39;pytz&#39;: cls.from_pytz,
10871115 &#39;zoneinfo&#39;: cls.from_zoneinfo,
1088 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_zoneinfo(z.unwrap_shim())
1116 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_timezone(z.unwrap_shim())
10891117 }[tz_module](tz)
10901118 except KeyError:
10911119 raise TypeError(&#39;Unsupported tzinfo type: %r&#39; % tz)</code></pre>
12511279 <ul class="two-column">
12521280 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.IANA_TO_MS_MAP" href="#exchangelib.ewsdatetime.EWSTimeZone.IANA_TO_MS_MAP">IANA_TO_MS_MAP</a></code></li>
12531281 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.MS_TO_IANA_MAP" href="#exchangelib.ewsdatetime.EWSTimeZone.MS_TO_IANA_MAP">MS_TO_IANA_MAP</a></code></li>
1282 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.from_datetime" href="#exchangelib.ewsdatetime.EWSTimeZone.from_datetime">from_datetime</a></code></li>
12541283 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.from_dateutil" href="#exchangelib.ewsdatetime.EWSTimeZone.from_dateutil">from_dateutil</a></code></li>
12551284 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.from_ms_id" href="#exchangelib.ewsdatetime.EWSTimeZone.from_ms_id">from_ms_id</a></code></li>
12561285 <li><code><a title="exchangelib.ewsdatetime.EWSTimeZone.from_pytz" href="#exchangelib.ewsdatetime.EWSTimeZone.from_pytz">from_pytz</a></code></li>
213213 query_string = None
214214 log.debug(
215215 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
216 self.account,
216217 self.folders,
217 self.account,
218218 shape,
219219 depth,
220220 additional_fields,
286286 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
287287 query_string = None
288288 yield from FindPeople(account=self.account, chunk_size=page_size).call(
289 folder=[folder],
289 folder=folder,
290290 additional_fields=additional_fields,
291291 restriction=restriction,
292292 order_fields=order_fields,
697697 query_string = None
698698 log.debug(
699699 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
700 self.account,
700701 self.folders,
701 self.account,
702702 shape,
703703 depth,
704704 additional_fields,
770770 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
771771 query_string = None
772772 yield from FindPeople(account=self.account, chunk_size=page_size).call(
773 folder=[folder],
773 folder=folder,
774774 additional_fields=additional_fields,
775775 restriction=restriction,
776776 order_fields=order_fields,
12701270 query_string = None
12711271 log.debug(
12721272 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
1273 self.account,
12731274 self.folders,
1274 self.account,
12751275 shape,
12761276 depth,
12771277 additional_fields,
13551355 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
13561356 query_string = None
13571357 yield from FindPeople(account=self.account, chunk_size=page_size).call(
1358 folder=[folder],
1358 folder=folder,
13591359 additional_fields=additional_fields,
13601360 restriction=restriction,
13611361 order_fields=order_fields,
46624662 query_string = None
46634663 log.debug(
46644664 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
4665 self.account,
46654666 self.folders,
4666 self.account,
46674667 shape,
46684668 depth,
46694669 additional_fields,
47354735 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
47364736 query_string = None
47374737 yield from FindPeople(account=self.account, chunk_size=page_size).call(
4738 folder=[folder],
4738 folder=folder,
47394739 additional_fields=additional_fields,
47404740 restriction=restriction,
47414741 order_fields=order_fields,
52355235 query_string = None
52365236 log.debug(
52375237 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
5238 self.account,
52385239 self.folders,
5239 self.account,
52405240 shape,
52415241 depth,
52425242 additional_fields,
53205320 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
53215321 query_string = None
53225322 yield from FindPeople(account=self.account, chunk_size=page_size).call(
5323 folder=[folder],
5323 folder=folder,
53245324 additional_fields=additional_fields,
53255325 restriction=restriction,
53265326 order_fields=order_fields,
57335733 from .base import Folder
57345734 # Subfolders will always be of class Folder
57355735 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
5736 all_fields.update(Folder.attribute_fields())
57365737 only_fields = []
57375738 for arg in args:
57385739 for field_path in all_fields:
59425943 from .base import Folder
59435944 # Subfolders will always be of class Folder
59445945 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
5946 all_fields.update(Folder.attribute_fields())
59455947 only_fields = []
59465948 for arg in args:
59475949 for field_path in all_fields:
7070 from .base import Folder
7171 # Subfolders will always be of class Folder
7272 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
73 all_fields.update(Folder.attribute_fields())
7374 only_fields = []
7475 for arg in args:
7576 for field_path in all_fields:
245246 from .base import Folder
246247 # Subfolders will always be of class Folder
247248 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
249 all_fields.update(Folder.attribute_fields())
248250 only_fields = []
249251 for arg in args:
250252 for field_path in all_fields:
454456 from .base import Folder
455457 # Subfolders will always be of class Folder
456458 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
459 all_fields.update(Folder.attribute_fields())
457460 only_fields = []
458461 for arg in args:
459462 for field_path in all_fields:
4545 from .transport import BASIC, DIGEST, NTLM, GSSAPI, SSPI, OAUTH2, CBA
4646 from .version import Build, Version
4747
48 __version__ = &#39;4.6.0&#39;
48 __version__ = &#39;4.6.1&#39;
4949
5050 __all__ = [
5151 &#39;__version__&#39;,
55765576 return cls(tz.zone)
55775577
55785578 @classmethod
5579 def from_datetime(cls, tz):
5580 &#34;&#34;&#34;Convert from a standard library `datetime.timezone` instance.&#34;&#34;&#34;
5581 return cls(tz.tzname(None))
5582
5583 @classmethod
55795584 def from_dateutil(cls, tz):
55805585 # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
55815586 # don&#39;t contain enough information to reliably match them with a CLDR timezone.
55975602 return {
55985603 cls.__module__.split(&#39;.&#39;)[0]: lambda z: z,
55995604 &#39;backports&#39;: cls.from_zoneinfo,
5605 &#39;datetime&#39;: cls.from_datetime,
56005606 &#39;dateutil&#39;: cls.from_dateutil,
56015607 &#39;pytz&#39;: cls.from_pytz,
56025608 &#39;zoneinfo&#39;: cls.from_zoneinfo,
5603 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_zoneinfo(z.unwrap_shim())
5609 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_timezone(z.unwrap_shim())
56045610 }[tz_module](tz)
56055611 except KeyError:
56065612 raise TypeError(&#39;Unsupported tzinfo type: %r&#39; % tz)
56655671 </dl>
56665672 <h3>Static methods</h3>
56675673 <dl>
5674 <dt id="exchangelib.EWSTimeZone.from_datetime"><code class="name flex">
5675 <span>def <span class="ident">from_datetime</span></span>(<span>tz)</span>
5676 </code></dt>
5677 <dd>
5678 <div class="desc"><p>Convert from a standard library <code>datetime.timezone</code> instance.</p></div>
5679 <details class="source">
5680 <summary>
5681 <span>Expand source code</span>
5682 </summary>
5683 <pre><code class="python">@classmethod
5684 def from_datetime(cls, tz):
5685 &#34;&#34;&#34;Convert from a standard library `datetime.timezone` instance.&#34;&#34;&#34;
5686 return cls(tz.tzname(None))</code></pre>
5687 </details>
5688 </dd>
56685689 <dt id="exchangelib.EWSTimeZone.from_dateutil"><code class="name flex">
56695690 <span>def <span class="ident">from_dateutil</span></span>(<span>tz)</span>
56705691 </code></dt>
57395760 return {
57405761 cls.__module__.split(&#39;.&#39;)[0]: lambda z: z,
57415762 &#39;backports&#39;: cls.from_zoneinfo,
5763 &#39;datetime&#39;: cls.from_datetime,
57425764 &#39;dateutil&#39;: cls.from_dateutil,
57435765 &#39;pytz&#39;: cls.from_pytz,
57445766 &#39;zoneinfo&#39;: cls.from_zoneinfo,
5745 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_zoneinfo(z.unwrap_shim())
5767 &#39;pytz_deprecation_shim&#39;: lambda z: cls.from_timezone(z.unwrap_shim())
57465768 }[tz_module](tz)
57475769 except KeyError:
57485770 raise TypeError(&#39;Unsupported tzinfo type: %r&#39; % tz)</code></pre>
74807502 query_string = None
74817503 log.debug(
74827504 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
7505 self.account,
74837506 self.folders,
7484 self.account,
74857507 shape,
74867508 depth,
74877509 additional_fields,
75537575 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
75547576 query_string = None
75557577 yield from FindPeople(account=self.account, chunk_size=page_size).call(
7556 folder=[folder],
7578 folder=folder,
75577579 additional_fields=additional_fields,
75587580 restriction=restriction,
75597581 order_fields=order_fields,
80538075 query_string = None
80548076 log.debug(
80558077 &#39;Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)&#39;,
8078 self.account,
80568079 self.folders,
8057 self.account,
80588080 shape,
80598081 depth,
80608082 additional_fields,
81388160 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
81398161 query_string = None
81408162 yield from FindPeople(account=self.account, chunk_size=page_size).call(
8141 folder=[folder],
8163 folder=folder,
81428164 additional_fields=additional_fields,
81438165 restriction=restriction,
81448166 order_fields=order_fields,
1276412786 <ul class="two-column">
1276512787 <li><code><a title="exchangelib.EWSTimeZone.IANA_TO_MS_MAP" href="#exchangelib.EWSTimeZone.IANA_TO_MS_MAP">IANA_TO_MS_MAP</a></code></li>
1276612788 <li><code><a title="exchangelib.EWSTimeZone.MS_TO_IANA_MAP" href="#exchangelib.EWSTimeZone.MS_TO_IANA_MAP">MS_TO_IANA_MAP</a></code></li>
12789 <li><code><a title="exchangelib.EWSTimeZone.from_datetime" href="#exchangelib.EWSTimeZone.from_datetime">from_datetime</a></code></li>
1276712790 <li><code><a title="exchangelib.EWSTimeZone.from_dateutil" href="#exchangelib.EWSTimeZone.from_dateutil">from_dateutil</a></code></li>
1276812791 <li><code><a title="exchangelib.EWSTimeZone.from_ms_id" href="#exchangelib.EWSTimeZone.from_ms_id">from_ms_id</a></code></li>
1276912792 <li><code><a title="exchangelib.EWSTimeZone.from_pytz" href="#exchangelib.EWSTimeZone.from_pytz">from_pytz</a></code></li>
744744
745745 time_window = EWSElementField(value_cls=TimeWindow, is_required=True)
746746 # Interval value is in minutes
747 merged_free_busy_interval = IntegerField(field_uri=&#39;MergedFreeBusyIntervalInMinutes&#39;, min=6, max=1440, default=30,
747 merged_free_busy_interval = IntegerField(field_uri=&#39;MergedFreeBusyIntervalInMinutes&#39;, min=5, max=1440, default=30,
748748 is_required=True)
749749 requested_view = ChoiceField(field_uri=&#39;RequestedView&#39;, choices={Choice(c) for c in REQUESTED_VIEWS},
750750 is_required=True) # Choice(&#39;None&#39;) is also valid, but only for responses
52575257
52585258 time_window = EWSElementField(value_cls=TimeWindow, is_required=True)
52595259 # Interval value is in minutes
5260 merged_free_busy_interval = IntegerField(field_uri=&#39;MergedFreeBusyIntervalInMinutes&#39;, min=6, max=1440, default=30,
5260 merged_free_busy_interval = IntegerField(field_uri=&#39;MergedFreeBusyIntervalInMinutes&#39;, min=5, max=1440, default=30,
52615261 is_required=True)
52625262 requested_view = ChoiceField(field_uri=&#39;RequestedView&#39;, choices={Choice(c) for c in REQUESTED_VIEWS},
52635263 is_required=True) # Choice(&#39;None&#39;) is also valid, but only for responses</code></pre>
691691 log.warning(&#39;Inconsistent next_offset values: %r. Using lowest value&#39;, next_offsets)
692692 return min(next_offsets)
693693
694 def _paged_call(self, payload_func, max_items, expected_message_count, **kwargs):
694 def _paged_call(self, payload_func, max_items, folders, **kwargs):
695695 &#34;&#34;&#34;Call a service that supports paging requests. Return a generator over all response items. Keeps track of
696696 all paging-related counters.
697697 &#34;&#34;&#34;
698 paging_infos = [dict(item_count=0, next_offset=None) for _ in range(expected_message_count)]
698 paging_infos = {f: dict(item_count=0, next_offset=None) for f in folders}
699699 common_next_offset = kwargs[&#39;offset&#39;]
700700 total_item_count = 0
701701 while True:
702 if not paging_infos:
703 # Paging is done for all folders
704 break
702705 log.debug(&#39;Getting page at offset %s (max_items %s)&#39;, common_next_offset, max_items)
703706 kwargs[&#39;offset&#39;] = common_next_offset
704 pages = self._get_pages(payload_func, kwargs, expected_message_count)
705 for (page, next_offset), paging_info in zip(pages, paging_infos):
707 kwargs[&#39;folders&#39;] = paging_infos.keys() # Only request the paging of the remaining folders.
708 pages = self._get_pages(payload_func, kwargs, len(paging_infos))
709 for (page, next_offset), (f, paging_info) in zip(pages, list(paging_infos.items())):
706710 paging_info[&#39;next_offset&#39;] = next_offset
707711 if isinstance(page, Exception):
712 # Assume this folder no longer works. Don&#39;t attempt to page it again.
713 log.debug(&#39;Exception occurred for folder %s. Removing.&#39;, f)
714 del paging_infos[f]
708715 yield page
709716 continue
710717 if page is not None:
717724 log.debug(&#34;&#39;max_items&#39; count reached (inner)&#34;)
718725 break
719726 if not paging_info[&#39;next_offset&#39;]:
720 # Paging is done for this message
727 # Paging is done for this folder. Don&#39;t attempt to page it again.
728 log.debug(&#39;Paging has completed for folder %s. Removing.&#39;, f)
729 del paging_infos[f]
721730 continue
731 log.debug(&#39;Folder %s still has items&#39;, f)
722732 # Check sanity of paging offsets, but don&#39;t fail. When we are iterating huge collections that take a
723733 # long time to complete, the collection may change while we are iterating. This can affect the
724734 # &#39;next_offset&#39; value and make it inconsistent with the number of already collected items.
732742 if max_items and total_item_count &gt;= max_items:
733743 log.debug(&#34;&#39;max_items&#39; count reached (outer)&#34;)
734744 break
735 common_next_offset = self._get_next_offset(paging_infos)
745 common_next_offset = self._get_next_offset(paging_infos.values())
736746 if common_next_offset is None:
737 # Paging is done for all messages
747 # Paging is done for all folders
738748 break
739749
740750 @staticmethod
741751 def _get_paging_values(elem):
742752 &#34;&#34;&#34;Read paging information from the paging container element.&#34;&#34;&#34;
753 offset_attr = elem.get(&#39;IndexedPagingOffset&#39;)
754 next_offset = None if offset_attr is None else int(offset_attr)
755 item_count = int(elem.get(&#39;TotalItemsInView&#39;))
743756 is_last_page = elem.get(&#39;IncludesLastItemInRange&#39;).lower() in (&#39;true&#39;, &#39;0&#39;)
744 offset = elem.get(&#39;IndexedPagingOffset&#39;)
745 if offset is None and not is_last_page:
746 log.debug(&#34;Not last page in range, but Exchange didn&#39;t send a page offset. Assuming first page&#34;)
747 offset = &#39;1&#39;
748 next_offset = None if is_last_page else int(offset)
749 item_count = int(elem.get(&#39;TotalItemsInView&#39;))
750 if not item_count and next_offset is not None:
751 raise ValueError(&#34;Expected empty &#39;next_offset&#39; when &#39;item_count&#39; is 0&#34;)
752 log.debug(&#39;Got page with next offset %s (last_page %s)&#39;, next_offset, is_last_page)
757 log.debug(&#39;Got page with offset %s, item_count %s, last_page %s&#39;, next_offset, item_count, is_last_page)
758 # Clean up contradictory paging values
759 if next_offset is None and not is_last_page:
760 log.debug(&#34;Not last page in range, but server didn&#39;t send a page offset. Assuming first page&#34;)
761 next_offset = 1
762 if next_offset is not None and is_last_page:
763 if next_offset != item_count:
764 log.debug(&#34;Last page in range, but we still got an offset. Assuming paging has completed&#34;)
765 next_offset = None
766 if not item_count and not is_last_page:
767 log.debug(&#34;Not last page in range, but also no items left. Assuming paging has completed&#34;)
768 next_offset = None
769 if item_count and next_offset == 0:
770 log.debug(&#34;Non-zero offset, but also no items left. Assuming paging has completed&#34;)
771 next_offset = None
753772 return item_count, next_offset
754773
755774 def _get_page(self, message):
17611780 log.warning(&#39;Inconsistent next_offset values: %r. Using lowest value&#39;, next_offsets)
17621781 return min(next_offsets)
17631782
1764 def _paged_call(self, payload_func, max_items, expected_message_count, **kwargs):
1783 def _paged_call(self, payload_func, max_items, folders, **kwargs):
17651784 &#34;&#34;&#34;Call a service that supports paging requests. Return a generator over all response items. Keeps track of
17661785 all paging-related counters.
17671786 &#34;&#34;&#34;
1768 paging_infos = [dict(item_count=0, next_offset=None) for _ in range(expected_message_count)]
1787 paging_infos = {f: dict(item_count=0, next_offset=None) for f in folders}
17691788 common_next_offset = kwargs[&#39;offset&#39;]
17701789 total_item_count = 0
17711790 while True:
1791 if not paging_infos:
1792 # Paging is done for all folders
1793 break
17721794 log.debug(&#39;Getting page at offset %s (max_items %s)&#39;, common_next_offset, max_items)
17731795 kwargs[&#39;offset&#39;] = common_next_offset
1774 pages = self._get_pages(payload_func, kwargs, expected_message_count)
1775 for (page, next_offset), paging_info in zip(pages, paging_infos):
1796 kwargs[&#39;folders&#39;] = paging_infos.keys() # Only request the paging of the remaining folders.
1797 pages = self._get_pages(payload_func, kwargs, len(paging_infos))
1798 for (page, next_offset), (f, paging_info) in zip(pages, list(paging_infos.items())):
17761799 paging_info[&#39;next_offset&#39;] = next_offset
17771800 if isinstance(page, Exception):
1801 # Assume this folder no longer works. Don&#39;t attempt to page it again.
1802 log.debug(&#39;Exception occurred for folder %s. Removing.&#39;, f)
1803 del paging_infos[f]
17781804 yield page
17791805 continue
17801806 if page is not None:
17871813 log.debug(&#34;&#39;max_items&#39; count reached (inner)&#34;)
17881814 break
17891815 if not paging_info[&#39;next_offset&#39;]:
1790 # Paging is done for this message
1816 # Paging is done for this folder. Don&#39;t attempt to page it again.
1817 log.debug(&#39;Paging has completed for folder %s. Removing.&#39;, f)
1818 del paging_infos[f]
17911819 continue
1820 log.debug(&#39;Folder %s still has items&#39;, f)
17921821 # Check sanity of paging offsets, but don&#39;t fail. When we are iterating huge collections that take a
17931822 # long time to complete, the collection may change while we are iterating. This can affect the
17941823 # &#39;next_offset&#39; value and make it inconsistent with the number of already collected items.
18021831 if max_items and total_item_count &gt;= max_items:
18031832 log.debug(&#34;&#39;max_items&#39; count reached (outer)&#34;)
18041833 break
1805 common_next_offset = self._get_next_offset(paging_infos)
1834 common_next_offset = self._get_next_offset(paging_infos.values())
18061835 if common_next_offset is None:
1807 # Paging is done for all messages
1836 # Paging is done for all folders
18081837 break
18091838
18101839 @staticmethod
18111840 def _get_paging_values(elem):
18121841 &#34;&#34;&#34;Read paging information from the paging container element.&#34;&#34;&#34;
1842 offset_attr = elem.get(&#39;IndexedPagingOffset&#39;)
1843 next_offset = None if offset_attr is None else int(offset_attr)
1844 item_count = int(elem.get(&#39;TotalItemsInView&#39;))
18131845 is_last_page = elem.get(&#39;IncludesLastItemInRange&#39;).lower() in (&#39;true&#39;, &#39;0&#39;)
1814 offset = elem.get(&#39;IndexedPagingOffset&#39;)
1815 if offset is None and not is_last_page:
1816 log.debug(&#34;Not last page in range, but Exchange didn&#39;t send a page offset. Assuming first page&#34;)
1817 offset = &#39;1&#39;
1818 next_offset = None if is_last_page else int(offset)
1819 item_count = int(elem.get(&#39;TotalItemsInView&#39;))
1820 if not item_count and next_offset is not None:
1821 raise ValueError(&#34;Expected empty &#39;next_offset&#39; when &#39;item_count&#39; is 0&#34;)
1822 log.debug(&#39;Got page with next offset %s (last_page %s)&#39;, next_offset, is_last_page)
1846 log.debug(&#39;Got page with offset %s, item_count %s, last_page %s&#39;, next_offset, item_count, is_last_page)
1847 # Clean up contradictory paging values
1848 if next_offset is None and not is_last_page:
1849 log.debug(&#34;Not last page in range, but server didn&#39;t send a page offset. Assuming first page&#34;)
1850 next_offset = 1
1851 if next_offset is not None and is_last_page:
1852 if next_offset != item_count:
1853 log.debug(&#34;Last page in range, but we still got an offset. Assuming paging has completed&#34;)
1854 next_offset = None
1855 if not item_count and not is_last_page:
1856 log.debug(&#34;Not last page in range, but also no items left. Assuming paging has completed&#34;)
1857 next_offset = None
1858 if item_count and next_offset == 0:
1859 log.debug(&#34;Non-zero offset, but also no items left. Assuming paging has completed&#34;)
1860 next_offset = None
18231861 return item_count, next_offset
18241862
18251863 def _get_page(self, message):
6464 return self._elems_to_objs(self._paged_call(
6565 payload_func=self.get_payload,
6666 max_items=max_items,
67 expected_message_count=len(folders),
67 folders=folders,
6868 **dict(
69 folders=folders,
7069 additional_fields=additional_fields,
7170 restriction=restriction,
7271 shape=shape,
162161 return self._elems_to_objs(self._paged_call(
163162 payload_func=self.get_payload,
164163 max_items=max_items,
165 expected_message_count=len(folders),
164 folders=folders,
166165 **dict(
167 folders=folders,
168166 additional_fields=additional_fields,
169167 restriction=restriction,
170168 shape=shape,
271269 return self._elems_to_objs(self._paged_call(
272270 payload_func=self.get_payload,
273271 max_items=max_items,
274 expected_message_count=len(folders),
272 folders=folders,
275273 **dict(
276 folders=folders,
277274 additional_fields=additional_fields,
278275 restriction=restriction,
279276 shape=shape,
6767 return self._elems_to_objs(self._paged_call(
6868 payload_func=self.get_payload,
6969 max_items=max_items,
70 expected_message_count=len(folders),
70 folders=folders,
7171 **dict(
72 folders=folders,
7372 additional_fields=additional_fields,
7473 restriction=restriction,
7574 order_fields=order_fields,
186185 return self._elems_to_objs(self._paged_call(
187186 payload_func=self.get_payload,
188187 max_items=max_items,
189 expected_message_count=len(folders),
188 folders=folders,
190189 **dict(
191 folders=folders,
192190 additional_fields=additional_fields,
193191 restriction=restriction,
194192 order_fields=order_fields,
317315 return self._elems_to_objs(self._paged_call(
318316 payload_func=self.get_payload,
319317 max_items=max_items,
320 expected_message_count=len(folders),
318 folders=folders,
321319 **dict(
322 folders=folders,
323320 additional_fields=additional_fields,
324321 restriction=restriction,
325322 order_fields=order_fields,
6969 return self._elems_to_objs(self._paged_call(
7070 payload_func=self.get_payload,
7171 max_items=max_items,
72 expected_message_count=1, # We can only query one folder, so there will only be one element in response
72 folders=[folder], # We can only query one folder, so there will only be one element in response
7373 **dict(
74 folder=folder,
7574 additional_fields=additional_fields,
7675 restriction=restriction,
7776 order_fields=order_fields,
9493 continue
9594 yield Persona.from_xml(elem, account=self.account)
9695
97 def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
96 def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
9897 offset=0):
98 folders = list(folders)
99 if len(folders) != 1:
100 raise ValueError(&#39;%r can only query one folder&#39; % self.SERVICE_NAME)
101 folder = folders[0]
99102 findpeople = create_element(&#39;m:%s&#39; % self.SERVICE_NAME, attrs=dict(Traversal=depth))
100103 personashape = create_shape_element(
101104 tag=&#39;m:PersonaShape&#39;, shape=shape, additional_fields=additional_fields, version=self.account.version
192195 return self._elems_to_objs(self._paged_call(
193196 payload_func=self.get_payload,
194197 max_items=max_items,
195 expected_message_count=1, # We can only query one folder, so there will only be one element in response
198 folders=[folder], # We can only query one folder, so there will only be one element in response
196199 **dict(
197 folder=folder,
198200 additional_fields=additional_fields,
199201 restriction=restriction,
200202 order_fields=order_fields,
217219 continue
218220 yield Persona.from_xml(elem, account=self.account)
219221
220 def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
222 def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
221223 offset=0):
224 folders = list(folders)
225 if len(folders) != 1:
226 raise ValueError(&#39;%r can only query one folder&#39; % self.SERVICE_NAME)
227 folder = folders[0]
222228 findpeople = create_element(&#39;m:%s&#39; % self.SERVICE_NAME, attrs=dict(Traversal=depth))
223229 personashape = create_shape_element(
224230 tag=&#39;m:PersonaShape&#39;, shape=shape, additional_fields=additional_fields, version=self.account.version
326332 return self._elems_to_objs(self._paged_call(
327333 payload_func=self.get_payload,
328334 max_items=max_items,
329 expected_message_count=1, # We can only query one folder, so there will only be one element in response
335 folders=[folder], # We can only query one folder, so there will only be one element in response
330336 **dict(
331 folder=folder,
332337 additional_fields=additional_fields,
333338 restriction=restriction,
334339 order_fields=order_fields,
342347 </details>
343348 </dd>
344349 <dt id="exchangelib.services.find_people.FindPeople.get_payload"><code class="name flex">
345 <span>def <span class="ident">get_payload</span></span>(<span>self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size, offset=0)</span>
350 <span>def <span class="ident">get_payload</span></span>(<span>self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size, offset=0)</span>
346351 </code></dt>
347352 <dd>
348353 <div class="desc"></div>
350355 <summary>
351356 <span>Expand source code</span>
352357 </summary>
353 <pre><code class="python">def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
358 <pre><code class="python">def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
354359 offset=0):
360 folders = list(folders)
361 if len(folders) != 1:
362 raise ValueError(&#39;%r can only query one folder&#39; % self.SERVICE_NAME)
363 folder = folders[0]
355364 findpeople = create_element(&#39;m:%s&#39; % self.SERVICE_NAME, attrs=dict(Traversal=depth))
356365 personashape = create_shape_element(
357366 tag=&#39;m:PersonaShape&#39;, shape=shape, additional_fields=additional_fields, version=self.account.version
19781978 return self._elems_to_objs(self._paged_call(
19791979 payload_func=self.get_payload,
19801980 max_items=max_items,
1981 expected_message_count=len(folders),
1981 folders=folders,
19821982 **dict(
1983 folders=folders,
19841983 additional_fields=additional_fields,
19851984 restriction=restriction,
19861985 shape=shape,
20872086 return self._elems_to_objs(self._paged_call(
20882087 payload_func=self.get_payload,
20892088 max_items=max_items,
2090 expected_message_count=len(folders),
2089 folders=folders,
20912090 **dict(
2092 folders=folders,
20932091 additional_fields=additional_fields,
20942092 restriction=restriction,
20952093 shape=shape,
21952193 return self._elems_to_objs(self._paged_call(
21962194 payload_func=self.get_payload,
21972195 max_items=max_items,
2198 expected_message_count=len(folders),
2196 folders=folders,
21992197 **dict(
2200 folders=folders,
22012198 additional_fields=additional_fields,
22022199 restriction=restriction,
22032200 order_fields=order_fields,
23262323 return self._elems_to_objs(self._paged_call(
23272324 payload_func=self.get_payload,
23282325 max_items=max_items,
2329 expected_message_count=len(folders),
2326 folders=folders,
23302327 **dict(
2331 folders=folders,
23322328 additional_fields=additional_fields,
23332329 restriction=restriction,
23342330 order_fields=order_fields,
24452441 return self._elems_to_objs(self._paged_call(
24462442 payload_func=self.get_payload,
24472443 max_items=max_items,
2448 expected_message_count=1, # We can only query one folder, so there will only be one element in response
2444 folders=[folder], # We can only query one folder, so there will only be one element in response
24492445 **dict(
2450 folder=folder,
24512446 additional_fields=additional_fields,
24522447 restriction=restriction,
24532448 order_fields=order_fields,
24702465 continue
24712466 yield Persona.from_xml(elem, account=self.account)
24722467
2473 def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
2468 def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
24742469 offset=0):
2470 folders = list(folders)
2471 if len(folders) != 1:
2472 raise ValueError(&#39;%r can only query one folder&#39; % self.SERVICE_NAME)
2473 folder = folders[0]
24752474 findpeople = create_element(&#39;m:%s&#39; % self.SERVICE_NAME, attrs=dict(Traversal=depth))
24762475 personashape = create_shape_element(
24772476 tag=&#39;m:PersonaShape&#39;, shape=shape, additional_fields=additional_fields, version=self.account.version
25792578 return self._elems_to_objs(self._paged_call(
25802579 payload_func=self.get_payload,
25812580 max_items=max_items,
2582 expected_message_count=1, # We can only query one folder, so there will only be one element in response
2581 folders=[folder], # We can only query one folder, so there will only be one element in response
25832582 **dict(
2584 folder=folder,
25852583 additional_fields=additional_fields,
25862584 restriction=restriction,
25872585 order_fields=order_fields,
25952593 </details>
25962594 </dd>
25972595 <dt id="exchangelib.services.FindPeople.get_payload"><code class="name flex">
2598 <span>def <span class="ident">get_payload</span></span>(<span>self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size, offset=0)</span>
2599 </code></dt>
2600 <dd>
2601 <div class="desc"></div>
2602 <details class="source">
2603 <summary>
2604 <span>Expand source code</span>
2605 </summary>
2606 <pre><code class="python">def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
2596 <span>def <span class="ident">get_payload</span></span>(<span>self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size, offset=0)</span>
2597 </code></dt>
2598 <dd>
2599 <div class="desc"></div>
2600 <details class="source">
2601 <summary>
2602 <span>Expand source code</span>
2603 </summary>
2604 <pre><code class="python">def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
26072605 offset=0):
2606 folders = list(folders)
2607 if len(folders) != 1:
2608 raise ValueError(&#39;%r can only query one folder&#39; % self.SERVICE_NAME)
2609 folder = folders[0]
26082610 findpeople = create_element(&#39;m:%s&#39; % self.SERVICE_NAME, attrs=dict(Traversal=depth))
26092611 personashape = create_shape_element(
26102612 tag=&#39;m:PersonaShape&#39;, shape=shape, additional_fields=additional_fields, version=self.account.version
16551655 using EWS is available at
16561656 [https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/notification-subscriptions-mailbox-events-and-ews-in-exchange](https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/notification-subscriptions-mailbox-events-and-ews-in-exchange):
16571657
1658 The following shows how to synchronize folders and items:
16581659 ```python
16591660 from exchangelib import Account
1660 from exchangelib.properties import CopiedEvent, CreatedEvent, DeletedEvent, \
1661 ModifiedEvent, MovedEvent, NewMailEvent, StatusEvent, FreeBusyChangedEvent
1662 form exchangelib.services import SendNotification
16631661
16641662 a = Account(...)
16651663
16691667 # SyncFolderHierarchy.CHANGE_TYPES
16701668 pass
16711669 # The next time you call a.inbox.sync_hierarchy(), you will only get folder
1672 # changes since the last .sync_hierarchy() call.
1670 # changes since the last .sync_hierarchy() call. The sync status is stored in
1671 # a.inbox.folder_sync_state.
16731672
16741673 # Synchronize your local items within a.inbox
16751674 for change_type, item in a.inbox.sync_items():
16771676 # SyncFolderItems.CHANGE_TYPES
16781677 pass
16791678 # The next time you call a.inbox.sync_items(), you will only get item changes
1680 # since the last .sync_items() call.
1681
1682 # Create a pull subscription that can be used to pull events from the server
1679 # since the last .sync_items() call. The sync status is stored in
1680 # a.inbox.item_sync_state.
1681 ```
1682
1683 Here's how to create a pull subscription that can be used to pull events from the server:
1684 ```python
16831685 subscription_id, watermark = a.inbox.subscribe_to_pull()
1684
1685 # Create a push subscription. The server will regularly send an HTTP POST
1686 # request to the callback URL to deliver changes or a status message.
1686 ```
1687
1688 Here's how to create a push subscription. The server will regularly send an HTTP POST
1689 request to the callback URL to deliver changes or a status message. There is also support
1690 for parsing the POST data that the Exchange server sends to the callback URL.
1691 ```python
16871692 subscription_id, watermark = a.inbox.subscribe_to_push(
16881693 callback_url='https://my_app.example.com/callback_url'
16891694 )
1690 # When the server sends a push notification, the POST data contains a
1691 # 'SendNotification' XML document. You can use exchangelib in the callback URL
1692 # implementation to parse this data:
1693 ws = SendNotification(protocol=a.protocol)
1694 for notification in ws.parse(response.data):
1695 # ws.parse() returns Notification objects
1696 pass
1697
1698 # Create a streaming subscription that can be used to stream events from the
1699 # server.
1695 ```
1696
1697 When the server sends a push notification, the POST data contains a
1698 'SendNotification' XML document. You can use exchangelib in the callback URL
1699 implementation to parse this data. Here's a short example of a Flask app that
1700 handles these documents:
1701 ```python
1702 from exchangelib.services import SendNotification
1703 from flask import Flask, request
1704
1705 app = Flask(__name__)
1706
1707 @app.route('/callback_url', methods=['POST'])
1708 def upload_file():
1709 ws = SendNotification(protocol=None)
1710 for notification in ws.parse(request.data):
1711 # ws.parse() returns Notification objects
1712 pass
1713 ```
1714
1715 Here's how to create a streaming subscription that can be used to stream events from the
1716 server.
1717 ```python
17001718 subscription_id = a.inbox.subscribe_to_streaming()
1701
1702 # Cancel the subscription. Does not apply to push subscriptions that cancel
1703 # automatically after a certain amount of failed attempts.
1719 ```
1720
1721 Cancel the subscription when you're done synchronizing. This is not supported for push
1722 subscriptions. They cancel automatically after a certain amount of failed attempts.
1723 ```python
17041724 a.inbox.unsubscribe(subscription_id)
1705
1706 # You can also use one of the three context managers that handle unsubscription
1707 # automatically:
1725 ```
1726
1727 When creating subscriptions, you can also use one of the three context managers
1728 that handle unsubscription automatically:
1729 ```python
17081730 with a.inbox.pull_subscription() as (subscription_id, watermark):
17091731 pass
17101732
17221744 `watermark` attribute.
17231745
17241746 ```python
1747 from exchangelib.properties import CopiedEvent, CreatedEvent, DeletedEvent, \
1748 ModifiedEvent
1749
17251750 for notification in a.inbox.get_events(subscription_id, watermark):
17261751 for event in notification.events:
17271752 if isinstance(event, (CreatedEvent, ModifiedEvent)):
17461771 on `Configuration.max_connections` on how to increase the connection count.
17471772
17481773 ```python
1774 from exchangelib.properties import MovedEvent, NewMailEvent, StatusEvent, \
1775 FreeBusyChangedEvent
1776
17491777 for notification in a.inbox.get_streaming_events(
17501778 subscription_id, connection_timeout=1
17511779 ):
1717 from .transport import BASIC, DIGEST, NTLM, GSSAPI, SSPI, OAUTH2, CBA
1818 from .version import Build, Version
1919
20 __version__ = '4.6.0'
20 __version__ = '4.6.1'
2121
2222 __all__ = [
2323 '__version__',
250250 return cls(tz.zone)
251251
252252 @classmethod
253 def from_datetime(cls, tz):
254 """Convert from a standard library `datetime.timezone` instance."""
255 return cls(tz.tzname(None))
256
257 @classmethod
253258 def from_dateutil(cls, tz):
254259 # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
255260 # don't contain enough information to reliably match them with a CLDR timezone.
271276 return {
272277 cls.__module__.split('.')[0]: lambda z: z,
273278 'backports': cls.from_zoneinfo,
279 'datetime': cls.from_datetime,
274280 'dateutil': cls.from_dateutil,
275281 'pytz': cls.from_pytz,
276282 'zoneinfo': cls.from_zoneinfo,
277 'pytz_deprecation_shim': lambda z: cls.from_zoneinfo(z.unwrap_shim())
283 'pytz_deprecation_shim': lambda z: cls.from_timezone(z.unwrap_shim())
278284 }[tz_module](tz)
279285 except KeyError:
280286 raise TypeError('Unsupported tzinfo type: %r' % tz)
185185 query_string = None
186186 log.debug(
187187 'Finding %s items in folders %s (shape: %s, depth: %s, additional_fields: %s, restriction: %s)',
188 self.account,
188189 self.folders,
189 self.account,
190190 shape,
191191 depth,
192192 additional_fields,
258258 restriction = Restriction(q, folders=[folder], applies_to=Restriction.ITEMS)
259259 query_string = None
260260 yield from FindPeople(account=self.account, chunk_size=page_size).call(
261 folder=[folder],
261 folder=folder,
262262 additional_fields=additional_fields,
263263 restriction=restriction,
264264 order_fields=order_fields,
4242 from .base import Folder
4343 # Subfolders will always be of class Folder
4444 all_fields = self.folder_collection.get_folder_fields(target_cls=Folder, is_complex=None)
45 all_fields.update(Folder.attribute_fields())
4546 only_fields = []
4647 for arg in args:
4748 for field_path in all_fields:
716716
717717 time_window = EWSElementField(value_cls=TimeWindow, is_required=True)
718718 # Interval value is in minutes
719 merged_free_busy_interval = IntegerField(field_uri='MergedFreeBusyIntervalInMinutes', min=6, max=1440, default=30,
719 merged_free_busy_interval = IntegerField(field_uri='MergedFreeBusyIntervalInMinutes', min=5, max=1440, default=30,
720720 is_required=True)
721721 requested_view = ChoiceField(field_uri='RequestedView', choices={Choice(c) for c in REQUESTED_VIEWS},
722722 is_required=True) # Choice('None') is also valid, but only for responses
663663 log.warning('Inconsistent next_offset values: %r. Using lowest value', next_offsets)
664664 return min(next_offsets)
665665
666 def _paged_call(self, payload_func, max_items, expected_message_count, **kwargs):
666 def _paged_call(self, payload_func, max_items, folders, **kwargs):
667667 """Call a service that supports paging requests. Return a generator over all response items. Keeps track of
668668 all paging-related counters.
669669 """
670 paging_infos = [dict(item_count=0, next_offset=None) for _ in range(expected_message_count)]
670 paging_infos = {f: dict(item_count=0, next_offset=None) for f in folders}
671671 common_next_offset = kwargs['offset']
672672 total_item_count = 0
673673 while True:
674 if not paging_infos:
675 # Paging is done for all folders
676 break
674677 log.debug('Getting page at offset %s (max_items %s)', common_next_offset, max_items)
675678 kwargs['offset'] = common_next_offset
676 pages = self._get_pages(payload_func, kwargs, expected_message_count)
677 for (page, next_offset), paging_info in zip(pages, paging_infos):
679 kwargs['folders'] = paging_infos.keys() # Only request the paging of the remaining folders.
680 pages = self._get_pages(payload_func, kwargs, len(paging_infos))
681 for (page, next_offset), (f, paging_info) in zip(pages, list(paging_infos.items())):
678682 paging_info['next_offset'] = next_offset
679683 if isinstance(page, Exception):
684 # Assume this folder no longer works. Don't attempt to page it again.
685 log.debug('Exception occurred for folder %s. Removing.', f)
686 del paging_infos[f]
680687 yield page
681688 continue
682689 if page is not None:
689696 log.debug("'max_items' count reached (inner)")
690697 break
691698 if not paging_info['next_offset']:
692 # Paging is done for this message
699 # Paging is done for this folder. Don't attempt to page it again.
700 log.debug('Paging has completed for folder %s. Removing.', f)
701 del paging_infos[f]
693702 continue
703 log.debug('Folder %s still has items', f)
694704 # Check sanity of paging offsets, but don't fail. When we are iterating huge collections that take a
695705 # long time to complete, the collection may change while we are iterating. This can affect the
696706 # 'next_offset' value and make it inconsistent with the number of already collected items.
704714 if max_items and total_item_count >= max_items:
705715 log.debug("'max_items' count reached (outer)")
706716 break
707 common_next_offset = self._get_next_offset(paging_infos)
717 common_next_offset = self._get_next_offset(paging_infos.values())
708718 if common_next_offset is None:
709 # Paging is done for all messages
719 # Paging is done for all folders
710720 break
711721
712722 @staticmethod
713723 def _get_paging_values(elem):
714724 """Read paging information from the paging container element."""
725 offset_attr = elem.get('IndexedPagingOffset')
726 next_offset = None if offset_attr is None else int(offset_attr)
727 item_count = int(elem.get('TotalItemsInView'))
715728 is_last_page = elem.get('IncludesLastItemInRange').lower() in ('true', '0')
716 offset = elem.get('IndexedPagingOffset')
717 if offset is None and not is_last_page:
718 log.debug("Not last page in range, but Exchange didn't send a page offset. Assuming first page")
719 offset = '1'
720 next_offset = None if is_last_page else int(offset)
721 item_count = int(elem.get('TotalItemsInView'))
722 if not item_count and next_offset is not None:
723 raise ValueError("Expected empty 'next_offset' when 'item_count' is 0")
724 log.debug('Got page with next offset %s (last_page %s)', next_offset, is_last_page)
729 log.debug('Got page with offset %s, item_count %s, last_page %s', next_offset, item_count, is_last_page)
730 # Clean up contradictory paging values
731 if next_offset is None and not is_last_page:
732 log.debug("Not last page in range, but server didn't send a page offset. Assuming first page")
733 next_offset = 1
734 if next_offset is not None and is_last_page:
735 if next_offset != item_count:
736 log.debug("Last page in range, but we still got an offset. Assuming paging has completed")
737 next_offset = None
738 if not item_count and not is_last_page:
739 log.debug("Not last page in range, but also no items left. Assuming paging has completed")
740 next_offset = None
741 if item_count and next_offset == 0:
742 log.debug("Non-zero offset, but also no items left. Assuming paging has completed")
743 next_offset = None
725744 return item_count, next_offset
726745
727746 def _get_page(self, message):
3636 return self._elems_to_objs(self._paged_call(
3737 payload_func=self.get_payload,
3838 max_items=max_items,
39 expected_message_count=len(folders),
39 folders=folders,
4040 **dict(
41 folders=folders,
4241 additional_fields=additional_fields,
4342 restriction=restriction,
4443 shape=shape,
3939 return self._elems_to_objs(self._paged_call(
4040 payload_func=self.get_payload,
4141 max_items=max_items,
42 expected_message_count=len(folders),
42 folders=folders,
4343 **dict(
44 folders=folders,
4544 additional_fields=additional_fields,
4645 restriction=restriction,
4746 order_fields=order_fields,
4141 return self._elems_to_objs(self._paged_call(
4242 payload_func=self.get_payload,
4343 max_items=max_items,
44 expected_message_count=1, # We can only query one folder, so there will only be one element in response
44 folders=[folder], # We can only query one folder, so there will only be one element in response
4545 **dict(
46 folder=folder,
4746 additional_fields=additional_fields,
4847 restriction=restriction,
4948 order_fields=order_fields,
6665 continue
6766 yield Persona.from_xml(elem, account=self.account)
6867
69 def get_payload(self, folder, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
68 def get_payload(self, folders, additional_fields, restriction, order_fields, query_string, shape, depth, page_size,
7069 offset=0):
70 folders = list(folders)
71 if len(folders) != 1:
72 raise ValueError('%r can only query one folder' % self.SERVICE_NAME)
73 folder = folders[0]
7174 findpeople = create_element('m:%s' % self.SERVICE_NAME, attrs=dict(Traversal=depth))
7275 personashape = create_shape_element(
7376 tag='m:PersonaShape', shape=shape, additional_fields=additional_fields, version=self.account.version
100100 self.assertEqual(
101101 EWSTimeZone('UTC'),
102102 EWSTimeZone.from_timezone(dateutil.tz.UTC)
103 )
104 self.assertEqual(
105 EWSTimeZone('UTC'),
106 EWSTimeZone.from_timezone(datetime.timezone.utc)
103107 )
104108
105109 def test_localize(self):
250250 </m:SendNotification>
251251 </soap11:Body>
252252 </soap11:Envelope>'''
253 ws = SendNotification(protocol=self.account.protocol)
253 ws = SendNotification(protocol=None)
254254 self.assertListEqual(
255255 list(ws.parse(xml)),
256256 [Notification(subscription_id='XXXXX=', previous_watermark='AAAAA=', more_events=False,