Skip to content

Formato iris nativo

Adaptador bidireccional que lee y escribe VariantAnnotation en TSV sin compresión. La virtud del formato es que se escribe con el mismo mapa de columnas que se lee: un único archivo TOML define el layout en ambas direcciones.

Sintaxis de rutas

Cada columna en el TOML se mapea mediante una expresión de ruta sobre VariantAnnotation. Hay tres tipos de paso:

Paso Sintaxis Comportamiento
Atributo field.subfield getattr encadenado
Índice field.0 indexa la tupla
Filtro field[key=value].subfield primer elemento donde element.key == value

Ejemplos de rutas

variant_id   = "variant.cpra"
chromosome   = "variant.position.chromosome"
genes        = "gene_symbols"                              # tuple → separado por ";"
clinsig      = "top_classification.classification.name"   # propiedad computada
mcps_af      = "allele_frequencies[population=mcps].frequency"
lof          = "scores[name=LoF].prediction.name"

Las propiedades computadas de VariantAnnotation (top_classification, most_severe_consequence, gene_symbols, …) funcionan en escritura pero son ignoradas en lectura — sus valores se derivan de los campos primarios al reconstruir el objeto.

Archivo de configuración TOML

El mapping por defecto está en iris/iris.toml (junto al paquete). Se puede pasar una ruta personalizada para subconjuntos o renombrados de columnas sin tocar código:

from iris.adapters.driven.iris import IrisWriter, IrisReader

writer = IrisWriter(config=Path("mi_panel.toml"))
reader = IrisReader(config=Path("mi_panel.toml"))

Estructura del TOML:

[meta]
version      = "1"
entity       = "VariantAnnotation"
mv_separator = ";"        # separador para campos multi-valor (genes, etc.)

[columns]
variant_id = "variant.cpra"
# ... una entrada por columna

Writer

iris.adapters.driven.iris.writer

IrisWriter — exports VariantAnnotation objects to the iris native TSV format.

IrisWriter

Exports VariantAnnotation objects to the iris native TSV format.

Column layout is fully driven by a TOML config file (default: iris.toml in this package). Each entry maps a column name to a dot-path expression on VariantAnnotation; see _resolver.py for the path syntax.

A custom config can target any subset of fields or rename columns without touching code.

Example
from iris.adapters.driven.iris.writer import IrisWriter

writer = IrisWriter()
with open("variants.iris.tsv", "w") as fh:
    writer.write(variant_annotations, fh)

write

write(variants, handle)

Write variants to handle in iris native TSV format.

Source code in src/iris/adapters/driven/iris/writer.py
def write(self, variants: Sequence[VariantAnnotation], handle: TextIO) -> None:
    """Write *variants* to *handle* in iris native TSV format."""
    cfg = _load(self.config)
    sep: str = cfg.get("meta", {}).get("mv_separator", ";")
    columns: dict[str, str] = cfg.get("columns", {})

    if not columns:
        return

    writer = csv.writer(handle, delimiter="\t", lineterminator="\n")
    writer.writerow(list(columns.keys()))
    for va in variants:
        writer.writerow([resolve(va, path, mv_separator=sep) for path in columns.values()])

Reader

iris.adapters.driven.iris.reader

IrisReader — reconstructs VariantAnnotation objects from the iris native TSV format.

IrisReader

Reconstructs VariantAnnotation objects from the iris native TSV format.

Column layout is driven by the same TOML config used by :class:IrisWriter. The reader builds a reconstruction plan from the config at first call and reuses it for every row.

Rows that cannot produce a valid :class:~iris.domain.genomics.entities.Variant are skipped with a WARNING log entry.

Example
from iris.adapters.driven.iris.reader import IrisReader

reader = IrisReader()
with open("variants.iris.tsv") as fh:
    variants = reader.read(fh)

read

read(handle)

Read TSV rows from handle and return reconstructed VariantAnnotation objects.

Source code in src/iris/adapters/driven/iris/reader.py
def read(self, handle: TextIO) -> list[VariantAnnotation]:
    """Read TSV rows from *handle* and return reconstructed VariantAnnotation objects."""
    cfg = _load(self.config)
    sep: str = cfg.get("meta", {}).get("mv_separator", ";")
    columns: dict[str, str] = cfg.get("columns", {})

    plan = _build_plan(columns)

    reader = csv.DictReader(handle, delimiter="\t")
    results: list[VariantAnnotation] = []
    for lineno, row in enumerate(reader, start=2):
        va = _reconstruct(row, plan, sep)
        if va is None:
            logger.warning("iris reader: skipped row %d", lineno)
            continue
        results.append(va)
    return results

