Exemple #1
0
    def validated_value(self, value):
        assert value

        translated_value = ""
        found_decimal_separator = False
        for character_to_process in value:
            if character_to_process == self.decimal_separator:
                if found_decimal_separator:
                    raise errors.FieldValueError(
                        _("decimal field must contain only one decimal separator (%s): %s")
                        % (_compat.text_repr(self.decimal_separator), _compat.text_repr(value)))
                translated_value += "."
                found_decimal_separator = True
            elif self.thousands_separator and (character_to_process == self.thousands_separator):
                if found_decimal_separator:
                    raise errors.FieldValueError(_(
                        "decimal field must contain thousands separator (%r) only before "
                        "decimal separator (%r): %r "
                        ) % (self.thousands_separator, self.decimal_separator, value))
            else:
                translated_value += character_to_process

        try:
            result = decimal.Decimal(translated_value)
        except Exception as error:
            # TODO: limit exception handler to decimal exception or whatever decimal.Decimal raises.
            message = "value is %r but must be a decimal number: %s" % (value, error)
            raise errors.FieldValueError(message)

        try:
            self.valid_range.validate(self._field_name, result)
        except errors.RangeValueError as error:
            raise errors.FieldValueError(str(error))

        return result
Exemple #2
0
    def add_data_format_row(self, row_data):
        """
        Extract name and value from ``row_data`` and apply it to
        :py:attr:`~cutplace.interface.Cid.data_format` by calling
        :py:meth:`~cutplace.data.DataFormat.set_property`.

        :param list row_data: a list with at least 2 items for name and value \
            that can be passed to \
            :py:meth:`cutplace.data.DataFormat.set_property()`.
        """
        assert row_data is not None
        assert len(row_data) >= 2

        name, value = row_data[:2]
        lower_name = name.lower()
        self._location.advance_cell()
        if name == '':
            raise errors.InterfaceError(
                _('name of data format property must be specified'),
                self._location)
        self._location.advance_cell()
        if (self._data_format is None) and (lower_name != data.KEY_FORMAT):
            raise errors.InterfaceError(
                ('first data format row must set property %s instead of %s') %
                (_compat.text_repr(data.KEY_FORMAT), _compat.text_repr(name)),
                self._location)
        if (self._data_format is not None) and (lower_name == data.KEY_FORMAT):
            raise errors.InterfaceError(
                _('data format already is %s and must be set only once') %
                _compat.text_repr(self._data_format.format), self._location)
        lower_value = value.lower()
        if self._data_format is None:
            self._data_format = data.DataFormat(lower_value, self._location)
        else:
            self._data_format.set_property(name.lower(), value, self._location)
Exemple #3
0
def excel_rows(source_path, sheet=1):
    """
    Rows read from an Excel document (both :file:`*.xls` and :file:`*.xlsx`
    thanks to :py:mod:`xlrd`).

    :param str source_path: path to the Excel file to be read
    :param int sheet: the sheet in the file to be read
    :return: sequence of lists with each list representing a row in the \
      Excel file
    :raises cutplace.errors.DataFormatError: in case the file cannot be read
    """
    assert source_path is not None
    assert sheet >= 1, 'sheet=%r' % sheet

    location = errors.Location(source_path, has_cell=True)
    try:
        with xlrd.open_workbook(source_path) as book:
            sheet = book.sheet_by_index(0)
            datemode = book.datemode
            for y in range(sheet.nrows):
                row = []
                for x in range(sheet.ncols):
                    row.append(_excel_cell_value(sheet.cell(y, x), datemode))
                    location.advance_cell()
                yield row
                location.advance_line()
    except xlrd.XLRDError as error:
        raise errors.DataFormatError(
            _('cannot read Excel file: %s') % error, location)
    except UnicodeError as error:
        raise errors.DataFormatError(
            _('cannot decode Excel data: %s') % error, location)
