def getReportResourceFilename(report_filename='', report_dir=''): """ Получить полное имя файла шаблона отчета. @param report_filename: Имя файла отчета в кратком виде. @param report_dir: Папка отчетов. @return: Полное имя файла отчета. """ # Проверить расширение rprt_filename = report_filename if not rprt_filename.endswith(DEFAULT_REPORT_FILE_EXT): rprt_filename = os.path.splitext( rprt_filename)[0] + DEFAULT_REPORT_FILE_EXT # Проверить актуальность шаблона full_src_filename = getPathFilename(report_filename, report_dir) full_rprt_filename = getPathFilename(rprt_filename, report_dir) if isNewReportTemplateFile(full_src_filename, full_rprt_filename): # Если исходный шаблон изменен позже чем рабочий файл шаблона <rprt> # то необходимо сделать изменения updateReportTemplateFile(full_src_filename, full_rprt_filename) if os.path.exists(rprt_filename): # Проверить может быть задано абсолютное имя файла filename = rprt_filename else: # Задано скорее всего относительное имя файла # относительно папки отчетов filename = full_rprt_filename if not os.path.exists(filename): # Нет такого файла log.warning(u'Файл шаблона отчета <%s> не найден' % textfunc.toUnicode(filename)) filename = createReportResourceFile(filename) log.debug(u'Полное имя файла шаблона <%s>' % textfunc.toUnicode(filename)) return filename
def NewByOffice(self, dst_path=None): """ Создание нового отчета средствами LibreOffice Calc. @param dst_path: Результирующая папка, в которую будет помещен новый файл. """ try: src_filename = DEFAULT_REP_TMPL_FILE new_filename = dlg.getTextInputDlg( self._ParentForm, u'Создание нового файла', u'Введите имя файла шаблона отчета') if os.path.splitext(new_filename)[1] != '.ods': new_filename += '.ods' if dst_path is None: # Необходимо определить результирующий путь dst_path = dlg.getDirDlg(self._ParentForm, u'Папка хранения') if not dst_path: dst_path = os.getcwd() dst_filename = os.path.join(dst_path, new_filename) if os.path.exists(dst_filename): if dlg.getAskBox(u'Заменить существующий файл?'): shutil.copyfile(src_filename, dst_filename) else: shutil.copyfile(src_filename, dst_filename) cmd = OFFICE_OPEN_CMD_FORMAT % dst_filename log.debug(u'Command <%s>' % textfunc.toUnicode(cmd)) os.system(cmd) return True except: # Вывести сообщение об ошибке в лог log.fatal(u'New report template by LibreOffice Calc')
def save(self, report_data=None, is_virtual_excel=True): """ Сохранить результаты генерации в файл @param report_data: Сгенерированный отчет. @param is_virtual_excel: Сохранение произвести с помощью VirtualExcel? True - да, False - Сохранение производится конвертацией с помощью UNOCONV. ВНИМАНИЕ! При конвертации с помощью UNOCONV ячейки не образмериваются. Размеры ячеек остаются по умолчанию. UNOCONV транслирует не все стили и атрибуты ячеек. @return: Имя сохраненного файла или None, если сохранения не произошло. """ if report_data: rep_file = icrepfile.icExcelXMLReportFile() save_dir = self.getProfileDir() if not save_dir: save_dir = icrepgensystem.DEFAULT_REPORT_DIR # print(u'DBG:', save_dir, report_data, type(report_data)) xml_rep_file_name = os.path.join( save_dir, '%s_report_result.xml' % report_data['name']) rep_file_name = os.path.join( save_dir, '%s_report_result.ods' % report_data['name']) rep_file.write(xml_rep_file_name, report_data) if is_virtual_excel: log.info(u'Конвертация отчета <%s> в файл <%s>' % (textfunc.toUnicode(xml_rep_file_name), textfunc.toUnicode(rep_file_name))) v_excel = icexcel.icVExcel() v_excel.Load(xml_rep_file_name) v_excel.SaveAs(rep_file_name) else: # ВНИМАНИЕ! UNOCONV транслирует не все стили и атрибуты ячеек # Поэтому сначала используется Virtual Excel cmd = 'unoconv --format=ods %s' % xml_rep_file_name log.info( u'UNOCONV. Конвертация отчета <%s> в файл <%s>. (%s)' % (textfunc.toUnicode(xml_rep_file_name), textfunc.toUnicode(rep_file_name), textfunc.toUnicode(cmd))) os.system(cmd) return rep_file_name return None
def generate(self, report=None, db_url=None, sql=None, stylelib=None, vars=None, *args, **kwargs): """ Запустить генератор отчета. @param report: Шаблон отчета. @param db_url: Connection string в виде url. Например postgresql+psycopg2://postgres:[email protected]:5432/realization. @param sql: Запрос SQL. @param stylelib: Библиотека стилей. @param vars: Словарь переменных отчета. @return: Возвращает сгенерированный отчет или None в случае ошибки. """ try: if report is not None: self._Rep = report if stylelib: self._Rep['style_lib'] = stylelib if vars: self._Rep['variables'] = vars # 1. Получить таблицу запроса _kwargs = copy.deepcopy(kwargs) _kwargs.update( dict(db_url=db_url, sql=sql, stylelib=stylelib, variables=vars)) query_tbl = self.getQueryTbl(self._Rep, **_kwargs) if self._isEmptyQueryTbl(query_tbl): dlg.getMsgBox( u'Внимание', u'Нет данных, соответствующих запросу: %s' % self._Rep['query'], self._ParentForm) return None # 2. Запустить генерацию rep = icrepgen.icReportGenerator() data_rep = rep.generate(self._Rep, query_tbl, NameSpace_=vars, *args, **kwargs) return data_rep except: # Вывести сообщение об ошибке в лог log.fatal(u'Ошибка генерации отчета <%s>.' % textfunc.toUnicode(self._Rep['name'])) return None
def write(self, RepFileName_, RepData_): """ Сохранить заполненный отчет в файле. @param RepFileName_: Имя файла отчета XML. @param RepData_: Данные отчета. @return: Функция возвращает имя созданного xml файла, или None в случае ошибки. """ xml_file = None try: # Начать запись xml_file = open(RepFileName_, 'w') xml_gen = icXMLSSGenerator(xml_file) xml_gen.startDocument() xml_gen.startBook() # Параметры страницы # xml_gen.savePageSetup(RepName_,Rep_) # Стили xml_gen.scanStyles(RepData_['sheet']) xml_gen.saveStyles() # Данные xml_gen.startSheet(RepData_['name'], RepData_) xml_gen.saveColumns(RepData_['sheet']) for i_row in xrange(len(RepData_['sheet'])): xml_gen.startRow(RepData_['sheet'][i_row]) # Сбросить индекс ячейки xml_gen.cell_idx = 1 for i_col in range(len(RepData_['sheet'][i_row])): cell = RepData_['sheet'][i_row][i_col] xml_gen.saveCell(i_row + 1, i_col + 1, cell, RepData_['sheet']) xml_gen.endRow() xml_gen.endSheet(RepData_) # Закончить запись xml_gen.endBook() xml_gen.endDocument() xml_file.close() return RepFileName_ except: if xml_file: xml_file.close() log.error(u'Ошибка сохранения отчета <%s>.' % textfunc.toUnicode(RepFileName_)) raise return None
def createReportResourceFile(template_filename): """ Создать ресурсный файл шаблона по имени запрашиваемого. @param template_filename: Имя запрашиваемого файла шаблона. @return: Скорректированное имя созданного файла шаблона или None в случае ошибки. """ # Коррекция имени файла с учетом русских букв в имени файла dir_name = os.path.dirname(template_filename) base_filename = os.path.basename(template_filename).replace(' ', '_') base_filename = textfunc.rus2lat(base_filename) if textfunc.isRUSText( base_filename) else base_filename norm_tmpl_filename = os.path.join(dir_name, base_filename) log.info(u'Создание нового файла шаблона <%s>' % norm_tmpl_filename) # Последовательно проверяем какой файл можно взять за основу для шаблона for ext in report_generator.SRC_REPORT_EXT: src_filename = os.path.splitext(template_filename)[0] + ext unicode_src_filename = textfunc.toUnicode(src_filename) if os.path.exists(src_filename): # Да такой файл есть и он может выступать # в качестве источника для шаблона log.info(u'Найден источник шаблона отчета <%s>' % unicode_src_filename) try: rep_generator = report_generator.createReportGeneratorSystem( ext) return rep_generator.Update(src_filename) except: log.fatal(u'Ошибка конвертации шаблона отчета <%s> -> <%s>' % (unicode_src_filename, norm_tmpl_filename)) return None log.warning( u'Не найдены источники шаблонов отчета в папке <%s> для <%s>' % (dir_name, textfunc.toUnicode(os.path.basename(template_filename)))) return None
def _genHeader(self, Header_): """ Сгенерировать заголовок отчета и перенести ее в выходной отчет. @param Header_: Бэнд заголовка. @return: Возвращает результат выполнения операции True/False. """ try: log.debug(u'Генерация заголовка') # Добавлять будем в конец отчета, # поэтому опреелить максимальную строчку max_row = len(self._Rep['sheet']) i_row = 0 cur_height = 0 # Перебрать все ячейки бэнда for row in range(Header_['row'], Header_['row'] + Header_['row_size']): for col in range(Header_['col'], Header_['col'] + Header_['col_size']): if self._TemplateSheet[row][col]: self._genCell(self._TemplateSheet, row, col, self._Rep, max_row + i_row, col, self._CurRec) cur_height = self._TemplateSheet[row][col]['height'] i_row += 1 # Увеличить текущую координату Y self._cur_top += cur_height # Прописать область self._Rep['header'] = { 'row': max_row, 'col': Header_['col'], 'row_size': i_row, 'col_size': Header_['col_size'], } # Очистить сумы суммирующих ячеек self._TemplateSheet = self._clearSum(self._TemplateSheet, 0, len(self._TemplateSheet)) return True except: # Вывести сообщение об ошибке в лог log.fatal(u'Ошибка генерации заголовка отчета <%s>.' % textfunc.toUnicode(self._RepName)) return False
def parse(self, TemplateData_, template_name=None): """ Разобрать/преобразовать прочитанную структуру. @param TemplateData_: Словарь описания шаблона. @param template_name: Имя шаблона(листа), если None то первый лист. """ try: # Создать первоначальный шаблон rep = copy.deepcopy(icrepgen.IC_REP_TMPL) # 0. Определение основных структур workbook = TemplateData_['children'][0] # Стили (в виде словаря) styles = dict([(style['ID'], style) for style in [element for element in workbook['children'] if element['name'] == 'Styles'][0]['children']]) worksheets = [element for element in workbook['children'] if element['name'] == 'Worksheet'] # I. Определить все бэнды в шаблоне # Если имя шаблона не определено, тогда взять первый лист if template_name is None: template_name = worksheets[0]['Name'] self._rep_worksheet = worksheets[0] else: # Установить активной страницу выбранного шаблона отчета self._rep_worksheet = [sheet for sheet in worksheets if sheet['Name'] == template_name][0] # Прописать имя отчета rep['name'] = template_name # Взять таблицу rep_template_tabs = [rep_obj for rep_obj in self._rep_worksheet['children'] if rep_obj['name'] == 'Table'] self._setDefaultCellSize(rep_template_tabs[0]) # Привести таблицу к нормальному виду rep_template_tab = self._normTable(rep_template_tabs[0]) # Взять описания колонок rep_template_cols = [element for element in rep_template_tab['children'] if element['name'] == 'Column'] rep_template_cols = self._defineSpan(rep_template_cols) # Взять описания строк rep_template_rows = [element for element in rep_template_tab['children'] if element['name'] == 'Row'] rep_template_rows = self._defineSpan(rep_template_rows) # Количество колонок без колонки тегов бендов col_count = self._getColumnCount(rep_template_rows) log.debug(u'Количество колонок: %d' % col_count) # II. Определить все ячейки листа used_rows = range(len(rep_template_rows)) used_cols = range(col_count) self.__cur_band = None # Тег текущего бенда # Перебор по строкам for cur_row in used_rows: if not self._isTitleBand(rep_template_rows, cur_row): # Не колонтитулы, добавить ячейки в общий лист rep['sheet'].append([]) for cur_col in used_cols: cell_attr = self._getCellAttr(rep_template_rows, rep_template_cols, styles, cur_row, cur_col) if not self._isTag(cell_attr['value']): rep['sheet'][-1].append(cell_attr) else: rep['sheet'][-1].append(None) # III. Определить бэнды в шаблоне # Перебрать все ячейки первой колонки self.__cur_band = None # Тег текущего бенда title_row = 0 # Счетчик строк колонтитулов/заголовочных бендов # Перебор всех строк в шаблоне for cur_row in range(len(rep_template_rows)): tag = self._getTagBandRow(rep_template_rows, cur_row) # Если это ячейка с определенным тегом, значит новый бенд if tag: # Определить текущий бэнд self.__cur_band = tag if tag in TITLE_TAGS: # Обработка строк заголовочных тегов parse_func = self._TitleTagParse.setdefault(tag, None) try: parse_func(self, rep, rep_template_rows[cur_row]['children']) except: log.fatal(u'Ошибка парсинга функции <%s>' % textfunc.toUnicode(parse_func)) title_row += 1 else: # Определить бэнд внутри объекта rep = self._defBand(self.__cur_band, cur_row, col_count, title_row, rep) else: log.error(u'Не корректный тег строки [%d]' % cur_row) # Прочитать в шаблон параметры страницы rep['page_setup'] = copy.deepcopy(icrepgen.IC_REP_PAGESETUP) sheet_options = [rep_obj for rep_obj in self._rep_worksheet['children'] if rep_obj['name'] == 'WorksheetOptions'] page_setup = [rep_obj for rep_obj in sheet_options[0]['children'] if rep_obj['name'] == 'PageSetup'][0] rep['page_setup'].update(self._getPageSetup(page_setup)) print_setup = [rep_obj for rep_obj in sheet_options[0]['children'] if rep_obj['name'] == 'Print'] if print_setup: rep['page_setup'].update(self._getPrintSetup(print_setup[0])) # Проверить заполнение генератора отчета if not rep['generator']: tmpl_filename = self.getTemplateFilename() rep['generator'] = os.path.splitext(tmpl_filename)[1].upper() if tmpl_filename else '.ODS' return rep except: log.fatal(u'Ошибка парсинга шаблона отчета <%s>' % textfunc.toUnicode(template_name)) return None
def _genTxt(self, cell, record=None, CellRow_=None, CellCol_=None): """ Генерация текста. @param cell: Ячейка. @param record: Словарь, описывающий текущую запись таблицы запроса. Формат: { <имя поля> : <значение поля>, ...} @param CellRow_: Номер строки ячейки в результирующем отчете. @param CellCol_: Номер колонки ячейки в результирующем отчете. @return: Возвращает сгенерированное значение. """ try: # Проверка на преобразование типов cell_val = cell['value'] if cell_val is not None and type(cell_val) not in (str, unicode): cell_val = str(cell_val) if cell_val not in self._cellFmt: parsed_fmt = self.funcStrParse(cell_val) self._cellFmt[cell_val] = parsed_fmt else: parsed_fmt = self._cellFmt[cell_val] func_str = [] # Выходной список значений i_sum = 0 # Перебрать строки функционала for cur_func in parsed_fmt['func']: # Функция if re.search(REP_FUNC_PATT, cur_func): value = str(self._execFuncGen(cur_func[2:-2], locals())) # Ламбда-выражение elif re.search(REP_LAMBDA_PATT, cur_func): # ВНИМАНИЕ: Лямбда-выражение в шаблоне должно иметь # 1 аргумент это словарь записи. # Например: # [~rec: rec['name']=='Петров'~] lambda_func = eval('lambda ' + cur_func[2:-2]) value = str(lambda_func(record)) # Переменная elif re.search(REP_VAR_PATT, cur_func): var_name = cur_func[2:-2] if var_name in self._NameSpace: log.debug(u'Обработка переменной <%s>' % var_name) else: log.warning( u'Переменная <%s> не найдена в пространстве имен' % var_name) value = str(self._NameSpace.setdefault(var_name, u'')) # Блок кода elif re.search(REP_EXEC_PATT, cur_func): # ВНИМАНИЕ: В блоке кода доступны объекты cell и record. # Если надо вывести информацию, то ее надо выводить в # переменную value. # Например: # [=if record['name']=='Петров': # value='-'=] value = '' exec_func = cur_func[2:-2].strip() try: exec exec_func except: log.fatal(u'Ошибка выполнения блока кода <%s>' % textfunc.toUnicode(exec_func)) # Системная функция elif re.search(REP_SYS_PATT, cur_func): # Функция суммирования if cur_func[2:6].lower() == 'sum(': value = str(cell['sum'][i_sum]['value']) i_sum += 1 # Перейти к следующей сумме # Функция вычисления среднего значения elif cur_func[2:6].lower() == 'avg(': if 'ic_sys_num_rec' not in record: record['ic_sys_num_rec'] = 0 value = str(cell['sum'][i_sum]['value'] / (record['ic_sys_num_rec'] + 1)) i_sum += 1 # Перейти к следующей сумме elif cur_func[2:-2].lower() == 'n': if 'ic_sys_num_rec' not in record: record['ic_sys_num_rec'] = 0 sys_num_rec = record['ic_sys_num_rec'] value = str(sys_num_rec + 1) else: # Вывести сообщение об ошибке в лог log.warning( u'Неизвестная системная функция <%s> шаблона <%s>.' % (textfunc.toUnicode(cur_func), self._RepName)) value = '' # Стиль elif re.search(REP_STYLE_PATT, cur_func): value = '' style_name = cur_func[2:-2] self._setStyleAttr(style_name) # Под-отчеты elif re.search(REP_SUBREPORT_PATT, cur_func): value = '' subreport_name = cur_func[2:-2] self._genSubReport(subreport_name, CellRow_) # Поле elif re.search(REP_FIELD_PATT, cur_func): field_name = str((cur_func[2:-2])) try: value = record[field_name] except KeyError: log.warning(u'В строке (%s) поле <%s> не найдено' % (textfunc.toUnicode(record), textfunc.toUnicode(field_name))) value = '' # ВНИМАНИЕ! В значении ячейки тоже могут быть управляющие коды value = self._genTxt({'value': value}, record) func_str.append(value) # Заполнение формата return self._valueFormat(parsed_fmt['fmt'], func_str) except: # Вывести сообщение об ошибке в лог log.fatal(u'Ошибка генерации текста ячейки <%s> шаблона <%s>.' % (textfunc.toUnicode(cell['value']), self._RepName)) return None
def generate(self, RepTemplate_, QueryTable_, NameSpace_=None, CoordFill_=None): """ Генерация отчета. @param RepTemplate_: Структура шаблона отчета (см. спецификации). @param QueryTable_: Таблица запроса. Словарь следующей структуры: { '__name__':имя таблицы запроса, '__fields__':[имена полей], '__data__':[список списков значений], '__sub__':{словарь данных подотчетов}, }. @param NameSpace_: Пространство имен шаблона. Обычный словарь: { 'имя переменной': значение переменной, }. ВНИМАНИЕ! Этот словарь может передаваться в таблице запроса ключ __variables__. @param CoordFill_: Координатное заполнение значений ячеек. Формат: { (Row,Col): 'Значение', }. ВНИМАНИЕ! Этот словарь может передаваться в таблице запроса ключ __coord_fill__. @return: Заполненную структуру отчета. """ try: # Покоординатная замена значений ячеек self._CoordFill = CoordFill_ if QueryTable_ and '__coord_fill__' in QueryTable_: if self._CoordFill is None: self._CoordFill = dict() self._CoordFill.update(QueryTable_['__coord_fill__']) # Инициализация списка групп self._RepGrp = list() # I. Определить все бэнды в шаблоне и ячейки сумм if isinstance(RepTemplate_, dict): self._Template = RepTemplate_ else: # Вывести сообщение об ошибке в лог log.warning(u'Ошибка типа шаблона отчета <%s>.' % type(RepTemplate_)) return None # Инициализация имени отчета if 'name' in QueryTable_ and QueryTable_['name']: # Если таблица запроса именована, то значит это имя готового отчета self._RepName = str(QueryTable_['name']) elif 'name' in self._Template: self._RepName = self._Template['name'] # Заполнить пространство имен self._NameSpace = NameSpace_ if self._NameSpace is None: self._NameSpace = dict() self._NameSpace.update(self._Template['variables']) if QueryTable_ and '__variables__' in QueryTable_: self._NameSpace.update(QueryTable_['__variables__']) log.debug(u'Переменные отчета: %s' % self._NameSpace.keys()) # Библиотека стилей self._StyleLib = None if 'style_lib' in self._Template: self._StyleLib = self._Template['style_lib'] self._TemplateSheet = self._Template['sheet'] self._TemplateSheet = self._initSumCells(self._TemplateSheet) # II. Инициализация таблицы запроса self._QueryTbl = QueryTable_ # Определить количество записей в таблице запроса self._QueryTblRecCount = 0 if self._QueryTbl and '__data__' in self._QueryTbl: self._QueryTblRecCount = len(self._QueryTbl['__data__']) # Проинициализировать бенды групп for grp in self._Template['groups']: grp['old_rec'] = None time_start = time.time() log.info(u'Отчет <%s>. Запуск генерации' % textfunc.toUnicode(self._RepName)) # III. Вывод данных в отчет # Создать отчет self._Rep = copy.deepcopy(IC_REP_TMPL) self._Rep['name'] = self._RepName # Инициализация необходимых переменных field_idx = {} # Индексы полей i = 0 i_rec = 0 # Перебор полей таблицы запроса if self._QueryTbl and '__fields__' in self._QueryTbl: for cur_field in self._QueryTbl['__fields__']: field_idx[cur_field] = i i += 1 # Если записи в таблице запроса есть, то ... if self._QueryTblRecCount: # Проинициализировать текущую строку для использования # ее в заголовке отчета rec = self._QueryTbl['__data__'][i_rec] # Заполнить словарь текущей записи for field_name in field_idx.keys(): val = rec[field_idx[field_name]] # Предгенерация значения данных ячейки self._CurRec[field_name] = val # Прописать индекс текущей записи self._CurRec['ic_sys_num_rec'] = i_rec # Верхний колонтитул if self._Template['upper']: self._genUpper(self._Template['upper']) # Вывести в отчет заголовок self._genHeader(self._Template['header']) # Главный цикл # Перебор записей таблицы запроса while i_rec < self._QueryTblRecCount: # Обработка групп # Проверка смены группы в описании всех групп # и найти индекс самой общей смененной группы i_grp_out = -1 # индекс самой общей смененной группы # Флаг начала генерации (примечания групп не выводяться) start_gen = False for i_grp in range(len(self._Template['groups'])): grp = self._Template['groups'][i_grp] if grp['old_rec']: # Проверить условие вывода примечания группы if self._CurRec[grp['field']] != grp['old_rec'][ grp['field']]: i_grp_out = i_grp break else: i_grp_out = 0 start_gen = True break if i_grp_out != -1: # Вывести примечания if start_gen is False: for i_grp in range( len(self._Template['groups']) - 1, i_grp_out - 1, -1): grp = self._Template['groups'][i_grp] self._genGrpFooter(grp) # Вывести заголовки for i_grp in range(i_grp_out, len(self._Template['groups'])): grp = self._Template['groups'][i_grp] grp['old_rec'] = copy.deepcopy(self._CurRec) self._genGrpHeader(grp) # Область данных self._genDetail(self._Template['detail']) # Увеличить суммы суммирующих ячеек self._sumIterate(self._TemplateSheet, self._CurRec) # Перейти на следующую запись i_rec += 1 # Заполнить словарь текущей записи if i_rec < self._QueryTblRecCount: rec = self._QueryTbl['__data__'][i_rec] # Заполнить словарь текущей записи for field_name in field_idx.keys(): val = rec[field_idx[field_name]] # Предгенерация значения данных ячейки self._CurRec[field_name] = val # Прописать индекс текущей записи self._CurRec['ic_sys_num_rec'] = i_rec # Вывести примечания после области данных for i_grp in range(len(self._Template['groups']) - 1, -1, -1): grp = self._Template['groups'][i_grp] if grp['old_rec']: self._genGrpFooter(grp) else: break # Вывести в отчет примечание отчета self._genFooter(self._Template['footer']) # Нижний колонтитул if self._Template['under']: self._genUnder(self._Template['under']) # Параметры страницы self._Rep['page_setup'] = self._Template['page_setup'] # Прогресс бар log.info( u'Отчет <%s>. Окончание генерации. Время: %d сек.' % (textfunc.toUnicode(self._RepName), time.time() - time_start)) return self._Rep except: # Вывести сообщение об ошибке в лог log.fatal(u'Ошибка генерации отчета.') return None
def _getSQLQueryTable(self, report, db_url=None, sql=None): """ Получить таблицу запроса. @param report: Шаблон отчета. @param db_url: Connection string в виде url. Например postgresql+psycopg2://postgres:[email protected]:5432/realization. @param sql: Текст SQL запроса. @return: Функция возвращает словарь - ТАБЛИЦА ЗАПРОСА ПРЕДСТАВЛЯЕТСЯ В ВИДЕ СЛОВАРЯ {'__fields__':имена полей таблицы,'__data__':данные таблицы} """ result = None # Инициализация db_connection = None try: if not db_url: data_source = report['data_source'] if not data_source: # Учет случая когда источник данных не определен log.warning(u'Не определен источник данных в отчете') return {'__fields__': list(), '__data__': list()} signature = data_source[:4].upper() if signature != DB_URL_SIGNATURE: log.warning('Not support DB type <%s>' % signature) return result # БД задается с помощью стандартного DB URL db_url = data_source[4:].lower().strip() log.info(u'DB connection <%s>' % db_url) # Установить связь с БД db_connection = sqlalchemy.create_engine(db_url) # Освободить БД # db_connection.dispose() log.info(u'DB SQL <%s>' % textfunc.toUnicode(sql, 'utf-8')) sql_result = db_connection.execute(sql) rows = sql_result.fetchall() cols = rows[0].keys() if rows else [] # Закрыть связь db_connection.dispose() db_connection = None # ТАБЛИЦА ЗАПРОСА ПРЕДСТАВЛЯЕТСЯ В ВИДЕ СЛОВАРЯ # {'__fields__':имена полей таблицы,'__data__':данные таблицы} !!! result = {'__fields__': cols, '__data__': list(rows)} return result except: if db_connection: # Закрыть связь db_connection.dispose() # Вывести сообщение об ошибке в лог log.fatal(u'Ошибка определения таблицы SQL запроса <%s>.' % sql) log.error(u'''ВНИМАНИЕ! Если возникает ошибка в модуле: ---------------------------------------------------------------------------------------------- File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/default.py", line 324, in do_execute cursor.execute(statement, parameters) TypeError: 'dict' object does not support indexing ---------------------------------------------------------------------------------------------- Это означает что SQLAlchemy не может распарсить SQL выражение. Необходимо вместо <%> использовать <%%> в SQL выражении. ''') return None