Как отладить вызов win32com в python
Чтобы собрать выходные данные из сценария ведения журнала, я хотел бы использовать onepy для добавления информации в записную книжку OneNote 2013. К сожалению, метод update_page_content()
предоставленный onepy не работает для меня. Чтобы глубже понять проблему, я переключился на C#, где существует множество онлайн-примеров для API OneNote, и после некоторых проблем мне удалось заставить работать следующий минималистичный пример C#:
using System;
using OneNote = Microsoft.Office.Interop.OneNote;
class Program
{
static void Main(string[] args)
{
OneNote.Application onenoteApp = new OneNote.Application();
string xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>";
onenoteApp.UpdatePageContent(xml, DateTime.MinValue);
}
}
Строка xml
был получен путем изменения документа XML, который был получен из OneNote с использованием GetPageContent
метод, как подробно описано в моем связанном предыдущем вопросе. Точное содержание xml
ради этого вопроса не имеет значения, единственное, что важно, это то, что вышеуказанная программа работает без проблем раз за разом, и изменения на существующей странице OneNote всегда выполняются успешно.
Переходя на Python, я попытался перевести свою минималистичную программу, не внося существенных изменений. Мой результат выглядит следующим образом:
import win32com
import pytz
import datetime
onenote_app = win32com.client.Dispatch('OneNote.Application.15')
xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>"
date = pytz.utc.localize(datetime.datetime.fromordinal(1))
onenote_app.UpdatePageContent(xml, date)
Я постарался использовать одинаковые значения для двух переменных. Конечно, содержание двух строк xml
идентичен (копировать и вставить). Кроме того, согласно отладчику VS2015, оба DateTime.MinValue
а также date
обратитесь к той же дате. Однако когда я выполняю программу на python, я получаю эту очень бесполезную ошибку.
135 def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
136 return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137 , dateExpectedLastModified, xsSchema, force)
138
139 _prop_map_get_ = {
com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147213296), None)
Насколько я понимаю, и C#, и Python фактически используют одну и ту же библиотеку для выполнения своих вызовов (оба называются Microsoft OneNote 15.0 Object Library
). Так что в принципе обе программы должны работать нормально. Если я не ошибаюсь в этом вопросе, я бы предположил, что Python делает что-то другое в своем обращении к библиотеке. Как я могу проследить, в чем здесь проблема? Есть ли способ использовать встроенную поддержку Python в Visual Studio 2015, чтобы лучше понять разницу между C# и кодом Python?
2 ответа
Как Jayongg уже указывает в своем ответе, моя проблема заключается в дате, переданной в качестве аргумента UpdatePageContent()
вызов. Однако его предложение передать ноль в качестве даты последнего изменения и отключить проверку даты, как подробно описано в документации, не так просто, как может показаться. В своем ответе я подробно опишу все подводные камни, с которыми я столкнулся.
Первая проблема показана в этом вопросе. Если кто-то пытается передать, например, datetime
объект или простое целое число UpdatePageContent()
затем возникает ошибка, подобная следующей.
135 def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
136 return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137 , dateExpectedLastModified, xsSchema, force)
138
139 _prop_map_get_ = {
ValueError: astimezone() cannot be applied to a naive datetime
В частности, если оставить аргумент даты пустым и использовать значение по умолчанию, он не будет работать должным образом:
135 def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
136 return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137 , dateExpectedLastModified, xsSchema, force)
138
139 _prop_map_get_ = {
TypeError: must be a pywintypes time object (got tuple)
По-видимому, некоторые внутренние вызовы API Python перепутаны. Если следовать инструкциям на странице проекта onepy и использовать makepy
чтобы предварительно сгенерировать обертку API, можно проверить исходные файлы, ответственные за эти вызовы, но, по крайней мере, для меня это было не очень поучительно. Я подозреваю, что InvokeTypes
Метод пытается поместить переданную дату в формат, понятный API, но необходимые требования не реализованы в самой оболочке Python.
К счастью, существует довольно простой обходной путь. Хитрость заключается в том, чтобы сделать пройденное datetime
объект с учетом часового пояса. Для этого нужно сначала локализовать его. Это может быть сделано, например, с помощью pytz
модуль.
import datetime
import pytz
date = pytz.utc.localize(datetime.datetime.fromordinal(1))
С этим знанием вызов API работает, но возникает ошибка COM из моего вопроса. Вот где приходит ответ jayongg: мне нужно передать ноль как дату последнего изменения (или мне нужно знать дату, когда произошло последнее изменение, что также должно работать). Теперь сложный вопрос: что такое ноль?
В моем коде C# эта дата задается DateTime.MinValue
, что по данным Visual Studio 2015 равно 0001-01-01 00:00:00. Та же дата в Python datetime.datetime.fromordinal(1)
, но это все еще не заставляет звонок работать. Я должен подозревать, что информация Visual Studio неверна или что между ними происходит какое-то волшебство, возможно, приведение типа VSDate -> Int -> APIDate.
Итак, как мне узнать, какую правильную нулевую дату пройти? Оказывается, ответ уже есть, нужно просто знать, где искать. Если вы проверяете в оболочке Python API, для рассматриваемого аргумента есть значение по умолчанию:
dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0)
То же самое можно получить с помощью следующего фрагмента.
>> date = pytz.utc.localize(datetime.datetime(year=1899, month=12, day=30))
>> print(tuple(date.timetuple()))
(1899, 12, 30, 0, 0, 0, 5, 364, 0)
И вуаля, передача переменной даты на вызов работает просто отлично.
>> onenote_app.UpdatePageContent(xml, date)
>>
Имеет смысл, верно? Я имею в виду, какую другую дату вы бы выбрали вместо 1899-12-30? На самом деле это имеет какой-то смысл. Этот день за один день до нулевой точки Дублинской Юлианской Даты. Согласно немецкой статье в Википедии, этот день используется Excel в качестве нулевой точки для дат, поэтому кажется правдоподобным, что OneNote делает то же самое.
Почему разница в один день? По-видимому, 1900 год неправильно воспринимается как високосный, чего не было. Таким образом, 1899-12-31 смещается на 1899-12-30, чего и хочет API. Вздох. Почему существует так много разных соглашений о том, как хранить даты? О, просто чтобы упомянуть. Office для Mac использует 1904 как нулевую точку. Да, конечно.
Код ошибки (-2147213296): 0x80042010, что:
hrLastModifiedDateDidNotMatch
0x80042010
Дата последнего изменения не совпадает.
https://msdn.microsoft.com/en-us/library/office/ff966472(v=office.14).aspx
Вы можете попробовать передать дату последнего изменения 0. От: https://msdn.microsoft.com/en-us/library/office/gg649853(v=office.14).aspx: dateExpectedLastModified— (необязательно) Дата и время, когда вы думаете, что страница, которую вы хотите обновить, была в последний раз изменена. Если вы передаете ненулевое значение для этого параметра, OneNote продолжит обновление, только если переданное вами значение соответствует фактической дате и времени последней модификации страницы. Передача значения для этого параметра помогает предотвратить случайную перезапись изменений, внесенных пользователями с момента последней модификации страницы.