Exemple #4
0
    def ods_content_root():
        """
        `ElementTree` for content.xml in `source_ods_path`.
        """
        assert source_ods_path is not None

        location = errors.Location(source_ods_path)
        try:
            # HACK: Use ``closing()`` because of Python 2.6.
            with closing(zipfile.ZipFile(source_ods_path, "r")) as zip_archive:
                try:
                    xml_data = zip_archive.read("content.xml")
                except Exception as error:
                    raise errors.DataFormatError(
                        _('cannot extract content.xml for ODS spreadsheet: %s')
                        % error, location)
        except errors.DataFormatError:
            raise
        except Exception as error:
            raise errors.DataFormatError(
                _('cannot uncompress ODS spreadsheet: %s') % error, location)

        with io.BytesIO(xml_data) as xml_stream:
            try:
                tree = ElementTree.parse(xml_stream)
            except Exception as error:
                raise errors.DataFormatError(
                    _('cannot parse content.xml: %s') % error, location)

        return tree.getroot()
Exemple #5
0
def validated_field_name(supposed_field_name, location=None):
    """
    Same as ``supposed_field_name`` except with surrounding white space removed.

    :param cutplace.errors.Location location: location used in case of errors
    :raise cutplace.errors.InterfaceError: if ``supposed_field_name`` is \
      invalid
    """
    field_name = supposed_field_name.strip()
    basic_requirements_text_but_is_empty = _(
        'field name must be a valid Python name consisting of ASCII letters, ' \
        'underscore (_) and digits but is empty'
    )
    basic_requirements_text_but_is_x = (
        'field name must be a valid Python name consisting of ASCII letters, ' \
        'underscore (_) and digits but is: %s'
    )

    if field_name == '':
        raise errors.InterfaceError(basic_requirements_text_but_is_empty, location)
    if keyword.iskeyword(field_name):
        raise errors.InterfaceError(_("field name must not be a Python keyword but is: '%s'") % field_name, location)
    is_first_character = True
    for character in field_name:
        if is_first_character:
            if character not in _ASCII_LETTERS:
                raise errors.InterfaceError(
                    _("field name must begin with a lower-case letter but is: %s")
                    % _compat.text_repr(field_name), location)
            is_first_character = False
        else:
            if character not in _ASCII_LETTERS_DIGITS_AND_UNDERSCORE:
                raise errors.InterfaceError(
                    basic_requirements_text_but_is_x % _compat.text_repr(field_name), location)
    return field_name
Exemple #6
0
    def __init__(self, field_name, is_allowed_to_be_empty, length, rule, data_format):
        super(ConstantFieldFormat, self).__init__(
            field_name, is_allowed_to_be_empty, length, rule, data_format, empty_value='')

        # Extract constant from rule tokens.
        tokens = _tools.tokenize_without_space(rule)
        toky = next(tokens)
        if _tools.is_eof_token(toky):
            # No rule means that the field must always be empty.
            self._constant = ''
        else:
            self._constant = _tools.token_text(toky)
            toky = next(tokens)
            if not _tools.is_eof_token(toky):
                raise errors.InterfaceError(
                    _('constant rule must be a single Python token but also found: %s')
                    % _compat.text_repr(_tools.token_text(toky)))
        has_empty_rule = (rule == '')
        if self.is_allowed_to_be_empty and not has_empty_rule:
            raise errors.InterfaceError(
                _('to describe a Constant that can be empty, use a Choice field with a single choice'))
        if not self.is_allowed_to_be_empty and has_empty_rule:
            raise errors.InterfaceError(
                _('field must be marked as empty to describe a constant empty value'))
        try:
            self.length.validate(
                _('rule of constant field %s') % _compat.text_repr(self.field_name), len(self._constant))
        except errors.RangeValueError:
            raise errors.InterfaceError(
                _('length is %s but must be %d to match constant %s')
                % (self.length, len(self._constant), _compat.text_repr(self._constant)))
