Skip to content

Commit

Permalink
Доработка проверок формата строк и значений в графах
Browse files Browse the repository at this point in the history
  • Loading branch information
WoolenSweater committed Sep 21, 2020
1 parent 2ce64cb commit 3a680d6
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 76 deletions.
14 changes: 8 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

### [0.6.2] - 2020-09-21

- Доработка проверок формата строк и значений в графах:
- Реализовал пропущенную проверку строки с типом 5. Выяснил, что проверки с типом 4 и 5 выполняются над спецификами строк, а не над значениями в графах.
- В классе `FormatChecker` выделил обработку строки и значения в графе с выбором метода проверки в отдельные методы. Выполнил небольшой рефакторинг.
- В классе `Schema` добавил парсинг специфик в отдельный словарь и переработал парсинг справочников, так как нужно учитывать все атрибуты в нодах term.
- Добавил лицензию.

### [0.6.1] - 2020-09-17

- Реализовал следующие проверки:
Expand All @@ -18,15 +26,13 @@
- Метод проверки возможности проведения логического контроля теперь возвращает отрицательный результат только в одном случае. Если проверяемая формула является условием (condition), оператор проверки не относиться к логическим (or, and) и оба элемента являются "заглушками" из пустой строки. Интерпретируется это как ошибка проведения логического контроля.
- Исправил неправильный результат проверок если в формуле есть логический оператор "or".


### [0.5.1] - 2020-09-15

- Исправил суммирование элементов.
- Коды секций/строк/графов схемы теперь тоже проходят трансформацию `строка -> число -> строка`, так как и в схемах замечены коды вида "01".
- Исправил добавление в список ошибок сообщения о непредвиденной ошибке проверки формата ячейки.
- Исправил установку погрешности для условий (condition).


### [0.5.0] - 2020-09-11

- Вновь переработал инициализацию элементов из за проблем возникающих в редких случаях когда в формуле есть проверки между элементами, значения осей которых равно "*".
Expand All @@ -40,31 +46,27 @@
- Добавил параметр для пропуска предупреждении о нереализованной проверке контролей со значениями за прошлый период.
- Исправил ошибку при которой могло происходить деление на None.


### [0.4.5] - 2020-09-01

- Поправил ошибку при которой контроль, который не должен был пройти, проходил из-за положительной проверки погрешности. Теперь погрешность по умолчанию равна -1.
- Исправил получение idp из корня шаблона. По аналогии с ошибкой #2.
- Переделал проверку полей блока title и добавил проверку значений и наличия обязательного поля.
- Сделал проверку дубликатов строк.


### [0.4.4] - 2020-08-31

