Skip to content

Commit

Permalink
Переработал обработку ошибок в "блоке" проверок формата. Прочие небол…
Browse files Browse the repository at this point in the history
…ьшие правки
  • Loading branch information
WoolenSweater committed Feb 12, 2021
1 parent dea95ef commit 02e56dc
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 67 deletions.
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# CHANGELOG

### [0.7.1] - 2021-02-12

- Теперь на вход передаются строго ElementTree, Element, bytes, file name/path, или file-like объекты.
- Добавил сортировку по возрастанию при итерациям по строкам секции отчёта. Ранее порядок не гарантировался, из-за чего при определенном сочетаний ошибок, результат проверки мог отличаться от запуска к запуску.
- Переработал обработку ошибок в "блоке" проверок формата.
- Все ошибки перенесены в отдельный модуль и выполнены в виде Exception'ов.
- Некоторые ошбики общего вида, разделились на несколько самостоятельных. Следовательно, появились новые коды ошибок, а у некоторых старых они изменились.
- При возбуждении какой либо ошибки, проверка прекращается. Другими словами, проверки стали "ленивыми".
- Добавил исключения для ситуаций, когда для проверяемой секции или значения не указаны правила в шаблоне.
- Выписал все возможные на данный момент коды ошибок в docs.md
- Исправил ошибку в сообщениях ошибок проверки специфик.


### [0.7.0] - 2020-11-06

- Изменил формат ответа.
Expand Down Expand Up @@ -30,7 +43,7 @@
### [0.6.1] - 2020-09-17

- Реализовал следующие проверки:
- Наличия в отчёте разделов в соответствии со схемой.
- Наличия в отчёте секций в соответствии со схемой.
- Формат ОКПО указанного в отчете в блоке title.
- Формат года указанного в отчете.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pip install rosstat-flc
```python
from rosstat.flc import parse_schema, parse_report

# На вход передаются ElementTree, Element, bytes, file name/path, или file-like объекты

schema = parse_schema('xml_schema.xml')
report = parse_report('xml_report.xml', skip_warns=True)

Expand Down
54 changes: 39 additions & 15 deletions docs/docs.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
# Reference [WIP]
# Коды ошибок [WIP]

_class_ ElemLogic()
Класс проверки выполнения логического условия между левым и правым элементами.
## 0. Непредвиденная ошибка

**check**(report, params, ctx_elem=None)
* 0.0 Не удалось выполнить проверку

* report(Report) - Подготовленный отчёт.
* params(ControlParams) - Параметры проверки.
* ctx_elem(ElemLogic, ElemSelector, ElemList, Elem) - Контекстный элемент. В логическом элементе не испольузется, так как предполагается, что он же содержит в себе левый и правый элементы логического условия, которые приходятся друг другу контекстом. Необходим только для поддержания единого набора всех параметров метода для их взаимного вызова в любом месте проверки.
## 1. Проверка аттрибутов

**Return type:** List[Elem]
* 1.1 Указан недопустимый год
* 1.2 Тип периодичности отчёта не соответствует типу периодичности шаблона
* 1.3 Неверное значение периода отчёта

---
## 2. Проверка полей заголовка

_class_ ControlParams(dimension, precision, fault, is_rule=False)
Вспомогательный класс с параметрами проверки.
* 2.1 Лишнее поле - `<имя поля>`
* 2.2 Повтор поля - `<имя поля>`
* 2.3 Отсутствует значение в поле - `<имя поля>`
* 2.4 Код ОКПО должен быть 8-и или 14-и значным числом
* 2.5 Отсутствует ключевое поле - `<имя поля>`
* 2.6 Отсутствует поле - `<имя поля>`

* dimension(Dict[str, list]) - словарь вида {'1': ['3', '4'], ..., '4': ['1', '2']} Ключ - код секции, значене все возможные коды графов, прочитанные из схемы.
* fault(float) - погрешность. Если логическое условие не выполнено, дополнительно проверяется погрешность. Отрицательное значение для проверки "condition" и если явно не передано в правиле "rule", так как там отклонение не допустимо.
* precision(int) - округление. Используется логическим элементом для округления перед проверкой условия.
* is_rule(bool) - флаг определяющий, проверяется "condition" или "rule".
## 3. Проверка формата

* 3.1 Раздел `<код секции>` отсутствует в отчёте
* 3.2 Строка `<код строки>` повторяется `<кол-во>` раз(а)
* 3.3 Раздел `<код секции>`, строка `<код строки>` не может быть пустой
* 3.4 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>` не может быть пустой
* 3.5 Раздел `<код секции>` не описан в шаблоне
* 3.6 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. В шаблоне отсутствует правило для проверки этого поля
* 3.7 Раздел `<код секции>`, строка `<код строки>`, специфика `<код специфики>`. Специфика отсутствует в справочнике
* 3.8 Раздел `<код секции>`, строка `<код строки>`, специфика `<код специфики>`. Недопустмое значение
* 3.9 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Значение не является числом
* 3.10 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Число не соответствует формату
* 3.11 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Длина строки больше допустимого
* 3.12 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Значение отсутствует в справочнике
* 3.13 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Значение не входит в диапазон допустимых
* 3.14 Раздел `<код секции>`, строка `<код строки>`, графа `<код графы>`. Значение не входит в список допустимых