Exemple #7
0
def validated_python_name(name, value):
    """
    Validated and cleaned up ``value`` that represents a Python name with any
    whitespace removed. If validation fails, raise :py:exc:`NameError` with
    mentioning ``name`` as the name under which ``value`` is known to the
    user.
    """
    assert name
    assert value is not None

    readable = io.StringIO(value.strip())
    toky = tokenize.generate_tokens(readable.readline)
    next_token = next(toky)
    next_type = next_token[0]
    result = next_token[1]
    if tokenize.ISEOF(next_type):
        raise NameError(_("%s must not be empty but was: %r") % (name, value))
    if next_type != token.NAME:
        raise NameError(_("%s must contain only ASCII letters, digits and underscore (_) but is: %r")
                        % (name, value))
    second_token = next(toky)
    second_token_type = second_token[0]
    if (not tokenize.ISEOF(second_token_type)) and (second_token_type != tokenize.NEWLINE):
        raise NameError(_("%s must be a single word, but after %r there also is %r") % (name, result, second_token[1]))
    return result
Exemple #8
0
    def validate_header(self, actual):
        """
        Validate the header row against the defined field names. `Header` must be set to `1`.

        :raises cutplace.errors.DataError: if any the column names and field names do not match
        exactly (it is both order and case sensitive).
        """
        expected = self.cid.field_names
        missing = sorted(list(set(expected) - set(actual)))
        unexpected = sorted(list(set(actual) - set(expected)))

        missing_msg = ''
        if missing:
            missing_msg = _('\nThe following columns are missing:\n  {missing}')

        unexpected_msg = ''
        if unexpected:
            unexpected_msg = _('\nThe following columns are unexpected:\n  {unexpected}')

        if actual != expected:
            error_msg = _('The following columns are expected in the header row:\n  {expected}')
            if set(actual) == set(expected):
                error_msg += _(
                    '\nThe order of the columns does not match. '
                    'The following columns were received:\n  {actual}'
                )
            error_msg += missing_msg + unexpected_msg
            raise errors.DataError(error_msg.format(
                    expected=expected,
                    missing=missing,
                    unexpected=unexpected,
                    actual=actual
                )
            )
Exemple #9
0
    def _validated_character(key, value, location):
        r"""
        A single character intended as value for data format property ``key``
        derived from ``value``, which can be:

        * a decimal or hex number (prefixed with ``'0x'``) referring to the ASCII/Unicode of the character
        * a string containing a single character such as ``'\t'``.
        * a symbolic name from :py:const:`cutplace.errors.NAME_TO_ASCII_CODE_MAP` such as ``tab``.

        :raises cutplace.errors.InterfaceError: on any broken ``value``
        """
        assert key
        assert value is not None

        name_for_errors = 'data format property %s' % _compat.text_repr(key)
        stripped_value = value.strip()
        if (len(stripped_value) == 1) and (stripped_value
                                           not in string.digits):
            result_code = ord(stripped_value)
        else:
            tokens = tokenize.generate_tokens(io.StringIO(value).readline)
            next_token = next(tokens)
            if _tools.is_eof_token(next_token):
                raise errors.InterfaceError(
                    _("value for %s must be specified") % name_for_errors,
                    location)
            next_type = next_token[0]
            next_value = next_token[1]
            if next_type == token.NAME:
                result_code = ranges.code_for_symbolic_token(
                    name_for_errors, next_value, location)
            elif next_type == token.NUMBER:
                result_code = ranges.code_for_number_token(
                    name_for_errors, next_value, location)
            elif next_type == token.STRING:
                result_code = ranges.code_for_string_token(
                    name_for_errors, next_value, location)
            elif (len(next_value)
                  == 1) and not _tools.is_eof_token(next_token):
                result_code = ord(next_value)
            else:
                raise errors.InterfaceError(
                    _('value for %s must a number, a single character or a symbolic name but is: %s'
                      ) % (name_for_errors, _compat.text_repr(value)),
                    location)
            # Ensure there are no further tokens.
            next_token = next(tokens)
            if (not _tools.is_eof_token(next_token)) and (next_token[0] !=
                                                          tokenize.NEWLINE):
                raise errors.InterfaceError(
                    _('value for %s must be a single character but is: %s') %
                    (name_for_errors, _compat.text_repr(value)), location)
        # TODO: Handle 'none' properly.
        assert result_code is not None
        assert result_code >= 0
        result = six.unichr(result_code)
        assert result is not None
        return result
