Windows, COM, 1C и Python

Пролог

Потребовалось сделать выгрузку из 1С в куда-то еще. 1С версии 8.2, но подобным же образом можно получить данные из 7.7, 8.0, 8.1 и думается 8.3 — только названия документов другие.
Данные будем получать через COM — на сколько я знаю, у 1Сников это довольно распростаненный способ, и документации довольно много.
Для работы с COM на питоне потребуется модуль win32com — установка ничем необычным не отличается.

Для напримера, из стандартной конфигурации 1C Бухгалтерия 2.0 забирать список выставленных счетов и отправлять на некий сайт.

Глава 1. Подключение к 1С.

Для подключение потребуется создать объект V82.COMConnector, и подключить соответствующую базу. Путь до базы, логин и пароль для доступа прописываются в соответсвующей строке подключения:

import win32com.client
CONSTR='File="C:\\DB\\";Usr="com";Pwd="com"'
v82 = win32com.client.Dispatch("V82.COMConnector").Connect(CON_STR)

Тут сразу возможна проблема, 32х-битная 1С не прописывается в 64-разрядной системе. Исправить не сложно: Администрирование->Службы компонентов. Компьютеры->Мой Компьютер->Приложения COM+. Создать новое приложение — серверное приложение — название V82_COMConnector. После, на вкладке «компонетны» созданного приложения добавить новый компонент: (установка новых компонентов) и указать на файлик comcntr.dll в комплекте 1С.

Если никаких эксцепшенов не выкинуло — продолжаем. 1С создана очень умными людьми, для других не очень умных людей, поэтому все в 1С через жопу. Например все объекты конфигурации называются кириллицей, что из-за наличия множества кодировок, вообще-то не очень удобно. Но это не самое печальное. Печальней то, что при работе с COM/1C используются сразу две кодировки. 1С хочет видеть запросы в CP-1251, а результаты возвращать в UTF8. Так что простым coding: cp1251 не обойтись, и потребуются пляски.

Для доступа к COM объектами с русскими названиями использоваться точку (.) не получится, будем использовать функцию getattr(). И немного модифицируем её, второй параметр она будет транслировать в cp1251

get = lambda obj,attr: getattr(obj, attr.encode('cp1251'))

Глава 2. Доступ к данным

Подготовительная работа закончилась, начинаем получать результаты.
Всю структуру данных можно невозбранно подсмотреть в конфигураторе 1С. И это единственный приятный момент, хотя нет: все встроенные типы, объекты, методы имеют так же названия на английском языке, и это тоже экономит нервы и время. Имена эти можно также подсмотреть в конфигураторе, в соответсвующем разделе справочной системы.
Например объект конфигурации «Документы», имеет английское название Documents и соответственно будет доступен через точку:
v82.Documents. А вот сами документы называются по-русски, и начинается небольшое извращение:

invoices = get(v82.Documents, u"СчетНаОплатуПокупателю")

Это «Менеджер документов» по внутренней терминологии 1С, для того, чтобы получить сами документы необходимо их выбрать, например методом Select(), которому можно передать несколько необязательных параметров в виде периода выборки.
После чего, можно пролистать выборку методом Next() этого менеджера, а сам документ получить с помощью метода GetObject()
Упрощено, получается так:

sel = get(v82.Documents, u"СчетНаОплатуПокупателю").Select(d_start, d_end)
while sel.Next():
    doc = sel.GetObject()
    ...

Данные из текущего документа вытаскиваем так же, с помощью getattr() или нашей get():

amount = get(doc, u"СуммаДокумента")

А некоторые обязательные поля, например дату и номер документа имеют англоязычные аналоги, и можно по-нормальному:

number = doc.Number
date = doc.Date

Эпилог

На этом все шаманства заканчиваются — дальше пишем скрипт используя вышеизложенные знания:

import win32com.client
from datetime import date, datetime, timedelta
from json import dumps
from httplib import HTTPConnection
from urllib import urlencode
 
SERVER = "192.168.0.1" 
URL = "/post/"
KEY="SecretKey"
days_upload=10
con_str = 'File="C:\\DB";Usr="com";Pwd="com"'
 
v82 = win32com.client.Dispatch("V82.COMConnector").Connect(con_str)
 
get = lambda obj,attr: getattr(obj, attr.encode('cp1251'))
 
d_start = date.today()-timedelta(days=days_upload)
d_end = date.today()
 
invoices = []
contractors = {}
 
sel = get(v82.Documents, u"СчетНаОплатуПокупателю").Select(d_start, d_end)
 
while sel.Next():
    doc = sel.GetObject()
    contr = get(doc,u"Контрагент")
    if not get(contr, u'Код') in contractors:
    	c = {
    		'code': get(contr, u'Код'),
    		'name': get(contr, u"Наименование"),
    	}
    	contractors[c['code']] = c
    dt = {
    	"number": doc.Number,
    	"date": "%s" % doc.Date,
    	"amount": get(doc, u"СуммаДокумента"),
    	"contr": get(contr, u"Код"),
    	"text":  get(doc, u'Комментарий')
    }
    invoices.append(dt)
 
params ={"invoices": dumps(invoices), "contractors": dumps(contractors), "key":KEY}
 
con = HTTPConnection(SERVER)
con.request("POST", URL, urlencode(params))
r = con.getresponse()
print r.status, r.reason
f = open("response.html","w+")
f.write(r.read())
f.close()
con.close()