diff --git a/spinedb_api/import_mapping/generator.py b/spinedb_api/import_mapping/generator.py index 1af95d9d..b73037a4 100644 --- a/spinedb_api/import_mapping/generator.py +++ b/spinedb_api/import_mapping/generator.py @@ -83,6 +83,7 @@ def get_mapped_data( rows = list(data_source) if not rows: return mapped_data, errors + column_count = len(max(rows, key=lambda x: len(x) if x else 0)) if column_convert_fns is None: column_convert_fns = {} if row_convert_fns is None: @@ -92,7 +93,7 @@ def get_mapped_data( for mapping in mappings: read_state = {} mapping = deepcopy(mapping) - mapping.polish(table_name, data_header) + mapping.polish(table_name, data_header, column_count) mapping_errors = check_validity(mapping) if mapping_errors: errors += mapping_errors diff --git a/spinedb_api/import_mapping/import_mapping.py b/spinedb_api/import_mapping/import_mapping.py index 5259a7f1..c07d4f3c 100644 --- a/spinedb_api/import_mapping/import_mapping.py +++ b/spinedb_api/import_mapping/import_mapping.py @@ -135,22 +135,44 @@ def read_start_row(self, row): raise ValueError(f"row must be >= 0 ({row})") self._read_start_row = row - def polish(self, table_name, source_header, for_preview=False): + def check_for_invalid_column_refs(self, header, table_name): + """Checks that the mappings column refs are not out of range for the source table + + Args: + header (list): The header of the table as a list + table_name (str): The name of the source table + + Returns: + str: Error message if a column ref exceeds the column count of the source table, + empty string otherwise + """ + if self.child is not None: + error = self.child.check_for_invalid_column_refs(header, table_name) + if error: + return error + if isinstance(self.position, int) and self.position >= len(header) > 0: + msg = f"Column ref {self.position + 1} is out of range for the source table \"{table_name}\"" + return msg + return "" + + def polish(self, table_name, source_header, column_count=0, for_preview=False): """Polishes the mapping before an import operation. 'Expands' transient ``position`` and ``value`` attributes into their final value. Args: table_name (str) source_header (list(str)) + column_count (int, optional) + for_preview (bool, optional) """ - self._polish_for_import(table_name, source_header) + self._polish_for_import(table_name, source_header, column_count) if for_preview: self._polish_for_preview(source_header) - def _polish_for_import(self, table_name, source_header): + def _polish_for_import(self, table_name, source_header, column_count): # FIXME: Polish skip columns if self.child is not None: - self.child._polish_for_import(table_name, source_header) + self.child._polish_for_import(table_name, source_header, column_count) if isinstance(self.position, str): # Column mapping with string position, we need to find the index in the header try: @@ -185,6 +207,9 @@ def _polish_for_import(self, table_name, source_header): except IndexError: msg = f"'{self.value}' is not a valid index in header '{source_header}'" raise InvalidMappingComponent(msg) + if isinstance(self.position, int) and self.position >= column_count > 0: + msg = f"Column ref {self.position + 1} is out of range for the source table \"{table_name}\"" + raise InvalidMappingComponent(msg) def _polish_for_preview(self, source_header): if self.position == Position.header and self.value is not None: diff --git a/spinedb_api/spine_io/importers/reader.py b/spinedb_api/spine_io/importers/reader.py index 92eda12f..051618b9 100644 --- a/spinedb_api/spine_io/importers/reader.py +++ b/spinedb_api/spine_io/importers/reader.py @@ -16,7 +16,7 @@ from itertools import islice -from spinedb_api.exception import ConnectorError +from spinedb_api.exception import ConnectorError, InvalidMappingComponent from spinedb_api.import_mapping.generator import get_mapped_data, identity from spinedb_api.import_mapping.import_mapping_compat import parse_named_mapping_spec from spinedb_api import DateTime, Duration, ParameterValueFormatError @@ -151,7 +151,7 @@ def get_mapped_data( row_convert_fns, unparse_value, ) - except (ConnectorError, ParameterValueFormatError) as error: + except (ConnectorError, ParameterValueFormatError, InvalidMappingComponent) as error: errors.append(str(error)) continue for key, value in data.items():