Exemple #10
0
    def validate_row(self, row):
        """
        Validate a single ``row``:

        1. Check if the number of items in ``row`` matches the number of
           fields in the CID
        2. Check that all fields conform to their field format (as defined
           by :py:class:`cutplace.fields.AbstractFieldFormat` and its
           descendants)
        3. Check that the row conforms to all row checks (as defined by
           :py:meth:`cutplace.checks.AbstractCheck.check_row`)

        The caller is responsible for :py:attr:`~.location` pointing to the
        correct row in the data while ``validate_row`` takes care of calling
        :py:meth:`cutplace.errors.Location.set_cell` appropriately.
        """
        assert row is not None
        assert self.location is not None

        # Validate that number of fields.
        actual_item_count = len(row)
        if actual_item_count < self._expected_item_count:
            raise errors.DataError(
                _('row must contain %d fields but only has %d: %s')
                % (self._expected_item_count, actual_item_count, row),
                self.location)
        if actual_item_count > self._expected_item_count:
            raise errors.DataError(
                _('row must contain %d fields but has %d, additional values are: %s')
                % (self._expected_item_count, actual_item_count, row[self._expected_item_count:]),
                self.location)

        # Validate each field according to its format.
        for field_index, field_value in enumerate(row):
            self.location.set_cell(field_index)
            field_to_validate = self.cid.field_formats[field_index]
            try:
                if not isinstance(field_value, six.text_type):
                    raise errors.FieldValueError(
                        _('type must be %s instead of %s: %s')
                        % (six.text_type.__name__, type(field_value).__name__, _compat.text_repr(field_value)))
                field_to_validate.validated(field_value)
            except errors.FieldValueError as error:
                error.prepend_message(
                    'cannot accept field %s' % _compat.text_repr(field_to_validate.field_name), self.location)
                raise

        # Validate the whole row according to row checks.
        self.location.set_cell(0)
        field_map = _create_field_map(self.cid.field_names, row)
        for check_name in self.cid.check_names:
            self.cid.check_map[check_name].check_row(field_map, self.location)
Exemple #11
0
    def add_check_row(self, possibly_incomplete_items):
        """
        Add a check as declared in ``possibly_incomplete_items``, which
        ideally is a list composed of 3 elements:

        1. description ('customer_id_must_be_unique')
        2. type (e.g. 'IsUnique'  mapping to :py:class:`cutplace.checks.IsUniqueCheck`)
        3. rule (e.g. 'customer_id')

        Missing items are interpreted as empty string (``''``), additional
        items are ignored.

        :raises cutplace.errors.InterfaceError: on broken \
          ``possibly_incomplete_items``
        """
        assert possibly_incomplete_items is not None

        items = list(possibly_incomplete_items)
        # HACK: Ignore possible concatenated (empty) cells between description and type.
        while (len(items) >= 2) and (items[1].strip() == ''):
            del items[1]

        check_description, check_type, check_rule = (items + 3 * [''])[:3]
        self._location.advance_cell()
        if check_description == '':
            raise errors.InterfaceError(
                _('check description must be specified'), self._location)
        self._location.advance_cell()
        check_class_name = check_type + "Check"
        if check_class_name not in self._check_name_to_class_map:
            list_of_available_check_types = _tools.human_readable_list(
                sorted(self._check_name_to_class_map.keys()))
            raise errors.InterfaceError(
                _("check type is '%s' but must be one of: %s") %
                (check_type, list_of_available_check_types), self._location)
        _log.debug("create check: %s(%r, %r)", check_type, check_description,
                   check_rule)
        check_class = self._create_check_class(check_type)
        check = check_class.__new__(check_class, check_description, check_rule,
                                    self._field_names, self._location)
        check.__init__(check_description, check_rule, self._field_names,
                       self._location)
        self._location.set_cell(1)
        existing_check = self._check_name_to_check_map.get(check_description)
        if existing_check is not None:
            raise errors.InterfaceError(
                _("check description must be used only once: %s") %
                _compat.text_repr(check_description), self._location,
                "first declaration", existing_check.location)
        self._check_name_to_check_map[check_description] = check
        self._check_names.append(check_description)
        assert len(self.check_names) == len(self._check_name_to_check_map)