## 4. Проверка формата

* 4.`<номер контроля>` Ошибка разбора формулы проверки периодичности
* 4.`<номер контроля>` Ошибка разбора условия контроля
* 4.`<номер контроля>` Ошибка разбора правила контроля
* 4.`<номер контроля>` Проверка со значениями из прошлого периода не реализована
* 4.`<номер контроля>` `<описание формулы контроля>`; слева `<значение слева>` `<оператор сравнения>` справа `<значение справа>` разница `<разница>`; обязательность `<признак обязательности>`
14 changes: 12 additions & 2 deletions rosstat/flc.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
from os.path import isfile
from io import BytesIO, BufferedIOBase
from lxml import etree
from .report import Report
from .schema import Schema


def _get_xml_etree(source):
if isinstance(source, str):
if isinstance(source, (etree._ElementTree, etree._Element)):
return source
elif isinstance(source, str) and isfile(source):
return etree.parse(source)
return source
elif isinstance(source, bytes):
return etree.parse(BytesIO(source))
elif isinstance(source, BufferedIOBase):
return etree.parse(source)

raise TypeError(f'Expected ElementTree, Element, bytes, file name/path, '
f'or file-like object, got {source!r}')


def parse_report(source):
Expand Down
4 changes: 2 additions & 2 deletions rosstat/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Section:
_ignore_report_specs: Tuple[str] = f(default=('XX',), repr=False)

def items(self, codes=None, specs=None):
codes = codes or set(self.row_codes)
codes = codes or sorted(set(self.row_codes), key=int)
for row_code in codes:
yield row_code, list(self.get_rows(row_code, specs=specs))

Expand Down Expand Up @@ -193,7 +193,7 @@ def set_periods(self, dics, idp):
self._period_code = str(int(int(self._period_raw) / max_div))
return True
return False
except Exception as ex:
except Exception:
return False

