error in function to_xml in fields.AppointmentStateField
FirePanda169 opened this issue · 7 comments
Describe the bug
When trying to move a CalendarItem from one mailbox to another, an error occurs with the types of AppointmentStateField
It occurs when you try to create it in a new mailbox.
It occurs due to a type mismatch in the conversion.
AppointmentStateField.from_xml returns type tuple, but AppointmentStateField.value_cls = int
To Reproduce
Expected behavior
Log output
Additional context
Python 3.11.8
exchangelib 5.2.0
Can you please provide some example code showing what you tried to do? And the error message or stack trace that you got? CalendarItem.appointment_state
is a read-only field, so it's surprising that you reached AppointmentStateField.from_xml
.
I'm trying to transfer all messages from one mailbox to another.
Messages like MeetingRequest too.
Example
get_folder - GetFolder
get_item - GetItem
create_item - CreateItem
These are wrappers on functions for convenience.
...
message_source = ews_control_source.get_item(task.source_message_oid, task.source_message_changekey)
folder_parent_target = ews_control_target.get_folder(task.target_folder_oid)
...
message_target = ews_control_target.create_item(folder_parent_target, message_source)
task.target_message_oid = message_target.id
task.target_message_changekey = message_target.changekey
stack trace
Traceback (most recent call last):
File "/app/t2t_migrator/workers/queue_base.py", line 70, in __call__
await current_function(task)
File "/app/t2t_migrator/workers/queue_mail_message.py", line 71, in ews_mail_message_create
await self.mail_control.create_mail_message_by_ews(
File "/app/t2t_migrator/controls/mail.py", line 1398, in create_mail_message_by_ews
message_target = ews_control_target.create_item(folder_parent_target, message_source)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/t2t_migrator/controls/ews.py", line 101, in create_item
return list(CreateItem(account=self.account).call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/services/common.py", line 225, in _elems_to_objs
for elem in elems:
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/services/common.py", line 287, in _chunked_get_elements
yield from self._get_elements(payload=payload_func(chunk, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/services/create_item.py", line 95, in get_payload
set_xml_value(item_elems, item, version=self.account.version)
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/util.py", line 259, in set_xml_value
elem.append(value.to_xml(version=version))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/properties.py", line 319, in to_xml
self.clean(version=version)
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/properties.py", line 300, in clean
setattr(self, f.name, f.clean(val, version=version))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/fields.py", line 444, in clean
value = super().clean(value, version=version)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/pypoetry/virtualenvs/migrationt2t-9TtSrW0h-py3.11/lib/python3.11/site-packages/exchangelib/fields.py", line 334, in clean
raise TypeError(f"Field {self.name!r} value {value!r} must be of type {self.value_cls}")
TypeError: Field 'appointment_state' value ('Meeting', 'Received') must be of type <class 'int'>
You didn't include enough code for me to be able to reproduce.
Anyway, this is not a bug. You're calling CreateItem
directly in "/app/t2t_migrator/controls/ews.py", line 101. You're free to use the low-level components of exchangelib, but then you need to handle the details yourself, for example which fields are read-only and which aren't.
If you want exchangelib to handle all that, then do something like CalendarItem(account=target_account, subject=..., ...).save()
.
I still think this is a type error.
This happens because AppointmentStateField inherits from IntegerField and AppointmentStateField.value_cls = int, and AppointmentStateField.from_xml returns tuple[str].
I understand that this is a read-only field. It does not go further into the request. Even if it goes away (by copying the library locally and conducting experiments), it does not create an error.
For myself in the fork, I will leave the code AppointmentStateField.from_xml, which does not throw an exception.
def from_xml(self, elem, account):
return super().from_xml(elem=elem, account=account)
Thank you.
I have one more question.
I use the same code to transfer all messages and there was a problem that all letters are marked Draft.
Using ExtendedProperty, I can remove this.
# add property
class IsDraft(ExtendedProperty):
property_tag = 0x0E07 # 3591
property_type = "Integer"
...
Item.register("extended_is_draft", IsDraft)
...
message_source = ews_control_source.get_item(task.source_message_oid, task.source_message_changekey)
message_source.extended_is_draft = message_source.is_draft
message_target = ews_control_target.create_item(folder_parent_target, message_source)
Can you tell me if there could be a similar property in ExtendedProperty for AppointmentState?
AppointmentStateField
is special because it's implemented as a bitmask in EWS. EWS sends an integer in XML. Therefore, the field is implemented as an integer field. But bitmasks are annoying to work with in Python, so we help the user by expanding the bitmask options to a tuple. Since the field is read-only, there's no point in implementing AppointmentStateField.to_xml()
because there's no path to it via the supported API in exchangelib.
It's also possible that AppointmentState
is indeed read-write but we just haven't found the correct incantations to write back the value. But https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/appointmentstate explicitly states that the field is read-only.
I don't know if AppointmentState
can be accessed via an extended property, unfortunately.
When transferring items from one account to another, you also have the possibility to export and upload.
I agree with you about working with bit masks and working with this field.
I have a specific task (data migration from one mailbox to another). We used imap, but we couldn’t find how to access archived mailboxes.
I'll try this option with Export and upload
Thank you