Exemple #12
0
 def check_row(self, field_name_to_value_map, location):
     row_key = tuple(field_name_to_value_map[field_name]
                     for field_name in self._field_names_to_check)
     see_also_location = self._row_key_to_location_map.get(row_key)
     if see_also_location is not None:
         raise errors.CheckError(
             _("values for %r must be unique: %s") %
             (self._field_names_to_check, row_key),
             location,
             see_also_message=_("location of first occurrence"),
             see_also_location=see_also_location)
     else:
         self._row_key_to_location_map[row_key] = copy.copy(location)
Exemple #13
0
 def _validated_int_at_least_0(key, value, location):
     assert key
     assert value is not None
     try:
         result = int(value)
     except ValueError:
         raise errors.InterfaceError(
             _('data format property %s is %s but must be a number') %
             (_compat.text_repr(key), _compat.text_repr(value)), location)
     if result < 0:
         raise errors.InterfaceError(
             _('data format property %s is %d but must be at least 0') %
             (_compat.text_repr(key), result), location)
     return result
Exemple #14
0
    def __init__(self,
                 description,
                 rule,
                 available_field_names,
                 location=None):
        super(IsUniqueCheck, self).__init__(description, rule,
                                            available_field_names, location)

        self._field_names_to_check = []
        self._row_key_to_location_map = None
        self.reset()

        # Extract field names to check from rule.
        rule_read_line = _compat.token_io_readline(rule)
        toky = tokenize.generate_tokens(rule_read_line)
        after_comma = True
        next_token = next(toky)
        unique_field_names = set()
        while (not _tools.is_eof_token(next_token)) and (next_token[0] !=
                                                         tokenize.NEWLINE):
            token_type = next_token[0]
            token_value = next_token[1]
            if after_comma:
                if token_type != tokenize.NAME:
                    raise errors.InterfaceError(
                        _("field name must contain only ASCII letters, numbers and underscores (_) "
                          "but found: %r [token type=%r]") %
                        (token_value, token_type), self.location_of_rule)
                try:
                    fields.field_name_index(token_value, available_field_names,
                                            location)
                    if token_value in unique_field_names:
                        raise errors.InterfaceError(
                            _("duplicate field name for unique check must be removed: %s"
                              ) % token_value, self.location_of_rule)
                    unique_field_names.add(token_value)
                except errors.InterfaceError as error:
                    raise errors.InterfaceError(six.text_type(error))
                self._field_names_to_check.append(token_value)
            elif not _tools.is_comma_token(next_token):
                raise errors.InterfaceError(
                    _("after field name a comma (,) must follow but found: %r")
                    % token_value, self.location_of_rule)
            after_comma = not after_comma
            next_token = next(toky)
        if not len(self._field_names_to_check):
            raise errors.InterfaceError(
                _("rule must contain at least one field name to check for uniqueness"
                  ), self.location_of_rule)
Exemple #15
0
def code_for_string_token(name, value, location):
    """
    The numeric code for text representing an string with a single character in ``value``.

    :param str name: the name of the value as it is known to the end user
    :param str value: the text that represents a string with a single character
    :param cutplace.errors.Location location: the location of ``value`` or ``None``
    """
    assert name is not None
    assert value is not None
    assert len(value) >= 2
    left_quote = value[0]
    right_quote = value[-1]
    assert left_quote in "\"\'", "left_quote=%r" % left_quote
    assert right_quote in "\"\'", "right_quote=%r" % right_quote

    value_without_quotes = value[1:-1]
    if len(value_without_quotes) != 1:
        value_without_quotes = value_without_quotes.encode('utf-8').decode(
            'unicode_escape')
        if len(value_without_quotes) != 1:
            raise errors.InterfaceError(
                _('text for %s must be a single character but is: %s') %
                (name, _compat.text_repr(value)), location)
    return ord(value_without_quotes)