- Исправил получение периода из отчета если передан отчёт типа `lxml.etree._Element` ([Issue #2](https://github.com/WoolenSweater/rosstat-flc/issues/2)).
- Исправил проверку периода контроля для формулы вида `(&NP in(6))` с 1 элементом в списке ([Issue #3](https://github.com/WoolenSweater/rosstat-flc/issues/3)).
- Исправил вызов метода `_apply_funcs` у элемента `ElemSelector`. Забыл передать параметры fault и precision ([Issue #4](https://github.com/WoolenSweater/rosstat-flc/issues/4)).
- Добавил проверку использования в формуле контролей элементов со значениями за прошлый период. Возвращаю сообщение об ошибке, так как такой функционал не реализован и неизвестно когда это удастся сделать.


### [0.4.3] - 2020-08-31

- Исправил парсинг отчета в тайтле которого есть ноды без атрибута value ([Issue #1](https://github.com/WoolenSweater/rosstat-flc/issues/1)).
- Исключил парсинг "пустых" контролей, выполняя strip у формулы перед проверкой ([Issue #5](https://github.com/WoolenSweater/rosstat-flc/issues/5)).
- Исправил выполнение контролей с элементами со спецификой равной "0", добавив её к игнорируемым.
- Исправил выполнение контролей с функцией ABS, повысив её приоритет над математическими операциями.


### [0.4.2] - 2020-08-28

- Добавил указание обязательности в сообщение о непройденном контроле.
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Nikita Ryabinin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
81 changes: 44 additions & 37 deletions rosstat/validator/checkers/format.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from ..exceptions import (OutOfAdditionDict, OutOfList, OutOfRange, OutOfDict,
InvalidStrLength, InvalidNumFormat, NotNumericValue)
InvalidStrLength, InvalidNumFormat, NotNumericValue,
OutOfAdditionDictCoord)


class FormatChecker:
def __init__(self, cell, dics, input_type, row_type=None):
self._cell = cell
def __init__(self, cell, dics):
self._dics = dics

self.row_type = row_type
self.input_type = input_type

self.dic = cell.attrib.get('dic')
self.format = cell.attrib.get('format')
self.vld_type = cell.attrib.get('vldType')
Expand All @@ -18,8 +15,7 @@ def __init__(self, cell, dics, input_type, row_type=None):
self.format_funcs_map = {'N': self._is_num, 'C': self._is_chars}

def __repr__(self):
return ('<FormatChecker row={row_type} input={input_type} '
'format={format} vld_type={vld_type} '
return ('<FormatChecker format={format} vld_type={vld_type} '
'vld_param={vld_param}>').format(**self.__dict__)

@classmethod
Expand All @@ -43,56 +39,67 @@ def _is_num(self, value, limits):
@classmethod
def _is_chars(self, value, limit):
'''Проверка длины символьного значения поля'''
if not len(value) <= limit:
if not len(value) <= int(limit):
raise InvalidStrLength()

def check(self, cell, errors_list):
'''Метод вызова проверки формата значения'''
self._check_format(cell)
self._check_value(cell)

def _check_format(self, cell):
'''Разбор "формулы" проверки формата. Вызов метода проверки'''
alias, args = self.format.strip(' )').split('(')
format_check_func = self.format_funcs_map[alias]
format_check_func(cell, args)
def check(self, obj, spec=None, specs_map=None):
if isinstance(obj, str):
self._check_cell(obj)
else:
self._check_row(obj, spec, specs_map)

def _check_value(self, cell):
'''Определение и вызов метода проверки по типу'''
def _check_cell(self, cell):
'''Метод вызова проверки формата значения'''
self.__check_format(cell)
if self.vld_type == '1':
self.__check_value_dic(cell)
elif self.vld_type == '2':
self.__check_value_range(cell)
elif self.vld_type == '3':
self.__check_value_list(cell)
elif self.vld_type == '4':
self.__check_value_dic_add(cell)

def _check_row(self, row, spec, specs_map):
if self.vld_type == '4':
self.__check_value_dic_add(row, spec)
elif self.vld_type == '5':
self.__check_value_dic_coord(cell)
self.__check_value_dic_coord(row, spec, specs_map)

def __check_value_dic(self, cell):
def __check_format(self, value):
'''Разбор "формулы" проверки формата. Вызов метода проверки'''
alias, args = self.format.strip(' )').split('(')
format_check_func = self.format_funcs_map[alias]
format_check_func(value, args)

def __check_value_dic(self, value):
'''Проверка на вхождение в справочник'''
if cell not in self._dics[self.dic]:
if value not in self._dics[self.dic]:
raise OutOfDict()

def __check_value_range(self, cell):
def __check_value_range(self, value):
'''Проверка на вхождение в диапазон'''
cell = float(cell)
value = float(value)
start, end = (int(n) for n in self.vld_param.split('-'))
if not (cell >= start and cell <= end):
if not (value >= start and value <= end):
raise OutOfRange()

def __check_value_list(self, cell):
def __check_value_list(self, value):
'''Проверка на вхождение в список'''
if cell not in self.vld_param.split(','):
if value not in self.vld_param.split(','):
raise OutOfList()

def __check_value_dic_add(self, cell):
def __check_value_dic_add(self, row, spec):
'''Проверка на вхождение в справочник приложение'''
if cell not in self._dics[self.vld_param]:
if getattr(row, spec) not in self._dics[self.vld_param]:
raise OutOfAdditionDict()

def __check_value_coord(self, cell):
return # пока не ясно как это првоеряется
attr, coords = self.vld_param.split('=#')
s_idx, r_idx, c_idx = coords.split(',')
def __check_value_dic_coord(self, row, spec, specs_map):
dic, coords = self.vld_param.split('=#')
*_, c_idx = coords.split(',')
spec_value = getattr(row, spec)
ctx_spec_value = getattr(row, specs_map[c_idx])

try:
if ctx_spec_value not in self._dics[self.dic][spec_value][dic]:
raise OutOfAdditionDictCoord()
except KeyError:
raise OutOfAdditionDictCoord()
4 changes: 4 additions & 0 deletions rosstat/validator/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class OutOfAdditionDict(FormatError):
msg = 'Значение не существует в справочнике приложении'


class OutOfAdditionDictCoord(FormatError):
msg = 'Недопустимое значение'


class ControlError(ValidationError):
pass

Expand Down
83 changes: 51 additions & 32 deletions rosstat/validator/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def __init__(self, xml_tree, *, skip_warns):
'duplicates', 'format', 'controls')

def __repr__(self):
return ('<Schema title={title}\nform={form}\ncontrols={controls}\n'
'dics={dics}>').format(**self.__dict__)
return '<Schema idp={idp} obj={obj} title={title}'.format(
**self.__dict__)

def _add_error(self, error):
'''Добавление ошибки в список'''
Expand Down Expand Up @@ -66,27 +66,31 @@ def _prepare_format(self):
for cell in row.xpath('./cell'):
cell_code = str_int(cell.attrib['column'])
input_type = cell.attrib['inputType']
format_checker = FormatChecker(
cell, self.dics, input_type, row_type=row_type)
format_checker = FormatChecker(cell, self.dics)
form[section_code][row_code][cell_code] = format_checker

if input_type == '1' and row_type != 'M':
coords = (section_code, row_code, cell_code)
self._required.append(coords)

form[section_code]['default'] = self._get_defaults(section,
section_code)
form[section_code].update(self._get_adds(section, section_code))
return form

def _get_defaults(self, section, section_code):
'''Создание словаря с чекерами "по умолчанию"'''
defaults = {}
for cell in section.xpath('./columns/column[@type!="B"]/default-cell'):
cell_code = str_int(cell.attrib['column'])
input_type = cell.attrib['inputType']
defaults[cell_code] = FormatChecker(cell, self.dics, input_type)
self._dimension[section_code].append(cell_code)
return defaults
def _get_adds(self, section, section_code):
'''Получение дополнительных данных для проверки: черекеры по умолчанию,
заполнение словаря размерности секций отчёта, определение имен
колонок специфик
'''
adds = {'default': {}, 'specs': {}}
for col in section.xpath('./columns/column[@type!="B"]'):
if col.attrib['type'] == 'S':
adds['specs'][col.attrib['code']] = col.attrib['fld']
continue
for cell in col:
cell_code = str_int(cell.attrib['column'])
adds['default'][cell_code] = FormatChecker(cell, self.dics)
self._dimension[section_code].append(cell_code)
return adds

def _prepare_controls(self):
'''Создание списка с контролями'''
Expand All @@ -104,9 +108,12 @@ def _prepare_dics(self):
dict_id = dic.attrib['id']
dics[dict_id] = {}

for term in dic.xpath('./term'):
term_id = term.attrib['id']
dics[dict_id][term_id] = term.text
for term_node in dic.xpath('./term'):
term_id = term_node.attrib.pop('id')
if term_id not in dics[dict_id]:
dics[dict_id][term_id] = defaultdict(set)
for k, v in term_node.attrib.items():
dics[dict_id][term_id][k].add(v)
return dics

def validate(self, report):
Expand Down Expand Up @@ -191,27 +198,39 @@ def _check_duplicates(self, report):
f'{counter} раз(а)')

def _check_format(self, report):
'''Проверка формата заполненых полей. Итерация по секциям и строкам'''
'''Итерация по секциям и строкам с передачей их в собственные методы
проверки формата. Для строки это проверка специфик, для полей -
проверка введеного значения.
'''
for s_idx, section in report.items():
for r_idx, rows in section.items():
for row in rows:
self.__check_cells(row, s_idx, r_idx)
self.__check_row(row, s_idx, r_idx)

def __check_row(self, row, s_idx, r_idx):
'''Итерация по ожидаемым спецификам с их последующей проверкой'''
specs_map = self.format[s_idx]['specs']
for c_idx, spec in specs_map.items():
self.__check_fmt(s_idx, r_idx, c_idx, row, spec, specs_map,
err_msg='Раздел {}, строка {}, специфика {}. {}')

def __check_cells(self, row, s_idx, r_idx):
'''Итерация по полям строки с их последующей проверкой'''
template = 'Раздел {}, строка {}, графа {}. {}'
for c_idx, cell in row.items():
if not cell:
continue
try:
format_checker = self.__get_format_checker(s_idx, r_idx, c_idx)
format_checker.check(cell, self._errors)
except FormatError as ex:
self._add_error(template.format(s_idx, r_idx, c_idx, ex.msg))
except Exception:
ex_msg = 'Непредвиденная ошибка проверки формата ячейки'
self._add_error(template.format(s_idx, r_idx, c_idx, ex_msg))
print('Unexpected Error', traceback.format_exc())
self.__check_fmt(s_idx, r_idx, c_idx, cell,
err_msg='Раздел {}, строка {}, графа {}. {}')

def __check_fmt(self, s_idx, r_idx, c_idx, *args, err_msg=None):
'''Непосредственная проверка значения с отловом и обработкой ошибки'''
try:
checker = self.__get_format_checker(s_idx, r_idx, c_idx)
checker.check(*args)
except FormatError as ex:
self._add_error(err_msg.format(s_idx, r_idx, c_idx, ex.msg))
except Exception:
ex_msg = 'Непредвиденная ошибка проверки формата'
self._add_error(err_msg.format(s_idx, r_idx, c_idx, ex_msg))
print('Unexpected Error', traceback.format_exc())

def __get_format_checker(self, s_idx, r_idx, c_idx):
'''Получение чекера для поля'''
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='rosstat-flc',
version='0.6.1',
version='0.6.2',
description='Tool for format-logistic control of reports sent to RosStat',
long_description=open('README.md', 'r').read(),
long_description_content_type="text/markdown",
Expand Down

0 comments on commit 3a680d6

Please sign in to comment.