def _get_periods_id(self, dics):
Expand Down
2 changes: 1 addition & 1 deletion rosstat/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def validate(self, report):
self._errors_handle(validator)
break
except Exception:
self.errors.append({'code': '0.99',
self.errors.append({'code': '0.0',
'name': 'Непредвиденная ошибка',
'message': 'Не удалось выполнить проверку'})
print('Unexpected Error', traceback.format_exc())
Expand Down
105 changes: 105 additions & 0 deletions rosstat/validators/format/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
class FormatError(Exception):
pass


# ---


class NoSectionReportError(FormatError):
def __init__(self, sec_code):
self.code = '1'
self.msg = 'Раздел {} отсутствует в отчёте'.format(sec_code)


class DuplicateError(FormatError):
def __init__(self, row_code, counter):
self.code = '2'
self.msg = 'Строка {} повторяется {} раз(а)'.format(row_code, counter)


class EmptyRowError(FormatError):
def __init__(self, sec_code, row_code):
self.code = '3'
self.msg = 'Раздел {}, строка {} не может быть пустой'.format(sec_code,
row_code)


class EmptyColumnError(FormatError):
def __init__(self, sec_code, row_code, col_code):
self.code = '4'
self.msg = ('Раздел {}, строка {}, графа {} не может быть пустой'
.format(sec_code, row_code, col_code))


class NoSectionTemplateError(FormatError):
def __init__(self, sec_code):
self.code = '5'
self.msg = 'Раздел {} не описан в шаблоне'.format(sec_code)


class NoRuleError(FormatError):
def __init__(self, sec_code, row_code, col_code):
self.code = '6'
self.msg = ('Раздел {}, строка {}, графа {}. '
'В шаблоне отсутствует правило для проверки этого поля'
.format(sec_code, row_code, col_code))


# ---


class SpecBaseError(FormatError):
def update(self, coords, spec):
self.msg = 'Раздел {}, строка {}, специфика {}. {}'.format(coords[0],
coords[1],
spec,
self.msg)


class SpecNotInDictError(SpecBaseError):
msg = 'Специфика отсутствует в справочнике'
code = '7'


class SpecValueError(SpecBaseError):
msg = 'Недопустмое значение'
code = '8'


# ---


class ValueBaseError(FormatError):
def update(self, coords):
self.msg = 'Раздел {}, строка {}, графа {}. {}'.format(*coords,
self.msg)


class ValueNotNumberError(ValueBaseError):
msg = 'Значение не является числом'
code = '9'


class ValueBadFormat(ValueBaseError):
msg = 'Число не соответствует формату'
code = '10'


class ValueLengthError(ValueBaseError):
msg = 'Длина строки больше допустимого'
code = '11'


class ValueNotInDictError(ValueBaseError):
msg = 'Значение отсутствует в справочнике'
code = '12'


class ValueNotInRangeError(ValueBaseError):
msg = 'Значение не входит в диапазон допустимых'
code = '13'


class ValueNotInListError(ValueBaseError):
msg = 'Значение не входит в список допустимых'
code = '14'
54 changes: 29 additions & 25 deletions rosstat/validators/format/format.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from ..base import AbstractValidator
from .inspectors import ValueInspector, SpecInspector
from .exceptions import (FormatError, DuplicateError, EmptyRowError,
EmptyColumnError, NoSectionReportError,
NoSectionTemplateError, NoRuleError)


class FormatValidator(AbstractValidator):
Expand All @@ -14,10 +17,13 @@ def __repr__(self):
return '<FormatValidator errors={errors}>'.format(**self.__dict__)

def validate(self, report):
self._check_sections(report)
self._check_duplicates(report)
self._check_required(report)
self._check_format(report)
try:
self._check_sections(report)
self._check_duplicates(report)
self._check_required(report)
self._check_format(report)
except FormatError as ex:
self.error(ex.msg, ex.code)

return not bool(self.errors)

Expand All @@ -27,7 +33,7 @@ def _check_sections(self, report):
schema_sections = set(self._schema.dimension.keys())

for section in schema_sections - report_sections:
self.error(f'Отсутствует раздел - {section}', '1')
raise NoSectionReportError(section)

def _check_duplicates(self, report):
'''Проверка дубликатов строк'''
Expand All @@ -38,20 +44,18 @@ def __fmt_specs(specs):
if counter > 1:
row_code, *specs = row
row = f'{row_code} {__fmt_specs(specs)}' if specs else row_code
self.error(f'Строка {row} повторяется {counter} раз(а)', '2')
raise DuplicateError(row, counter)

def _check_required(self, report):
'''Проверка наличия обязательных к заполнению строк и значений'''
for sec_code, row_code, col_code in self._schema.required:
rows = list(report.get_section(sec_code).get_rows(row_code))
if not rows:
self.error(f'Раздел {sec_code}, строка {row_code} '
f'не может быть пустой', '3')
raise EmptyRowError(sec_code, row_code)

for row in rows:
if not row.get_col(col_code):
self.error(f'Раздел {sec_code}, строка {row_code}, графа '
f'{col_code} не может быть пустой', '4')
raise EmptyColumnError(sec_code, row_code, col_code)

def _check_format(self, report):
'''Проверка формата строк и значений в них'''
Expand All @@ -65,31 +69,31 @@ def __check_row(self, sec_code, row_code, row):
'''Итерация по ожидаемым спецификам с их последующей проверкой'''
specs_map = self.__get_specs(sec_code)
for col_code, spec in specs_map.items():
coords = (sec_code, row_code, col_code)
self.__check_fmt(
coords, SpecInspector, row, spec, specs_map,
err_msg='Раздел {}, строка {}, специфика {}. {}', code='5')
self.__check_fmt((sec_code, row_code, col_code),
SpecInspector, row, spec, specs_map)

def __check_cells(self, sec_code, row_code, row):
'''Итерация по значениям строки с их последующей проверкой'''
for col_code, value in row.items():
coords = (sec_code, row_code, col_code)
self.__check_fmt(
coords, ValueInspector, value,
err_msg='Раздел {}, строка {}, графа {}. {}', code='6')
self.__check_fmt((sec_code, row_code, col_code),
ValueInspector, value)

def __check_fmt(self, coords, inspector_class, *args, err_msg, code):
def __check_fmt(self, coords, inspector_class, *args):
'''Инициализация инспектора, проверка'''
fmt_node = self.__get_format_node(*coords)
inspector = inspector_class(fmt_node, self._schema.dics)
error = inspector.check(*args)
if error:
self.error(err_msg.format(*coords, error), code)
inspector.check(coords, *args)

def __get_format_node(self, sec_code, row_code, col_code):
'''Возвращает ноду с услвоиями проверки'''
return self._schema.format[sec_code][row_code][col_code]
'''Возвращает ноду с условиями проверки'''
try:
return self._schema.format[sec_code][row_code][col_code]
except KeyError:
raise NoRuleError(sec_code, row_code, col_code)

def __get_specs(self, sec_code):
'''Возвращает словарь со спецификами'''
return self._schema.format[sec_code]['specs']
try:
return self._schema.format[sec_code]['specs']
except KeyError:
raise NoSectionTemplateError(sec_code)

0 comments on commit 02e56dc

Please sign in to comment.