Exemple #16
0
    def validate(self, name, value, location=None):
        """
        Validate that ``value`` is within the specified range.

        :param str name: the name of ``value`` known to the end user for \
          usage in possible error messages
        :param int value: the value to validate
        :param cutplace.errors.Location location: the location to refer to \
          in possible error messages
        :raises cutplace.errors.RangeValueError: if ``value`` is out of range
        """
        assert name is not None
        assert name
        assert value is not None

        if self._items is not None:
            is_valid = False
            item_index = 0
            while not is_valid and item_index < len(self._items):
                lower, upper = self._items[item_index]
                if lower is None:
                    assert upper is not None
                    if value <= upper:
                        is_valid = True
                elif upper is None:
                    if value >= lower:
                        is_valid = True
                elif (value >= lower) and (value <= upper):
                    is_valid = True
                item_index += 1
            if not is_valid:
                raise errors.RangeValueError(
                    _("%s is %r but must be within range: %s") %
                    (name, value, self), location)
Exemple #17
0
    def validate_length(self, value):
        """
        Validate that ``value`` conforms to
        :py:attr:`~cutplace.fields.AbstractFieldFormat.length`.

        :raises cutplace.errors.FieldValueError: if ``value`` is too short \
          or too long
        """
        assert value is not None

        if self.length is not None and not (self.is_allowed_to_be_empty and (value == '')):
            try:
                if self.data_format.format == data.FORMAT_FIXED:
                    # Length of fixed format is considered a maximum, fewer characters have to be padded later.
                    value_length = len(value)
                    fixed_length = self.length.lower_limit
                    if value_length > fixed_length:
                        raise errors.FieldValueError(
                            _('fixed format field must have at most %d characters instead of %d: %s')
                            % (fixed_length, value_length, _compat.text_repr(value))
                        )
                else:
                    self.length.validate(
                        "length of '%s' with value %s" % (self.field_name, _compat.text_repr(value)), len(value))
            except errors.RangeValueError as error:
                raise errors.FieldValueError(six.text_type(error))
Exemple #18
0
    def __init__(self,
                 description,
                 rule,
                 available_field_names,
                 location_of_definition=None):
        r"""
        Create a check.

        :param str description: human readable description of the check
        :param str rule: the check conditions to validate
        :param list available_field_names: the names of the fields available for the check (typically referring \
            to :py:attr:`cutplace.interface.Cid.field_names`)
        :param location_of_definition: location in the CID where the check was declared to be (used by error \
            messages); if ``None``, use ``cutplace.errors.create_caller_location(['checks'])``
        :type location_of_definition: :py:class:`~cutplace.errors.Location` or None
        """
        assert description
        assert rule is not None
        assert available_field_names is not None

        if not available_field_names:
            raise errors.InterfaceError(
                _("field names must be specified before check"),
                location_of_definition)
        self._description = description
        self._rule = rule
        self._field_names = available_field_names
        if location_of_definition is None:
            self._location = errors.create_caller_location(['checks'])
            self._location_of_rule = self._location
        else:
            self._location = copy.copy(location_of_definition)
            self._location.set_cell(1)
            self._location_of_rule = copy.copy(location_of_definition)
            self._location_of_rule.set_cell(3)
Exemple #19
0
    def __init__(self,
                 description,
                 rule,
                 available_field_names,
                 location=None):
        super(DistinctCountCheck,
              self).__init__(description, rule, available_field_names,
                             location)

        rule_read_line = _compat.token_io_readline(rule)
        tokens = tokenize.generate_tokens(rule_read_line)
        first_token = next(tokens)

        # Obtain and validate field to count.
        if first_token[0] != tokenize.NAME:
            raise errors.InterfaceError(
                _("rule must start with a field name but found: %r") %
                first_token[1], self.location_of_rule)
        self._field_name_to_count = first_token[1]
        fields.field_name_index(self._field_name_to_count,
                                available_field_names, location)
        line_where_field_name_ends, column_where_field_name_ends = first_token[
            3]
        assert column_where_field_name_ends > 0
        assert line_where_field_name_ends == 1

        # Build and test Python expression for validation.
        self._expression = DistinctCountCheck._COUNT_NAME + rule[
            column_where_field_name_ends:]
        self._distinct_value_to_count_map = None
        self.reset()
        self._eval()
