error_messages.py 7.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#!/usr/bin/env python3
import re
from datetime import datetime

import ujson as json

FRENCH_DATE_RE = re.compile(r'^[0-3]\d/[0-1]\d/[12]\d{3}$')
DATETIME_RE = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$')


def update_msg(err, msg):
    """ Handy method to update err message and return err """
    err['message'] = msg
    return err

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

def et_join(values):
    """french enum
    >>> et_join([])
    ''
    >>> et_join(['a'])
    'a'
    >>> et_join(['a','b'])
    'a et b'
    >>> et_join(['a','b','c'])
    'a, b et c'
    >>> et_join(['a','b','c','d','e'])
    'a, b, c, d et e'
    """
    if values is None or len(values) == 0:
        return ''
    if len(values) == 1:
        return values[0]
    return ' et '.join([', '.join(values[:-1]), values[-1]])

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
# Core goodtables checks


def blank_row(err, headers, schema):
    """ blank-row error """
    return update_msg(err, 'La ligne est vide')


def duplicate_row(err, headers, schema):
    """ duplicate-row error """
    msg_prefix = 'La ligne est identique '
    row_numbers = err['message-data']['row_numbers']
    if not ',' in row_numbers:
        msg = msg_prefix + "à la ligne {}".format(row_numbers)
    else:
51
        msg = msg_prefix + "aux lignes {}".format(et_join(row_numbers))
52 53 54 55 56 57 58 59 60 61
    return update_msg(err, msg)


def enumerable_constraint(err, headers, schema):
    """ enumerable-constraint """
    constraint_values = json.loads(err['message-data']['constraint'].replace("'", '"'))
    ok_values = ['"{}"'.format(val) for val in constraint_values]
    if len(ok_values) == 1:
        return update_msg(err, 'Valeur incorrecte. La valeur autorisée est : {}'.format(ok_values[0]))
    else:
62
        ok_values_str = et_join(ok_values)
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
        return update_msg(err, 'Valeur incorrecte. Les valeurs autorisées sont : {}'.format(ok_values_str))


def maximum_constraint(err, headers, schema):
    """ maximum-constraint """
    max_value = err['message-data']['constraint']
    return update_msg(err, 'Valeur incorrecte. La valeur ne doit pas dépasser {}'.format(max_value))


def pattern_constraint(err, headers, schema):
    """ pattern-constraint """
    column_number = err['column-number']
    field = schema['fields'][column_number]
    col_name = field['name']
    addon_info_list = []
    if 'description' in field:
        addon_info_list.append('Pour rappel, la description de cette colonne est « {} »'.format(field['description']))
    if 'example' in field:
        addon_info_list.append('Exemple(s) de valeur correcte : {}'.format(field['example']))
    addon_info = '<br/>' + '<br/>'.join(addon_info_list) if addon_info_list else ''
    return update_msg(err, 'Valeur incorrecte. La valeur ne respecte pas le format attendu pour la colonne « {} ».{}'.format(col_name, addon_info))


def required_constraint(err, headers, schema):
    """ required-constraint error """
    col_name = ''
    col_nb = err['column-number']
    if col_nb <= len(headers):
        col_name = ' "{}"'.format(headers[col_nb - 1])
    return update_msg(err, 'Le contenu de la colonne{} est obligatoire\n'.format(col_name)
                      + 'Merci d\'indiquer une valeur.')


def type_or_format_error(err, headers, schema):
    """ type-or-format-value """
    err_type = err['message-data']['field_type']
    err_value = err['message-data']['value']

    # Date
    if err_type == 'date':
        # Checks if date is dd/mm/yyyy
        dm = FRENCH_DATE_RE.match(err_value)
        if dm:
            iso_date = datetime.strptime(err_value, '%d/%m/%Y').strftime('%Y-%m-%d')
            return update_msg(err, "Le format de la date n'est correct.\nLa forme attendue est \"{}\"".format(iso_date))

        # Checks if date is yyyy-mm-ddThh:MM:ss
        # print('DATE TIME ? [{}]'.format(err_value))
        dm = DATETIME_RE.match(err_value)
        if dm:
            iso_date = err_value[:err_value.find('T')]
            return update_msg(err, "Le format de la date n'est correct.\nLa forme attendue est \"{}\"".format(iso_date))

        # Default date err msg
        return update_msg(err, 'Le format de la date est incorrect')

    # Number
    elif err_type == 'number':
        if ',' in err_value:
            en_number = err_value.replace(',', '.')
            return update_msg(err, "Le format du nombre est incorrect.\nLa forme attendue est \"{}\"".format(en_number))

    # Boolean
126 127 128 129 130 131
    elif err_type == 'boolean':
        column_number = err['column-number']
        field = schema['fields'][column_number]
        true_values = field.get('trueValues', ['true'])
        false_values = field.get('falseValues', ['false'])
        return update_msg(err, "Valeur incorrecte. Les valeurs acceptées sont {} pour 'vrai' et {} pour 'faux'".format(et_join(false_values), et_join(true_values)))
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

    # Default msg
    return update_msg(err, 'XXXX {} XXXX'.format(err_type))

# Validata custom checks


def french_siret_value(err, headers, schema):
    """ french-siret-value error """
    return update_msg(err, 'Le numéro SIRET est non valide')

# Validata pre-checks


def invalid_column_delimiter(err, headers, schema):
    """ invalid-column-delimiter """
    md = err['message-data']
    return update_msg(err, 'Le fichier CSV utilise le délimiteur de colonne « {} » au lieu du délimiteur attendu  « {} ».'.format(md['detected'], md['expected'])
                      + "<br/>Pour vous permettre de continuer la validation, un remplacement automatique a été réalisé.")


def missing_headers(err, headers, schema):
    """ missing-headers """
    cols = err['message-data']['headers']
    if len(cols) == 1:
        return update_msg(err, "La colonne \"{}\" n'a pas été trouvée dans le fichier".format(cols[0]))
    else:
        cols_ul = '<ul>' + '\n'.join(['<li>{}</li>'.format(col) for col in cols]) + '</ul>'
        fields_nb = len(schema.get('fields', []))
        addon_info = 'Utilisez-vous le bon schéma ?' if len(cols) == fields_nb else ''
        return update_msg(err, "Les colonnes suivantes n'ont pas été trouvées dans le fichier : {}{}".format(cols_ul, addon_info))


def extra_headers(err, headers, schema):
    """ extra-headers """
    cols = err['message-data']['headers']
    if len(cols) == 1:
        return update_msg(err, "La colonne \"{}\" est inconnue dans le schéma".format(cols[0]))
    else:
        cols_ul = '<ul>' + '\n'.join(['<li>{}</li>'.format(col) for col in cols]) + '</ul>'
        addon_info = 'Utilisez-vous le bon schéma ?' if len(cols) == len(headers) else ''
        return update_msg(err, "Les colonnes suivantes sont inconnues dans le schéma : {}{}".format(cols_ul, addon_info))


def wrong_headers_order(err, headers, schema):
    """ wrong-headers-order """
    fields = [f['name'] for f in schema.get('fields', [])]
    assert len(headers) == len(fields), 'Wrong column order between two lists of different lengths'
    msgs = []
    for i, (header, field) in enumerate(zip(headers, fields)):
        if header == field:
            continue
        msgs.append('la colonne {} devrait être « {} » (au lieu de « {} »)'.format(i+1, field, header))
    errors_str = '<ul>\n' + '\n'.join(['<li>{}</li>\n'.format(msg) for msg in msgs]) + '\n</ul>'
    return update_msg(err, "Les colonnes du tableau ne sont pas dans l'ordre attendu :\n{}".format(errors_str))