Как отладить вызов win32com в python

Чтобы собрать выходные данные сценария ведения журнала, я хотел бы использовать onepy для добавления информации в Блокнот OneNote 2013. К сожалению, метод update_page_content(), предоставленный onepy, у меня не работает. Чтобы лучше понять проблему, я переключился на C#, где существует много онлайн-примеров для OneNote API, и после некоторых проблем Мне удалось заставить работать следующий минималистичный пример 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?


person ranguwud    schedule 20.01.2016    source источник


Ответы (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 в качестве нулевой точки. Да, конечно.

person ranguwud    schedule 24.01.2016

Ваш код ошибки (-2147213296) — 0x80042010, то есть:

хрластмодифиеддатедиднотматч

0x80042010

Дата последнего изменения не совпадает.

https://msdn.microsoft.com/en-us/library/office/ff966472(v=office.14).aspx

Вы можете попробовать передать дату последнего изменения 0. From: https://msdn.microsoft.com/en-us/library/office/gg649853(v=office.14).aspx: dateExpectedLastModified — (необязательно) дата и время, когда, по вашему мнению, страница который вы хотите обновить, был последним изменен. Если вы передадите ненулевое значение для этого параметра, OneNote продолжит обновление только в том случае, если переданное вами значение соответствует фактической дате и времени последнего изменения страницы. Передача значения для этого параметра помогает предотвратить случайную перезапись изменений, сделанных пользователями с момента последнего изменения страницы.

person jayongg    schedule 20.01.2016
comment
Вы правы в своем подозрении, что проблема в прошедшей дате. Тем временем я запустил программу python и добавлю свое решение в качестве ответа, как только найду время для этого. Однако вы не отвечаете на мой более общий вопрос: откуда вы знаете, что -2147213296 означает 0x80042010? Эти два числа не кажутся связанными со мной. По крайней мере, преобразование -2147213296 в шестнадцатеричный формат дает мне 0x7ffbdff0, что совершенно не связано. - person ranguwud; 21.01.2016
comment
на самом деле это дает вам -0x7ffbdff0 (отрицательное число). Вам нужно преобразовать его в положительное. Вы делаете это, добавляя 0x100000000 к результату. - person jayongg; 22.01.2016
comment
Это верно, но кажется мне произвольным. Я мог бы также добавить 0xffffffff и получить, например, другой результат. Является ли общим правилом добавление 0x100000000, что всегда верно? Кроме того, почему нечего добавить для положительных возвращаемых значений? - person ranguwud; 22.01.2016
comment
Это не произвольно. Добавление 0x100000000 (и отбрасывание переносимого бита) - это то, как вы можете преобразовать 32-битное целое число со знаком в целое число без знака. - person jayongg; 23.01.2016