From 4ad091e79c7256f9a27aad2c22c3d290bd04858e Mon Sep 17 00:00:00 2001 From: sellth Date: Mon, 5 Feb 2024 19:46:26 +0100 Subject: [PATCH] chore: Convert str.format to f-strings (#138) --- altamisa/apps/isatab2dot.py | 33 +++---- altamisa/apps/isatab2isatab.py | 9 +- altamisa/apps/isatab_validate.py | 6 +- altamisa/isatab/headers.py | 6 +- altamisa/isatab/helpers.py | 3 +- altamisa/isatab/models.py | 2 +- altamisa/isatab/parse_assay_study.py | 76 +++++++--------- altamisa/isatab/parse_investigation.py | 69 +++++--------- altamisa/isatab/validate_assay_study.py | 72 +++++++-------- altamisa/isatab/validate_investigation.py | 105 +++++++++++----------- altamisa/isatab/write_assay_study.py | 31 ++++--- altamisa/isatab/write_investigation.py | 11 +-- docs/examples/process_isa_model.py | 6 +- tests/__snapshots__/test_apps.ambr | 6 +- tests/__snapshots__/test_parse_study.ambr | 12 ++- tests/test_parse_investigation.py | 2 +- tests/test_write_assay.py | 15 ++-- tests/test_write_study.py | 8 +- 18 files changed, 209 insertions(+), 263 deletions(-) diff --git a/altamisa/apps/isatab2dot.py b/altamisa/apps/isatab2dot.py index 30f4cb0..eeeecc7 100644 --- a/altamisa/apps/isatab2dot.py +++ b/altamisa/apps/isatab2dot.py @@ -34,18 +34,15 @@ def print_dot( ): print(indent + "/* materials */", file=outf) for name, mat in obj.materials.items(): - label = json.dumps("{}:\n{}\n({})".format(mat.type, mat.name if mat.name else "-", name)) + label = json.dumps(f"{mat.type}:\n{mat.name if mat.name else '-'}\n({name})") print( - "{}{} [label={},shape={},color={},fontcolor={}]".format( - indent, json.dumps(name), label, mat_shape, mat_color, mat_color - ), + f"{indent}{json.dumps(name)} [label={label},shape={mat_shape},color={mat_color},fontcolor={mat_color}]", file=outf, ) print(indent + "/* processes */", file=outf) for name, proc in obj.processes.items(): label = json.dumps( - "{}:\n{}\n{}\n({})".format( - "Process", + "Process:\n{}\n{}\n({})".format( proc.protocol_ref if proc.protocol_ref else "-", proc.name if proc.name else "-", name, @@ -59,7 +56,7 @@ def print_dot( ) print(indent + "/* arcs */", file=outf) for arc in obj.arcs: - print("{}{} -> {};".format(indent, json.dumps(arc.tail), json.dumps(arc.head)), file=outf) + print(f"{indent}{json.dumps(arc.tail)} -> {json.dumps(arc.head)};", file=outf) def run(args: Arguments): @@ -79,27 +76,25 @@ def run(args: Arguments): for s, study_info in enumerate(investigation.studies): if not study_info.info.path: - print(" /* no file for study {} */".format(s + 1), file=output_file) + print(f" /* no file for study {s + 1} */", file=output_file) continue with open(os.path.join(path, study_info.info.path), "rt") as inputf: - study = StudyReader.from_stream("S{}".format(s + 1), inputf).read() - print(" /* study {} */".format(study_info.info.path), file=output_file) - print(" subgraph clusterStudy{} {{".format(s), file=output_file) - print(' label = "Study: {}"'.format(study_info.info.path), file=output_file) + study = StudyReader.from_stream(f"S{s + 1}", inputf).read() + print(f" /* study {study_info.info.path} */", file=output_file) + print(f" subgraph clusterStudy{s} {{", file=output_file) + print(f' label = "Study: {study_info.info.path}"', file=output_file) print_dot(study, output_file) print(" }", file=output_file) for a, assay_info in enumerate(study_info.assays): if not assay_info.path: - print(" /* no file for assay {} */".format(a + 1), file=output_file) + print(f" /* no file for assay {a + 1} */", file=output_file) continue with open(os.path.join(path, assay_info.path), "rt") as inputf: - assay = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() - print(" /* assay {} */".format(assay_info.path), file=output_file) - print(" subgraph clusterAssayS{}A{} {{".format(s, a), file=output_file) - print(' label = "Assay: {}"'.format(assay_info.path), file=output_file) + assay = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() + print(f" /* assay {assay_info.path} */", file=output_file) + print(f" subgraph clusterAssayS{s}A{a} {{", file=output_file) + print(f' label = "Assay: {assay_info.path}"', file=output_file) print_dot(assay, output_file) print(" }", file=output_file) diff --git a/altamisa/apps/isatab2isatab.py b/altamisa/apps/isatab2isatab.py index bc889df..be3aa22 100644 --- a/altamisa/apps/isatab2isatab.py +++ b/altamisa/apps/isatab2isatab.py @@ -61,8 +61,7 @@ def run_warnings_caught(args: Arguments): path_in = os.path.realpath(os.path.dirname(args.input_investigation_file)) path_out = os.path.realpath(os.path.dirname(args.output_investigation_file)) if path_in == path_out: - tpl = "Can't output ISA-tab files to same directory as as input: {} == {}" - msg = tpl.format(path_in, path_out) + msg = f"Can't output ISA-tab files to same directory as as input: {path_in} == {path_out}" raise IsaException(msg) with ExitStack() as stack: @@ -98,15 +97,13 @@ def run_reading( for s, study_info in enumerate(investigation.studies): if study_info.info.path: with open(os.path.join(path_in, study_info.info.path), "rt") as inputf: - studies[s] = StudyReader.from_stream("S{}".format(s + 1), inputf).read() + studies[s] = StudyReader.from_stream(f"S{s + 1}", inputf).read() if study_info.assays: assays[s] = {} for a, assay_info in enumerate(study_info.assays): if assay_info.path: with open(os.path.join(path_in, assay_info.path), "rt") as inputf: - assays[s][a] = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() + assays[s][a] = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() # Validate studies and assays for s, study_info in enumerate(investigation.studies): diff --git a/altamisa/apps/isatab_validate.py b/altamisa/apps/isatab_validate.py index 32acb34..1bc66e2 100644 --- a/altamisa/apps/isatab_validate.py +++ b/altamisa/apps/isatab_validate.py @@ -82,15 +82,13 @@ def run_warnings_caught(args: Arguments): for s, study_info in enumerate(investigation.studies): if study_info.info.path: with open(os.path.join(path_in, study_info.info.path), "rt") as inputf: - studies[s] = StudyReader.from_stream("S{}".format(s + 1), inputf).read() + studies[s] = StudyReader.from_stream(f"S{s + 1}", inputf).read() if study_info.assays: assays[s] = {} for a, assay_info in enumerate(study_info.assays): if assay_info.path: with open(os.path.join(path_in, assay_info.path), "rt") as inputf: - assays[s][a] = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() + assays[s][a] = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() # Validate studies and assays for s, study_info in enumerate(investigation.studies): diff --git a/altamisa/isatab/headers.py b/altamisa/isatab/headers.py index 09849ae..890a54e 100644 --- a/altamisa/isatab/headers.py +++ b/altamisa/isatab/headers.py @@ -446,8 +446,7 @@ def _parse_next(self): raise ParseIsatabException(msg) return self._parse_labeled_column_header(val, label, type_) # None of the if-statements above was taken - tpl = 'Header "{}" unknown, processing unclear' - msg = tpl.format(val) + msg = f'Header "{val}" unknown, processing unclear' raise ParseIsatabException(msg) def _parse_term_source_ref(self): @@ -471,8 +470,7 @@ def _parse_simple_column_header(self, type_): def _parse_labeled_column_header(self, val, key, type_): tok = val[len(key) :] # strip '^{key}' if not tok or tok[0] != "[" or tok[-1] != "]": - tpl = "Problem parsing labeled header {}" - msg = tpl.format(val) + msg = f"Problem parsing labeled header {val}" raise ParseIsatabException(msg) self.col_no += 1 return type_(self.col_no - 1, tok[1:-1]) diff --git a/altamisa/isatab/helpers.py b/altamisa/isatab/helpers.py index a1666d8..5f09aef 100644 --- a/altamisa/isatab/helpers.py +++ b/altamisa/isatab/helpers.py @@ -21,7 +21,6 @@ def list_strip(line: List[str]) -> List[str]: """Remove trailing space from strings in a list (e.g. a csv line)""" new_line = [field.strip() for field in line] if new_line != line: - tpl = "Removed trailing whitespaces in fields of line: {}" - msg = tpl.format(line) + msg = f"Removed trailing whitespaces in fields of line: {line}" warnings.warn(msg, ParseIsatabWarning) return new_line diff --git a/altamisa/isatab/models.py b/altamisa/isatab/models.py index 531da90..23360ef 100644 --- a/altamisa/isatab/models.py +++ b/altamisa/isatab/models.py @@ -509,7 +509,7 @@ def __getitem__(self, idx): elif idx == 1: return self.head else: - raise IndexError("Invalid index: %d" % idx) # pragma: no cover + raise IndexError(f"Invalid index: {idx}") # pragma: no cover @attr.s(auto_attribs=True, frozen=True) diff --git a/altamisa/isatab/parse_assay_study.py b/altamisa/isatab/parse_assay_study.py index 142efa0..515817f 100644 --- a/altamisa/isatab/parse_assay_study.py +++ b/altamisa/isatab/parse_assay_study.py @@ -130,8 +130,10 @@ def _assign_column_headers(self): # noqa: C901 header.column_type not in self.name_headers and header.column_type not in self.allowed_column_types ): - tpl = 'Invalid column type occured "{}" not in {}' - msg = tpl.format(header.column_type, self.allowed_column_types) + msg = ( + "Invalid column type occured " + f'"{header.column_type}" not in {self.allowed_column_types}' + ) raise ParseIsatabException(msg) # Most headers are not secondary, so make this the default state. is_secondary = False @@ -206,7 +208,7 @@ def _assign_column_headers(self): # noqa: C901 ): # pragma: no cover tpl = ( "Ontologies not supported for primary annotation " - "'{}' (in col {}).".format(prev.column_type, "{}") + f"'{prev.column_type}' (in col {{}})." ) elif prev.term_source_ref_header: # pragma: no cover tpl = 'Seen "Term Source REF" header for same entity ' "in col {}" @@ -232,8 +234,7 @@ def _assign_column_headers(self): # noqa: C901 @staticmethod def _raise_seen_before(name, col_no): # pragma: no cover - tpl = 'Seen "{}" header for same entity in col {}' - msg = tpl.format(name, col_no) + msg = f'Seen "{name}" header for same entity in col {col_no}' raise ParseIsatabException(msg) def _build_complex( @@ -256,7 +257,7 @@ def _build_complex( unit = self._build_freetext_or_term_ref(header.unit_header, line) if unit is not None and not isinstance(unit, (str, models.OntologyTermRef)): raise ParseIsatabException( - "Unit must be a string or an OntologyTermRef, not {}".format(type(unit)) + f"Unit must be a string or an OntologyTermRef, not {type(unit)}" ) # Then, constructing ``klass`` is easy return klass(header.label, value, unit) @@ -284,11 +285,10 @@ def _build_freetext_or_term_ref( ] return term_refs else: # pragma: no cover - tpl = ( + msg = ( "Irregular numbers of fields in ontology term columns" - "(i.e. ';'-separated fields): {}" + f"(i.e. ';'-separated fields): {line[header.col_no : header2.col_no + 2]}" ) - msg = tpl.format(line[header.col_no : header2.col_no + 2]) raise ParseIsatabException(msg) # Else, just create single ontology term references @@ -348,24 +348,22 @@ def build(self, line: List[str]) -> models.Material: # First, build the individual components if not self.name_header: raise ParseIsatabException( - "No name header found for material found for file {}".format(self.filename) + f"No name header found for material found for file {self.filename}" ) type_ = self.name_header.column_type - assay_id = "-{}".format(self.assay_id) if self.assay_id else "" + assay_id = f"-{self.assay_id}" if self.assay_id else "" name = line[self.name_header.col_no] if name: # make material/data names unique by column if self.name_header.column_type == table_headers.SOURCE_NAME: - unique_name = "{}-{}-{}".format(self.study_id, "source", name) + unique_name = f"{self.study_id}-source-{name}" elif self.name_header.column_type == table_headers.SAMPLE_NAME: # use static column identifier "sample-", since the same # samples occur in different columns in study and assay - unique_name = "{}-{}-{}".format(self.study_id, "sample", name) + unique_name = f"{self.study_id}-sample-{name}" else: # anything else gets the column id - unique_name = "{}{}-{}-COL{}".format( - self.study_id, assay_id, name, self.name_header.col_no + 1 - ) + unique_name = f"{self.study_id}{assay_id}-{name}-COL{self.name_header.col_no + 1}" else: name_val = "{}{}-{} {}-{}-{}".format( self.study_id, @@ -434,8 +432,7 @@ def build(self, line: List[str]) -> models.Process: try: date = datetime.strptime(line[self.date_header.col_no], "%Y-%m-%d").date() except ValueError as e: # pragma: no cover - tpl = 'Invalid ISO8601 date "{}"' # pragma: no cover - msg = tpl.format(line[self.date_header.col_no]) + msg = f'Invalid ISO8601 date "{line[self.date_header.col_no]}"' # pragma: no cover raise ParseIsatabException(msg) from e else: date = "" @@ -479,14 +476,11 @@ def _build_protocol_ref_and_name( ) -> Tuple[str, Union[models.AnnotatedStr, str], Optional[str], Optional[str]]: # At least one of these headers has to be specified if not self.name_header and not self.protocol_ref_header: # pragma: no cover - raise ParseIsatabException( - "No protocol reference header found for process found for file {}".format( - self.filename - ) - ) + msg = f"No protocol reference header found for process found for file {self.filename}" + raise ParseIsatabException(msg) # Perform case distinction on which case is actually true counter_value = self._next_counter() - assay_id = "-{}".format(self.assay_id) if self.assay_id else "" + assay_id = f"-{self.assay_id}" if self.assay_id else "" name = None name_type = None if not self.name_header: # and self.protocol_ref_header: @@ -509,9 +503,7 @@ def _build_protocol_ref_and_name( name = line[self.name_header.col_no] name_type = self.name_header.column_type if name: # Use name if available - unique_name = "{}{}-{}-{}".format( - self.study_id, assay_id, name, self.name_header.col_no + 1 - ) + unique_name = f"{self.study_id}{assay_id}-{name}-{self.name_header.col_no + 1}" else: # Empty! # pragma: no cover name_val = "{}{}-{} {}-{}-{}".format( self.study_id, @@ -527,9 +519,7 @@ def _build_protocol_ref_and_name( name = line[self.name_header.col_no] name_type = self.name_header.column_type if name: - unique_name = "{}{}-{}-{}".format( - self.study_id, assay_id, name, self.name_header.col_no + 1 - ) + unique_name = f"{self.study_id}{assay_id}-{name}-{self.name_header.col_no + 1}" else: name_val = "{}{}-{}-{}-{}".format( self.study_id, @@ -544,7 +534,7 @@ def _build_protocol_ref_and_name( tpl = "Missing protocol reference in column {} of file {} " msg = tpl.format(self.protocol_ref_header.col_no + 1, self.filename) else: - msg = "Missing protocol reference in file {}".format(self.filename) + msg = f"Missing protocol reference in file {self.filename}" raise ParseIsatabException(msg) return protocol_ref, unique_name, name, name_type @@ -596,16 +586,16 @@ def _intercept_duplicates(self, start, end): ] header = [h for h in self.header[start:end] if h.column_type in column_types_to_check] names = [ - "{}[{}]".format(h.column_type, h.label) - if isinstance(h, LabeledColumnHeader) - else h.column_type + f"{h.column_type}[{h.label}]" if isinstance(h, LabeledColumnHeader) else h.column_type for h in header ] duplicates = set([c for c in names if names.count(c) > 1]) if duplicates: - assay = " assay {}".format(self.assay_id) if self.assay_id else "" - tpl = "Found duplicated column types in header of study {}{}: {}" - msg = tpl.format(self.study_id, assay, ", ".join(duplicates)) + assay = f" assay {self.assay_id}" if self.assay_id else "" + msg = ( + "Found duplicated column types in header of study " + f"{self.study_id}{assay}: {', '.join(duplicates)}" + ) raise ParseIsatabException(msg) def _make_breaks(self): @@ -768,11 +758,11 @@ def _construct(self, rows): if ( entry.unique_name in processes and entry != processes[entry.unique_name] ): # pragma: no cover - tpl = ( + msg = ( "Found processes with same name but different " - "annotation:\nprocess 1: {}\nprocess 2: {}" + f"annotation:\nprocess 1: {entry}\n" + f"process 2: {processes[entry.unique_name]}" ) - msg = tpl.format(entry, processes[entry.unique_name]) raise ParseIsatabException(msg) processes[entry.unique_name] = entry else: @@ -780,11 +770,11 @@ def _construct(self, rows): if ( entry.unique_name in materials and entry != materials[entry.unique_name] ): # pragma: no cover - tpl = ( + msg = ( "Found materials with same name but different " - "annotation:\nmaterial 1: {}\nmaterial 2: {}" + f"annotation:\nmaterial 1: {entry}\n" + f"material 2: {materials[entry.unique_name]}" ) - msg = tpl.format(entry, materials[entry.unique_name]) raise ParseIsatabException(msg) materials[entry.unique_name] = entry # Collect arc diff --git a/altamisa/isatab/parse_investigation.py b/altamisa/isatab/parse_investigation.py index e7a95fc..2e2893e 100644 --- a/altamisa/isatab/parse_investigation.py +++ b/altamisa/isatab/parse_investigation.py @@ -25,8 +25,7 @@ def _parse_comment_header(val): # key might start with "Comment[" but NOT "Comment [" tok = val[len("Comment") :] if not tok or tok[0] != "[" or tok[-1] != "]": # pragma: no cover - tpl = 'Problem parsing comment header "{}"' - msg = tpl.format(val) + msg = f'Problem parsing comment header "{val}"' raise ParseIsatabException(msg) return tok[1:-1] @@ -56,8 +55,7 @@ def _split_study_protocols_parameters( ) raise ParseIsatabException(msg) if len(names) > len(set(names)): # pragma: no cover - tpl = "Repeated protocol parameter; found: {}" - msg = tpl.format(names) + msg = f"Repeated protocol parameter; found: {names}" raise ParseIsatabException(msg) for name, acc, src in zip(names, name_term_accs, name_term_srcs): if any((name, acc, src)): # skips empty parameters @@ -88,15 +86,13 @@ def _split_study_protocols_components( ) raise ParseIsatabException(msg) if len(names) > len(set(names)): # pragma: no cover - tpl = "Repeated protocol components; found: {}" - msg = tpl.format(names) + msg = f"Repeated protocol components; found: {names}" raise ParseIsatabException(msg) for name, ctype, acc, src in zip( names, types, type_term_accs, type_term_srcs ): # pragma: no cover if not name and any((ctype, acc, src)): - tpl = "Missing protocol component name; " 'found: "{}", "{}", "{}", "{}"' - msg = tpl.format(name, ctype, acc, src) + msg = f'Missing protocol component name; found: "{name}", "{ctype}", "{acc}", "{src}"' raise ParseIsatabException(msg) if any((name, ctype, acc, src)): # skips empty components yield models.ProtocolComponentInfo(name, models.OntologyTermRef(ctype, acc, src)) @@ -192,18 +188,15 @@ def _read_multi_column_section(self, prefix: str, ref_keys: Sequence[str], secti if key.startswith("Comment"): comment_keys.append(key) elif key not in ref_keys: # pragma: no cover - tpl = "Line must start with one of {} but is {}" - msg = tpl.format(ref_keys, line) + msg = f"Line must start with one of {ref_keys} but is {line}" raise ParseIsatabException(msg) if key in section: # pragma: no cover - tpl = 'Key {} repeated, previous value "{}"' - msg = tpl.format(key, section[key]) + msg = f'Key {key} repeated, previous value "{section[key]}"' raise ParseIsatabException(msg) section[key] = line[1:] # Check that all keys are given and all contain the same number of entries if len(section) != len(ref_keys) + len(comment_keys): # pragma: no cover - tpl = "Missing entries in section {}; only found: {}" - msg = tpl.format(section_name, list(sorted(section))) + msg = f"Missing entries in section {section_name}; only found: {list(sorted(section))}" raise ParseIsatabException(msg) # TODO: should be warning? if not len(set([len(v) for v in section.values()])) == 1: # pragma: no cover lengths = "\n".join( @@ -224,26 +217,22 @@ def _read_single_column_section(self, prefix, ref_keys, section_name): if line is None: break if len(line) > 2: # pragma: no cover - tpl = "Line {} contains more than one value: {}" - msg = tpl.format(line[0], line[1:]) + msg = f"Line {line[0]} contains more than one value: {line[1:]}" raise ParseIsatabException(msg) key = line[0] if key.startswith("Comment"): comment_keys.append(key) elif key not in ref_keys: # pragma: no cover - tpl = "Line must start with one of {} but is {}" - msg = tpl.format(ref_keys, line) + msg = f"Line must start with one of {ref_keys} but is {line}" raise ParseIsatabException(msg) if key in section: # pragma: no cover - tpl = 'Key {} repeated, previous value "{}"' - msg = tpl.format(key, section[key]) + msg = f'Key {key} repeated, previous value "{section[key]}"' raise ParseIsatabException(msg) # read value if field is available, empty string else section[key] = line[1] if len(line) > 1 else "" # Check that all keys are given if len(section) != len(ref_keys) + len(comment_keys): # pragma: no cover - tpl = "Missing entries in section {}; only found: {}" - msg = tpl.format(section_name, list(sorted(section))) + msg = f"Missing entries in section {section_name}; only found: {list(sorted(section))}" raise ParseIsatabException(msg) # TODO: should be warning? return section, comment_keys @@ -253,8 +242,7 @@ def _read_ontology_source_reference(self) -> Iterator[models.OntologyRef]: if ( not line or not line[0] == investigation_headers.ONTOLOGY_SOURCE_REFERENCE ): # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.ONTOLOGY_SOURCE_REFERENCE, line) + msg = f"Expected {investigation_headers.ONTOLOGY_SOURCE_REFERENCE} but got {line}" raise ParseIsatabException(msg) # Read the other four lines in this section. section, comment_keys = self._read_multi_column_section( @@ -269,8 +257,7 @@ def _read_ontology_source_reference(self) -> Iterator[models.OntologyRef]: # If ontology source is empty, skip it # (since ISAcreator always adds a last empty ontology column) if not any((name, file_, version, desc, any(comments))): - tpl = "Skipping empty ontology source: {}, {}, {}, {}" - msg = tpl.format(name, file_, version, desc) + msg = f"Skipping empty ontology source: {name}, {file_}, {version}, {desc}" warnings.warn(msg, ParseIsatabWarning) continue yield models.OntologyRef(name, file_, version, desc, comments, list(section.keys())) @@ -279,8 +266,7 @@ def _read_basic_info(self) -> models.BasicInfo: # Read INVESTIGATION header line = self._read_next_line() if not line or not line[0] == investigation_headers.INVESTIGATION: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.INVESTIGATION, line) + msg = f"Expected {investigation_headers.INVESTIGATION} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_single_column_section( @@ -308,8 +294,7 @@ def _read_publications(self) -> Iterator[models.PublicationInfo]: if ( not line or not line[0] == investigation_headers.INVESTIGATION_PUBLICATIONS ): # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.INVESTIGATION_PUBLICATIONS, line) + msg = f"Expected {investigation_headers.INVESTIGATION_PUBLICATIONS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -335,8 +320,7 @@ def _read_contacts(self) -> Iterator[models.ContactInfo]: if ( not line or not line[0] == investigation_headers.INVESTIGATION_CONTACTS ): # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.INVESTIGATION_CONTACTS, line) + msg = f"Expected {investigation_headers.INVESTIGATION_CONTACTS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -383,8 +367,7 @@ def _read_studies(self) -> Iterator[models.StudyInfo]: # Read STUDY header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY, line) + msg = f"Expected {investigation_headers.STUDY} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_single_column_section( @@ -425,8 +408,7 @@ def _read_study_design_descriptors(self) -> Iterator[models.DesignDescriptorsInf if ( not line or not line[0] == investigation_headers.STUDY_DESIGN_DESCRIPTORS ): # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_DESIGN_DESCRIPTORS, line) + msg = f"Expected {investigation_headers.STUDY_DESIGN_DESCRIPTORS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -445,8 +427,7 @@ def _read_study_publications(self) -> Iterator[models.PublicationInfo]: # Read STUDY PUBLICATIONS header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY_PUBLICATIONS: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_PUBLICATIONS, line) + msg = f"Expected {investigation_headers.STUDY_PUBLICATIONS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -470,8 +451,7 @@ def _read_study_factors(self) -> Iterator[models.FactorInfo]: # Read STUDY FACTORS header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY_FACTORS: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_FACTORS, line) + msg = f"Expected {investigation_headers.STUDY_FACTORS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -490,8 +470,7 @@ def _read_study_assays(self) -> Iterator[models.AssayInfo]: # Read STUDY ASSAYS header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY_ASSAYS: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_ASSAYS, line) + msg = f"Expected {investigation_headers.STUDY_ASSAYS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -543,8 +522,7 @@ def _read_study_protocols(self) -> Iterator[models.ProtocolInfo]: # Read STUDY PROTOCOLS header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY_PROTOCOLS: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_PROTOCOLS, line) + msg = f"Expected {investigation_headers.STUDY_PROTOCOLS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( @@ -608,8 +586,7 @@ def _read_study_contacts(self) -> Iterator[models.ContactInfo]: # Read STUDY CONTACTS header line = self._read_next_line() if not line or not line[0] == investigation_headers.STUDY_CONTACTS: # pragma: no cover - tpl = "Expected {} but got {}" - msg = tpl.format(investigation_headers.STUDY_CONTACTS, line) + msg = f"Expected {investigation_headers.STUDY_CONTACTS} but got {line}" raise ParseIsatabException(msg) # Read the other lines in this section. section, comment_keys = self._read_multi_column_section( diff --git a/altamisa/isatab/validate_assay_study.py b/altamisa/isatab/validate_assay_study.py index 0be41e8..bba700a 100644 --- a/altamisa/isatab/validate_assay_study.py +++ b/altamisa/isatab/validate_assay_study.py @@ -41,9 +41,11 @@ def _validate_completeness(term_ref: models.OntologyTermRef): # All three variables must be available, if ontology name or accession is provided if term_ref.ontology_name or term_ref.accession: if not all((term_ref.name, term_ref.ontology_name, term_ref.accession)): - tpl = "Incomplete ontology term reference:\nName: {}\nOntology: {}\nAccession: {}" - msg = tpl.format( - term_ref.name or "?", term_ref.ontology_name or "?", term_ref.accession or "?" + msg = ( + "Incomplete ontology term reference:\n" + f"Name: {term_ref.name or '?'}\n" + f"Ontology: {term_ref.ontology_name or '?'}\n" + f"Accession: {term_ref.accession or '?'}" ) warnings.warn(msg, CriticalIsaValidationWarning) @@ -54,8 +56,7 @@ def _validate_ontology_source(self, term_ref: models.OntologyTermRef): and self.ontology_source_refs and term_ref.ontology_name not in self.ontology_source_refs ): - tpl = 'Ontology with name "{}" not defined in investigation!' - msg = tpl.format(term_ref.ontology_name) + msg = f'Ontology with name "{term_ref.ontology_name}" not defined in investigation!' warnings.warn(msg, CriticalIsaValidationWarning) @@ -105,8 +106,7 @@ def has_content(value): has_content(material.material_type), ) ): - tpl = "Found annotated material/file without name: {}" - msg = tpl.format(material) + msg = f"Found annotated material/file without name: {material}" warnings.warn(msg, CriticalIsaValidationWarning) # Warn about assay samples containing annotations, as they should be recorded in studies @@ -115,8 +115,9 @@ def has_content(value): and material.type == table_headers.SAMPLE_NAME and any((any_char, any_comm, any_fact, material.extract_label, material.material_type)) ): - tpl = "Found annotated Sample in assay (should be annotated in studies only): {}" - msg = tpl.format(material) + msg = ( + f"Found annotated Sample in assay (should be annotated in studies only): {material}" + ) warnings.warn(msg, CriticalIsaValidationWarning) def _validate_material_naming_start_node(self, material: models.Material): @@ -125,21 +126,18 @@ def _validate_material_naming_start_node(self, material: models.Material): (self._model_type == MODEL_TYPE_STUDY and material.type == table_headers.SOURCE_NAME) or (self._model_type == MODEL_TYPE_ASSAY and material.type == table_headers.SAMPLE_NAME) ) and not material.name: - tpl = "Found start node without original name: {}" - msg = tpl.format(material.unique_name) + msg = f"Found start node without original name: {material.unique_name}" warnings.warn(msg, CriticalIsaValidationWarning) @staticmethod def _validate_annotation_restrictions(material: models.Material): # Restrict certain annotations to corresponding material types if material.extract_label and material.type != table_headers.LABELED_EXTRACT_NAME: - tpl = "Label not applied to Labeled Extract Name: {}." - msg = tpl.format(material.type) + msg = f"Label not applied to Labeled Extract Name: {material.type}." warnings.warn(msg, CriticalIsaValidationWarning) if material.characteristics and material.type in table_headers.DATA_FILE_HEADERS: - tpl = "Data nodes don't support Characteristics: {}." - msg = tpl.format(material.characteristics) + msg = f"Data nodes don't support Characteristics: {material.characteristics}." warnings.warn(msg, CriticalIsaValidationWarning) if material.material_type and material.type not in ( @@ -150,8 +148,7 @@ def _validate_annotation_restrictions(material: models.Material): table_headers.SAMPLE_NAME, table_headers.SOURCE_NAME, ): - tpl = "Material Type not applied to proper Material: {}." - msg = tpl.format(material.type) + msg = f"Material Type not applied to proper Material: {material.type}." warnings.warn(msg, CriticalIsaValidationWarning) def _validate_assay_restrictions(self, type_): @@ -163,8 +160,7 @@ def _validate_assay_restrictions(self, type_): **table_restrictions.RESTRICTED_FILES_ATECH, }: if self._assay_info is None: - tpl = "Material/data '{}' not recommended for unspecified assay." - msg = tpl.format(type_) + msg = f"Material/data '{type_}' not recommended for unspecified assay." warnings.warn(msg, ModerateIsaValidationWarning) else: self._validate_single_assay_restriction( @@ -228,8 +224,7 @@ def _validate_factor_values(self, factor_values): # Validate whether used factor values are declared in investigation for factor in factor_values: if factor.name not in self._factor_refs: - tpl = 'Factor "{}" not declared in investigation' - msg = tpl.format(factor.name) + msg = f'Factor "{factor.name}" not declared in investigation' warnings.warn(msg, ModerateIsaValidationWarning) @@ -254,11 +249,10 @@ def validate(self, process: models.Process): self._validate_name_types(process) self._validate_special_case_annotations(process) else: - tpl = ( + msg = ( "Can't validate parameter values and names for process with " - 'undeclared protocol "{}" and name type "{}"' + f'undeclared protocol "{process.protocol_ref}" and name type "{process.name_type}"' ) - msg = tpl.format(process.protocol_ref, process.name_type) warnings.warn(msg, ModerateIsaValidationWarning) self._validate_ontology_term_refs(process) @@ -267,8 +261,7 @@ def _validate_protocol_ref(self, process: models.Process): if process.protocol_ref == table_tokens.TOKEN_UNKNOWN: return False elif process.protocol_ref not in self._protocols: - tpl = 'Protocol "{}" not declared in investigation file' - msg = tpl.format(process.protocol_ref) + msg = f'Protocol "{process.protocol_ref}" not declared in investigation file' warnings.warn(msg, CriticalIsaValidationWarning) return False else: @@ -277,10 +270,12 @@ def _validate_protocol_ref(self, process: models.Process): def _validate_parameter_values(self, process: models.Process): # Check if parameter value is declared in corresponding protocol if process.protocol_ref != table_tokens.TOKEN_UNKNOWN: - tpl = 'Parameter Value "{}" not declared for Protocol "{}" in investigation file' for pv in process.parameter_values: if pv.name not in self._protocols[process.protocol_ref].parameters: - msg = tpl.format(pv.name, process.protocol_ref) + msg = ( + f'Parameter Value "{pv.name}" not declared for Protocol ' + f'"{process.protocol_ref}" in investigation file' + ) warnings.warn(msg, ModerateIsaValidationWarning) def _validate_restrictions( @@ -288,8 +283,7 @@ def _validate_restrictions( ): if test in assay_tech_restrictions or test in protocol_type_restrictions: if self._assay_info is None: - tpl = '"{}" not supported for unspecified assay.' - msg = tpl.format(test) + msg = f'"{test}" not supported for unspecified assay.' warnings.warn(msg, ModerateIsaValidationWarning) else: # Check if restricted to assay technology @@ -403,8 +397,10 @@ def validate(self, arc: models.Arc): # Check that samples only start arcs, i.e. head can't be sample head = self._nodes[arc.head] if hasattr(head, "type") and head.type == table_headers.SAMPLE_NAME: - tpl = "Found a sample not starting the assay graph: '{}' ('{}')" - msg = tpl.format(head.name, head.unique_name) + msg = ( + "Found a sample not starting the assay graph: " + f'"{head.name}" ("{head.unique_name}")' + ) warnings.warn(msg, CriticalIsaValidationWarning) # Study checks @@ -412,14 +408,18 @@ def validate(self, arc: models.Arc): # Check that sources only start arcs, i.e. head can't be source head = self._nodes[arc.head] if hasattr(head, "type") and head.type == table_headers.SOURCE_NAME: - tpl = "Found a source not starting the study graph: '{}' ('{}')" - msg = tpl.format(head.name, head.unique_name) + msg = ( + "Found a source not starting the study graph: " + f'"{head.name}" ("{head.unique_name}")' + ) warnings.warn(msg, CriticalIsaValidationWarning) # Check that samples only start arcs, i.e. tail can't be sample tail = self._nodes[arc.tail] if hasattr(tail, "type") and tail.type == table_headers.SAMPLE_NAME: - tpl = "Found a sample not ending the study graph: '{}' ('{}')" - msg = tpl.format(tail.name, tail.unique_name) + msg = ( + "Found a sample not ending the study graph: " + f'"{tail.name}" ("{tail.unique_name}")' + ) warnings.warn(msg, CriticalIsaValidationWarning) diff --git a/altamisa/isatab/validate_investigation.py b/altamisa/isatab/validate_investigation.py index 136cfc3..d094d38 100644 --- a/altamisa/isatab/validate_investigation.py +++ b/altamisa/isatab/validate_investigation.py @@ -35,32 +35,28 @@ def _validate_mail_address(mail_address: str) -> None: """Helper function to validate mail strings""" if mail_address and not MAIL_PATTERN.match(mail_address): - tpl = "Invalid mail address: {}" - msg = tpl.format(mail_address) + msg = f"Invalid mail address: {mail_address}" warnings.warn(msg, AdvisoryIsaValidationWarning) def _validate_phone_number(phone_number: str) -> None: """Helper function to validate phone/fax number strings""" if phone_number and not PHONE_PATTERN.match(phone_number): - tpl = "Invalid phone/fax number: {}" - msg = tpl.format(phone_number) + msg = f"Invalid phone/fax number: {phone_number}" warnings.warn(msg, AdvisoryIsaValidationWarning) def _validate_doi(doi: str) -> None: """Helper function to validate doi strings""" if doi and not DOI_PATTERN.match(doi): - tpl = "Invalid doi string: {}" - msg = tpl.format(doi) + msg = f"Invalid doi string: {doi}" warnings.warn(msg, AdvisoryIsaValidationWarning) def _validate_pubmed_id(pubmed_id: str) -> None: """Helper function to validate pubmed id strings""" if pubmed_id and not PMID_PATTERN.match(pubmed_id): - tpl = "Invalid pubmed_id string: {}" - msg = tpl.format(pubmed_id) + msg = f"Invalid pubmed_id string: {pubmed_id}" warnings.warn(msg, AdvisoryIsaValidationWarning) @@ -92,16 +88,16 @@ def _validate_ontology_sources(self): for source in self._investigation.ontology_source_refs.values(): # Check that ontology sources are complete if not all((source.name, source.file, source.version, source.description)): - tpl = "Incomplete ontology source; found: {}, {}, {}, {}, {}" - msg = tpl.format( - source.name, source.file, source.version, source.description, source.comments + msg = ( + f"Incomplete ontology source; found: {source.name}, {source.file}, " + f"{source.version}, {source.description}, {source.comments}" ) warnings.warn(msg, CriticalIsaValidationWarning) # Check that ontology source names contain no whitespaces if re.search("\\s", source.name): - tpl = "Ontology source name including whitespace(s); found: {}, {}, {}, {}, {}" - msg = tpl.format( - source.name, source.file, source.version, source.description, source.comments + msg = ( + f"Ontology source name including whitespace(s); found: {source.name}, " + f"{source.file}, {source.version}, {source.description}, {source.comments}" ) warnings.warn(msg, AdvisoryIsaValidationWarning) @@ -117,71 +113,69 @@ def _validate_investigation_info(self): # (https://isa-specs.readthedocs.io/en/latest/isatab.html#investigation-section) if len(self._investigation.studies) == 1: if any((info.title, info.description, info.submission_date, info.public_release_date)): - tpl = ( - "Investigation with only one study contains metadata:\n\tID:\t{}\n\tTitle:\t" - "{}\n\tPath:\t{}\n\tSubmission Date:\t{}\n\tPublic Release Date:\t{" - "}\n\tPrefer recording metadata in the study section." - ) - msg = tpl.format( - info.identifier, - info.title, - info.path or "", - info.description, - info.submission_date, - info.public_release_date, + msg = ( + "Investigation with only one study contains metadata:\n" + f"\tID:\t{info.identifier}\n\tTitle:\t{info.title}\n" + f"\tPath:\t{info.path or ''}\n\tDescription:\t{info.description}\n" + f"\tSubmission Date:\t{info.submission_date}\n" + f"\tPublic Release Date:\t{info.public_release_date}\n" + "\tPrefer recording metadata in the study section." ) warnings.warn(msg, ModerateIsaValidationWarning) # If more than one study is available, investigation should at least contain an id and title else: # Validate availability of investigation identifier if not info.identifier: - tpl = "Investigation without identifier:\nTitle:\t{}\nPath:\t{}" - msg = tpl.format(info.title, info.path or "") + msg = ( + "Investigation without identifier:\n" + f"Title:\t{info.title}\nPath:\t{info.path or ''}" + ) warnings.warn(msg, ModerateIsaValidationWarning) # Validate availability of investigation title if not info.title: - tpl = "Investigation without title:\nID:\t{}\nPath:\t{}" - msg = tpl.format(info.identifier, info.path or "") + msg = ( + "Investigation without title:\n" + f"ID:\t{info.identifier}\nPath:\t{info.path or ''}" + ) warnings.warn(msg, ModerateIsaValidationWarning) def _validate_studies(self): # Check if any study exists if not self._investigation.studies: - tpl = "No studies declared in investigation: {}" - msg = tpl.format(self._investigation.info.path) + msg = f"No studies declared in investigation: {self._investigation.info.path}" warnings.warn(msg, CriticalIsaValidationWarning) return for study in self._investigation.studies: # Validate availability of minimal study information (ids, paths, titles) if not (study.info.identifier and study.info.path): - tpl = ( + msg = ( "Study with incomplete minimal information (ID and path):" - "\nID:\t{}\nTitle:\t{}\nPath:\t{}" + f"\nID:\t{study.info.identifier}\nTitle:\t{study.info.title}\n" + f"Path:\t{study.info.path or ''}" ) - msg = tpl.format(study.info.identifier, study.info.title, study.info.path or "") warnings.warn(msg, CriticalIsaValidationWarning) if not study.info.title: - tpl = "Study without title:\nID:\t{}\nTitle:\t{}\nPath:\t{}" - msg = tpl.format(study.info.identifier, study.info.title, study.info.path or "") + msg = ( + "Study without title:\n" + f"ID:\t{study.info.identifier}\nTitle:\t{study.info.title}\n" + f"Path:\t{study.info.path or ''}" + ) warnings.warn(msg, ModerateIsaValidationWarning) # Assure distinct studies, i.e. unique ids, paths and preferably titles if study.info.identifier in self._study_ids: - tpl = "Study identifier used more than once: {}" - msg = tpl.format(study.info.identifier) + msg = f"Study identifier used more than once: {study.info.identifier}" warnings.warn(msg, CriticalIsaValidationWarning) else: self._study_ids.add(study.info.identifier) if study.info.path: if study.info.path in self._study_paths: - tpl = "Study path used more than once: {}" - msg = tpl.format(study.info.path or "") + msg = f"Study path used more than once: {study.info.path or ''}" warnings.warn(msg, CriticalIsaValidationWarning) else: self._study_paths.add(study.info.path) if study.info.title: if study.info.title in self._study_titles: - tpl = "Study title used more than once: {}" - msg = tpl.format(study.info.title) + msg = f"Study title used more than once: {study.info.title}" warnings.warn(msg, ModerateIsaValidationWarning) else: self._study_titles.add(study.info.title) @@ -225,8 +219,10 @@ def _validate_factors(self, factors: Dict[str, models.FactorInfo]): def _validate_assays(self, assays: Tuple[models.AssayInfo, ...], study_id: str): # Check if any assays exists (according to specs, having an assays is not mandatory) if not assays: - tpl = "No assays declared in study '{}' of investigation '{}'" - msg = tpl.format(study_id, self._investigation.info.path) + msg = ( + f'No assays declared in study "{study_id}" of ' + f'investigation "{self._investigation.info.path}"' + ) warnings.warn(msg, AdvisoryIsaValidationWarning) return for assay in assays: @@ -243,25 +239,24 @@ def _validate_assays(self, assays: Tuple[models.AssayInfo, ...], study_id: str): else assay.technology_type ) if not (assay.path and meas_type and tech_type): - tpl = ( + msg = ( "Assay with incomplete minimal information (path, measurement and " - "technology type):\nPath:\t{}\nMeasurement Type:\t{}\nTechnology Type:\t{" - "}\nTechnology Platform:\t{}" + f"technology type):\nPath:\t{assay.path or ''}\n" + f"Measurement Type:\t{meas_type}\nTechnology Type:\t{tech_type}\n" + f"Technology Platform:\t{assay.platform}" ) - msg = tpl.format(assay.path or "", meas_type, tech_type, assay.platform) warnings.warn(msg, CriticalIsaValidationWarning) if not assay.platform: - tpl = ( - "Assay without platform:\nPath:\t{}" - "\nMeasurement Type:\t{}\nTechnology Type:\t{}\nTechnology Platform:\t{}" + msg = ( + f"Assay without platform:\nPath:\t{assay.path or ''}" + f"\nMeasurement Type:\t{meas_type}\nTechnology Type:\t{tech_type}\n" + f"Technology Platform:\t{assay.platform}" ) - msg = tpl.format(assay.path or "", meas_type, tech_type, assay.platform) warnings.warn(msg, AdvisoryIsaValidationWarning) # Assure distinct assays, i.e. unique paths if assay.path: if assay.path in self._assay_paths: - tpl = "Assay path used more than once: {}" - msg = tpl.format(assay.path or "") + msg = f"Assay path used more than once: {assay.path or ''}" warnings.warn(msg, CriticalIsaValidationWarning) else: self._assay_paths.add(assay.path) diff --git a/altamisa/isatab/write_assay_study.py b/altamisa/isatab/write_assay_study.py index eb0f8ce..958ea47 100644 --- a/altamisa/isatab/write_assay_study.py +++ b/altamisa/isatab/write_assay_study.py @@ -251,8 +251,7 @@ def _extract_headers(self): # self._headers.append(node.headers) else: # pragma: no cover # TODO: create new headers based on attributes - tpl = "No reference headers available in node {} of first row" - msg = tpl.format(node.unique_name) + msg = f"No reference headers available in node {node.unique_name} of first row" raise WriteIsatabException(msg) def _write_headers(self): @@ -265,18 +264,16 @@ def _extract_and_write_nodes(self): for row in self._ref_table: # Number of nodes per row must match number of header groups if len(row) < len(self._headers): - tpl = ( + msg = ( "Fewer nodes in row than header groups available:" - "\n\tHeader groups:\t{}\n\tRow nodes:\t{}" + f"\n\tHeader groups:\t{self._headers}\n\tRow nodes:\t{row}" ) - msg = tpl.format(self._headers, row) raise WriteIsatabException(msg) elif len(row) > len(self._headers): - tpl = ( + msg = ( "More nodes in row than header groups available:" - "\n\tHeader groups:\t{}\n\tRow nodes:\t{}" + f"\n\tHeader groups:\t{self._headers}\n\tRow nodes:\t{row}" ) - msg = tpl.format(self._headers, row) raise WriteIsatabException(msg) # Iterate nodes and corresponding headers @@ -292,8 +289,7 @@ def _extract_and_write_nodes(self): self._append_attribute(line, attributes, header, node) # Iterating the headers should deplete attributes if len(attributes) > 0: # pragma: no cover - tpl = "Leftover attributes {} found in node {}" - msg = tpl.format(attributes, node.unique_name) + msg = f"Leftover attributes {attributes} found in node {node.unique_name}" raise WriteIsatabException(msg) self._write_next_line(line) @@ -318,8 +314,10 @@ def _append_attribute(self, line, attributes, header, node): line.append(attribute) self._previous_attribute = attribute else: # pragma: no cover - tpl = "Expected '{}' not found in node '{}' after/for attribute '{}'" - msg = tpl.format(header, node.unique_name, self._previous_attribute) + msg = ( + f'Expected {header} not found in node "{node.unique_name}" ' + f'after/for attribute "{self._previous_attribute}"' + ) raise WriteIsatabException(msg) @staticmethod @@ -328,8 +326,10 @@ def _append_attribute_ontology_reference(line, attribute, header, node): if is_ontology_term_ref(attribute): line.extend([attribute.ontology_name or "", attribute.accession or ""]) else: # pragma: no cover - tpl = "Expected {} not found in attribute {} of node {}" - msg = tpl.format(header, attribute, node.unique_name) + msg = ( + f'Expected {header} not found in attribute "{attribute}" of node ' + f'"{node.unique_name}"' + ) raise WriteIsatabException(msg) @staticmethod @@ -382,8 +382,7 @@ def _extract_attributes(self, node) -> dict: elif hasattr(node, "parameter_values"): # is process node attributes = self._extract_process(node) else: # unknown node type # pragma: no cover - tpl = "Node of unexpected type (not material/data nor process): {}" - msg = tpl.format(node) + msg = f"Node of unexpected type (not material/data nor process): {node}" raise WriteIsatabException(msg) return attributes diff --git a/altamisa/isatab/write_investigation.py b/altamisa/isatab/write_investigation.py index 9412d1b..33a60e1 100644 --- a/altamisa/isatab/write_investigation.py +++ b/altamisa/isatab/write_investigation.py @@ -39,8 +39,7 @@ def _extract_section_header(first_entry, section_name): # TODO: check that headers and attributes match return first_entry.headers else: - tpl = "No reference headers available for section {}. Applying default order." - msg = tpl.format(section_name) + msg = f"No reference headers available for section {section_name}. Applying default order." warnings.warn(msg, WriteIsatabWarning) return None @@ -125,7 +124,7 @@ def _write_section( # Add comments to section dict if comments: for key, value in comments.items(): - section["Comment[{}]".format(key)] = value + section[f"Comment[{key}]"] = value # Write the section name self._writer.writerow((section_name,)) # Write the lines in this section. @@ -144,12 +143,10 @@ def _write_section_by_header_order(self, headers, section, section_name): values = section.pop(header) self._write_line(header, values) else: # pragma: no cover - tpl = "No data found for header {} in section {}" - msg = tpl.format(header, section_name) + msg = f"No data found for header {header} in section {section_name}" raise WriteIsatabException(msg) if len(section) > 0: # pragma: no cover - tpl = "Leftover rows found in section {}:\n{}" - msg = tpl.format(section_name, section) + msg = f"Leftover rows found in section {section_name}:\n{section}" raise WriteIsatabException(msg) def _write_ontology_source_reference(self): diff --git a/docs/examples/process_isa_model.py b/docs/examples/process_isa_model.py index d9e31ce..89b3b07 100644 --- a/docs/examples/process_isa_model.py +++ b/docs/examples/process_isa_model.py @@ -34,15 +34,13 @@ for s, study_info in enumerate(investigation.studies): if study_info.info.path: with open(os.path.join(path_in, study_info.info.path), "rt") as inputf: - studies[s] = StudyReader.from_stream("S{}".format(s + 1), inputf).read() + studies[s] = StudyReader.from_stream(f"S{s + 1}", inputf).read() if study_info.assays: assays[s] = {} for a, assay_info in enumerate(study_info.assays): if assay_info.path: with open(os.path.join(path_in, assay_info.path), "rt") as inputf: - assays[s][a] = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() + assays[s][a] = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() # Validate studies and assays for s, study_info in enumerate(investigation.studies): diff --git a/tests/__snapshots__/test_apps.ambr b/tests/__snapshots__/test_apps.ambr index 85ec357..1679e09 100644 --- a/tests/__snapshots__/test_apps.ambr +++ b/tests/__snapshots__/test_apps.ambr @@ -6,7 +6,8 @@ ID: i_minimal Title: Minimal Investigation Path: i_minimal.txt - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', @@ -42,7 +43,8 @@ ID: i_warnings Title: Investigation with Warnings Path: i_warnings.txt - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', diff --git a/tests/__snapshots__/test_parse_study.ambr b/tests/__snapshots__/test_parse_study.ambr index 48135e4..114bba5 100644 --- a/tests/__snapshots__/test_parse_study.ambr +++ b/tests/__snapshots__/test_parse_study.ambr @@ -6,7 +6,8 @@ ID: i_minimal Title: Minimal Investigation Path: i_minimal.txt - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', @@ -26,7 +27,8 @@ ID: i_minimal Title: Minimal Investigation Path: - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', @@ -46,7 +48,8 @@ ID: i_minimal Title: Minimal Investigation Path: - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', @@ -66,7 +69,8 @@ ID: i_small Title: Small Investigation Path: i_small.txt - Submission Date: + Description: + Submission Date: None Public Release Date: None Prefer recording metadata in the study section. ''', diff --git a/tests/test_parse_investigation.py b/tests/test_parse_investigation.py index f5e855d..02f479a 100644 --- a/tests/test_parse_investigation.py +++ b/tests/test_parse_investigation.py @@ -909,7 +909,7 @@ def test_parse_assays_investigation(assays_investigation_file): ) assert record[1].category == ParseIsatabWarning assert str(record[1].message) == msg - msg = "No assays declared in study 's_assays' of investigation 'i_assays.txt'" + msg = 'No assays declared in study "s_assays" of investigation "i_assays.txt"' assert record[2].category == AdvisoryIsaValidationWarning assert str(record[2].message) == msg msg = "Study identifier used more than once: s_assays" diff --git a/tests/test_write_assay.py b/tests/test_write_assay.py index bcfc631..512920d 100644 --- a/tests/test_write_assay.py +++ b/tests/test_write_assay.py @@ -36,12 +36,10 @@ def _parse_write_assert_assay(investigation_file, tmp_path, quote=None, normaliz continue # Load assay if not assay_info.path: - raise ValueError("Assay {} has no path".format(assay_info)) + raise ValueError(f"Assay {assay_info} has no path") path_in = os.path.join(directory, assay_info.path) with open(path_in, "rt") as inputf: - assay = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() + assay = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() AssayValidator(investigation, study_info, assay_info, assay).validate() # Write assay to temporary file path_out = tmp_path / assay_info.path @@ -51,9 +49,7 @@ def _parse_write_assert_assay(investigation_file, tmp_path, quote=None, normaliz # Read and write assay again path_in = path_out with open(path_out, "rt") as inputf: - assay = AssayReader.from_stream( - "S{}".format(s + 1), "A{}".format(a + 1), inputf - ).read() + assay = AssayReader.from_stream(f"S{s + 1}", f"A{a + 1}", inputf).read() AssayValidator(investigation, study_info, assay_info, assay).validate() path_out = tmp_path / (assay_info.path.name + "_b") with open(path_out, "wt", newline="") as file: @@ -184,8 +180,9 @@ def test_assay_writer_gelelect(gelelect_investigation_file, tmp_path): assert str(record[0].message) == msg msg = ( "Investigation with only one study contains metadata:\n\tID:\t1551099271112" - "\n\tTitle:\tInvestigation\n\tPath:\ti_Investigation.txt\n\tSubmission Date:\t\n\tPublic Release" - " Date:\tNone\n\tPrefer recording metadata in the study section." + "\n\tTitle:\tInvestigation\n\tPath:\ti_Investigation.txt\n\tDescription:\t\n" + "\tSubmission Date:\tNone\n\tPublic Release Date:\tNone\n" + "\tPrefer recording metadata in the study section." ) assert record[1].category == ModerateIsaValidationWarning assert str(record[1].message) == msg diff --git a/tests/test_write_study.py b/tests/test_write_study.py index 21d9bbb..ca4215c 100644 --- a/tests/test_write_study.py +++ b/tests/test_write_study.py @@ -31,10 +31,10 @@ def _parse_write_assert(investigation_file, tmp_path, quote=None): for s, study_info in enumerate(investigation.studies): # Load study if not study_info.info.path: - raise ValueError("Study {} has no path".format(study_info)) + raise ValueError(f"Study {study_info} has no path") path_in = os.path.join(directory, study_info.info.path) with open(path_in, "rt") as inputf: - study = StudyReader.from_stream("S{}".format(s + 1), inputf).read() + study = StudyReader.from_stream(f"S{s + 1}", inputf).read() StudyValidator(investigation, study_info, study).validate() # Write study to temporary file path_out = tmp_path / study_info.info.path @@ -88,8 +88,8 @@ def test_study_writer_gelelect(gelelect_investigation_file, tmp_path): assert str(record[0].message) == msg msg = ( "Investigation with only one study contains metadata:\n\tID:\t1551099271112\n\tTitle:\t" - "Investigation\n\tPath:\ti_Investigation.txt\n\tSubmission Date:\t\n\tPublic Release " - "Date:\tNone\n\tPrefer recording metadata in the study section." + "Investigation\n\tPath:\ti_Investigation.txt\n\tDescription:\t\n\tSubmission Date:\tNone\n" + "\tPublic Release Date:\tNone\n\tPrefer recording metadata in the study section." ) assert record[1].category == ModerateIsaValidationWarning assert str(record[1].message) == msg