Exemple #20
0
def _raise_delimited_data_format_error(delimited_path, reader, error):
    location = errors.Location(delimited_path)
    line_number = reader.line_num
    if line_number > 0:
        location.advance_line(line_number)
    raise errors.DataFormatError(
        _('cannot parse delimited file: %s') % error, location)
Exemple #21
0
    def validated_value(self, value):
        assert value

        if value not in self.choices:
            raise errors.FieldValueError(
                _("value is %s but must be one of: %s")
                % (_compat.text_repr(value), _tools.human_readable_list(self.choices)))
        return value
Exemple #22
0
    def validated_value(self, value):
        assert value

        if not self.regex.match(value):
            raise errors.FieldValueError(
                _("value %s must match regular expression: %s")
                % (_compat.text_repr(value), _compat.text_repr(self.rule)))
        return value
Exemple #23
0
 def write_row(self, row_to_write):
     try:
         self._delimited_writer.writerow(row_to_write)
     except UnicodeEncodeError as error:
         raise errors.DataFormatError(
             _('cannot write data row: %s; row=%s') % (error, row_to_write),
             self.location)
     self._location.advance_line()
Exemple #24
0
    def _has_data_after_skipped_line_delimiter():
        """
        If `fixed_file` has data, assume they are a line delimiter as specified
        by `line_delimiter` and read and validate them.

        In case `line_delimiter` is `None`, the result is always ``True`` even
        if the input has already reached its end.
        """
        assert location is not None
        assert line_delimiter in _VALID_FIXED_LINE_DELIMITERS
        assert unread_character_after_line_delimiter[0] is None

        result = True
        if line_delimiter is not None:
            if line_delimiter == '\r\n':
                actual_line_delimiter = fixed_file.read(2)
            else:
                assert line_delimiter in ('\n', '\r', 'any')
                actual_line_delimiter = fixed_file.read(1)
            if actual_line_delimiter == '':
                result = False
            elif line_delimiter == 'any':
                if actual_line_delimiter == '\r':
                    # Process the optional '\n' for 'any'.
                    anticipated_linefeed = fixed_file.read(1)
                    if anticipated_linefeed == '\n':
                        actual_line_delimiter += anticipated_linefeed
                    elif anticipated_linefeed == '':
                        result = False
                    else:
                        # Unread the previous character because it is unrelated to line delimiters.
                        unread_character_after_line_delimiter[
                            0] = anticipated_linefeed
                if actual_line_delimiter not in _VALID_FIXED_ANY_LINE_DELIMITERS:
                    valid_line_delimiters = _tools.human_readable_list(
                        _VALID_FIXED_ANY_LINE_DELIMITERS)
                    raise errors.DataFormatError(
                        _('line delimiter is %s but must be one of: %s') %
                        (_compat.text_repr(actual_line_delimiter),
                         valid_line_delimiters), location)
            elif actual_line_delimiter != line_delimiter:
                raise errors.DataFormatError(
                    _('line delimiter is %s but must be %s') %
                    (_compat.text_repr(actual_line_delimiter),
                     _compat.text_repr(line_delimiter)), location)
        return result
Exemple #25
0
    def validated_value(self, value):
        assert value

        if not self.regex.match(value):
            raise errors.FieldValueError(
                _('value %s must match pattern: %s (regex %s)')
                % (_compat.text_repr(value), _compat.text_repr(self.rule), _compat.text_repr(self.pattern)))
        return value
