Skip to content

Commit

Permalink
Fix importer fewer columns than expected Traceback (#277)
Browse files Browse the repository at this point in the history
Now when an importer spec is modified to have a column
reference that is out of range for the source data, an
error is shown. If such importer spec is executed an
error will also be thrown and logged.

Re spine-tools/Spine-Toolbox#2333
  • Loading branch information
PiispaH committed Oct 5, 2023
1 parent 7406ac9 commit af34079
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 7 deletions.
3 changes: 2 additions & 1 deletion spinedb_api/import_mapping/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
33 changes: 29 additions & 4 deletions spinedb_api/import_mapping/import_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions spinedb_api/spine_io/importers/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down

0 comments on commit af34079

Please sign in to comment.