Resolver

iris.adapters.driven.iris._resolver

Dot-path resolver for the iris native format.

Syntax

path ::= step ("." step)* step ::= filter_step | index_step | attr_step filter_step ::= name "[" key "=" value "]" index_step ::= integer attr_step ::= identifier

Examples:


"variant.cpra" "variant.position.chromosome" "allele_frequencies[population=mcps].frequency" "scores[name=LoF].prediction.name" "gene_symbols" # tuple → joined with mv_separator

PathInfo

Bases: NamedTuple

Structural metadata extracted from a single column path.

kind instance-attribute

kind

One of: 'variant_cpra', 'variant_field', 'gene_symbols', 'collection', 'readonly'.

collection_attr class-attribute instance-attribute

collection_attr = ''

Attribute name on VariantAnnotation (e.g. 'allele_frequencies').

filter_key class-attribute instance-attribute

filter_key = ''

Field used to identify the collection item (e.g. 'population').

filter_val class-attribute instance-attribute

filter_val = ''

Value of filter_key that identifies this specific item (e.g. 'mcps').

item_path class-attribute instance-attribute

item_path = ''

Remaining path on the collection item (e.g. 'frequency').

resolve

resolve(obj, path, *, mv_separator=';')

Walk path on obj and return the result as a TSV-safe string.

Rules for the final value: - None"" - tuple / list → elements joined with mv_separator - Enumenum.name - anything else → str(value)

Source code in src/iris/adapters/driven/iris/_resolver.py
def resolve(obj: Any, path: str, *, mv_separator: str = ";") -> str:
    """Walk *path* on *obj* and return the result as a TSV-safe string.

    Rules for the final value:
    - ``None``               → ``""``
    - ``tuple`` / ``list``   → elements joined with *mv_separator*
    - ``Enum``               → ``enum.name``
    - anything else          → ``str(value)``
    """
    for step in _parse(path):
        if obj is None:
            return ""
        if step.kind == "filter":
            collection = getattr(obj, step.name, None) or ()
            obj = next(
                (x for x in collection if str(getattr(x, step.fkey, "")) == step.fval), None
            )
        elif step.kind == "index":
            try:
                obj = obj[int(step.name)]
            except (IndexError, TypeError):
                return ""
        else:
            obj = getattr(obj, step.name, None)

    if obj is None:
        return ""
    if isinstance(obj, (tuple, list, frozenset)):
        return mv_separator.join(str(x) for x in obj if x is not None)
    if isinstance(obj, Enum):
        return obj.name
    return str(obj)

classify

classify(path)

Return structural metadata for a column path.

Source code in src/iris/adapters/driven/iris/_resolver.py
def classify(path: str) -> PathInfo:
    """Return structural metadata for a column path."""
    steps = _parse(path)
    root = steps[0]

    # gene_symbols: tuple[str, ...] → join on write, split on read
    if path == "gene_symbols":
        return PathInfo("gene_symbols")

    # variant.cpra → reconstruct the whole Variant from the CPRA string
    if root.name == "variant" and len(steps) >= 2 and steps[1].name == "cpra":
        return PathInfo("variant_cpra")

    # other variant.* fields
    if root.name == "variant":
        return PathInfo("variant_field", item_path=".".join(s.name for s in steps[1:]))

    # collection[key=val].rest
    if root.kind == "filter":
        item_path = ".".join(
            (f"{s.name}[{s.fkey}={s.fval}]" if s.kind == "filter" else s.name) for s in steps[1:]
        )
        return PathInfo(
            "collection",
            collection_attr=root.name,
            filter_key=root.fkey,
            filter_val=root.fval,
            item_path=item_path,
        )

    # top_classification.* → treat as single classification (no source filter)
    if root.name == "top_classification":
        item_path = ".".join(s.name for s in steps[1:])
        return PathInfo(
            "collection",
            collection_attr="classifications",
            filter_key="",
            filter_val="",
            item_path=item_path,
        )

    return PathInfo("readonly")