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
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)
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)
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()
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
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)))
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
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 ) )
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
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)
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)
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)
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
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)
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)
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)
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))
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)
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()
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)
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
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
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()
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
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
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)
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)
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
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)))
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