Exemple #26
0
    def read(self, cid_path, rows):
        """
        Provided no ``cid_path`` has already been specified for
        :py:class:`~cutplace.interface.Cid.__init__()`, process ``rows``
        using :py:meth:`~cutplace.interface.Cid.add_data_format_row()`,
        :py:meth:`~cutplace.interface.Cid.add_field_format()` and
        :py:meth:`~cutplace.interface.Cid.add_check()`. Report any errors by
        referring to ``cid_path``.

        :param str cid_path: the path from which ``rows`` where obtained
        :param sequence rows: sequence of lists where each list either \
          describes a data format, field format, check or comment for a CID.

        :raises cutplace.errors.InterfaceError: in case any row in ``rows`` \
          cannot be processed
        """
        assert cid_path is not None
        assert self.data_format is None, 'CID must be read only once'

        # TODO: Detect format and use proper reader.
        self._location = errors.Location(cid_path, has_cell=True)
        if self._cid_path is None:
            self._cid_path = cid_path
        for row in rows:
            if row:
                row_type = row[0].lower().strip()
                row_data = (row[1:] + [''] * 6)[:6]
                if row_type == 'd':
                    self.add_data_format_row(row_data)
                elif row_type == 'f':
                    self.add_field_format_row(row_data)
                elif row_type == 'c':
                    self.add_check_row(row_data)
                elif row_type != '':
                    # Raise error when value is not supported.
                    raise errors.InterfaceError(
                        _('CID row type is "%s" but must be empty or one of: C, D, or F'
                          ) % row_type, self._location)
            self._location.advance_line()
        if self.data_format is None:
            raise errors.InterfaceError(_('data format must be specified'),
                                        self._location)
        self.data_format.validate()
        if len(self.field_names) == 0:
            raise errors.InterfaceError(_('fields must be specified'),
                                        self._location)
Exemple #27
0
    def validate(self, name, value, location=None):
        """
        Validate that ``value`` is within the specified range.

        :param str name: the name of ``value`` known to the end user for \
          usage in possible error messages
        :param int value: the value to validate
        :param cutplace.errors.Location location: the location to refer to \
          in possible error messages
        :raises cutplace.errors.RangeValueError: if ``value`` is out of range
        """
        assert name is not None
        assert name
        assert value is not None

        if not isinstance(value, decimal.Decimal):
            try:
                value_as_decimal = decimal.Decimal(value)
            except decimal.DecimalException:
                raise errors.RangeValueError(
                    _("value must be decimal but is %s") %
                    _compat.text_repr(value), location)
        else:
            value_as_decimal = value

        if self._items is not None:
            is_valid = False
            item_index = 0
            while not is_valid and item_index < len(self._items):
                lower, upper = self._items[item_index]
                if lower is None:
                    assert upper is not None
                    if value_as_decimal <= upper:
                        is_valid = True
                elif upper is None:
                    if value_as_decimal >= lower:
                        is_valid = True
                elif (value_as_decimal >= lower) and (value_as_decimal <=
                                                      upper):
                    is_valid = True
                item_index += 1
            if not is_valid:
                raise errors.RangeValueError(
                    _("%s is %r but must be within range: %r") %
                    (name, value_as_decimal, self), location)
Exemple #28
0
 def _eval(self):
     """
     The current result of `self._expression`.
     """
     local_variables = {
         DistinctCountCheck._COUNT_NAME: self._distinct_count()
     }
     try:
         result = eval(self._expression, {}, local_variables)
     except Exception as message:
         raise errors.InterfaceError(
             _("cannot evaluate count expression %r: %s") %
             (self._expression, message), self.location_of_rule)
     if result not in (True, False):
         raise errors.InterfaceError(
             _("count expression %r must result in %r or %r, but test resulted in: %r"
               ) % (self._expression, True, False, result),
             self.location_of_rule)
     return result
Exemple #29
0
 def check_distinct(name1, name2):
     assert name1 is not None
     assert name2 is not None
     assert name1 < name2, 'names must be sorted for consistent error message: %r, %r' % (
         name1, name2)
     value1 = self.__dict__['_' + name1]
     value2 = self.__dict__['_' + name2]
     if value1 == value2:
         raise errors.InterfaceError(
             _("'%s' and '%s' are both %s but must be different from each other"
               ) % (name1, name2, _compat.text_repr(value1)))
Exemple #30
0
    def validated_value(self, value):
        assert value

        try:
            value_as_int = int(value)
        except ValueError:
            raise errors.FieldValueError(_("value must be an integer number: %s") % _compat.text_repr(value))
        try:
            self.valid_range.validate("value", value_as_int)
        except errors.RangeValueError as error:
            raise errors.FieldValueError(six.text_type(error))
        return value_as_int