Skip to content

API Reference

All public classes are importable directly from the top-level package:

from samplesheet_parser import (
    SampleSheetFactory,
    SampleSheetParser,
    SampleSheetV1,
    SampleSheetV2,
    SampleSheetConverter,
    SampleSheetValidator,
    SampleSheetDiff,
    SampleSheetWriter,
    SampleSheetMerger,
    SampleSheetSplitter,
    SampleSheetFilter,
    ElementRunManifest,
    Chemistry,
    ColorBalanceMode,
    chemistry_for_instrument,
    analyze_color_balance,
    ColorBalanceReport,
    normalize_index_lengths,
    hamming_distance,
)

SampleSheetFactory

Method / attribute Returns Description
create_parser(path, *, clean, experiment_id, parse) SampleSheetParser Auto-detect format and return the appropriate parser
get_umi_length() int UMI length from the current parser
register(detector, parser_class, version) None Register a custom format detector; tried before built-in detection in LIFO order
clear_registry() None Remove all custom registrations (useful in tests)
.version SampleSheetVersion \| None Detected format version

SampleSheetParser Protocol

SampleSheetParser is a runtime_checkable structural protocol satisfied by SampleSheetV1, SampleSheetV2, and the non-Illumina ElementRunManifest. Use it as a type hint wherever any parser is accepted, or implement it in a third-party parser and register it with the factory.

from samplesheet_parser import SampleSheetParser

isinstance(sheet, SampleSheetParser)   # True for V1, V2, and AVITI instances

SampleSheetV1 / SampleSheetV2 (shared interface)

Both parsers satisfy SampleSheetParser and expose:

Method / attribute Returns Description
parse(do_clean=True) None Parse all sections
clean() str Return cleaned content as a string — source file is never modified
samples() list[dict] One record per unique (sample_id, lane) pair
index_type() str "dual", "single", or "none"
parse_custom_section(name, *, required=False) dict[str, str] Parse any non-standard section as key/value pairs
.adapters list[str] Adapter sequences
.experiment_name str \| None Run/experiment name

V2-only

Method Returns Description
get_umi_length() int UMI length from OverrideCycles
get_read_structure() ReadStructure Parsed read structure dataclass

SampleSheetConverter

SampleSheetConverter(path, *, workflow: Workflow | str | None = None)
Method / attribute Returns Description
to_v2(output_path) Path Convert IEM V1 → BCLConvert V2
to_v1(output_path) Path Convert BCLConvert V2 → IEM V1 (lossy)
.source_version SampleSheetVersion \| None Auto-detected format of the input
.workflow_override Workflow \| None Resolved workflow override, if any

The workflow parameter accepts "a", "b", or a Workflow enum value and overrides auto-detection of the i5 orientation workflow from the instrument header. See Conversion → Index 2 orientation.


samplesheet_parser.instruments

i5 orientation workflow classification helpers.

from samplesheet_parser.instruments import (
    Workflow,
    detect_workflow,
    parse_workflow,
    reverse_complement,
    WORKFLOW_A_INSTRUMENTS,
    WORKFLOW_B_INSTRUMENTS,
    AMBIGUOUS_INSTRUMENTS,
)
Name Kind Description
Workflow str, Enum Workflow.A (i5 forward) / Workflow.B (i5 RC'd on chip)
detect_workflow(name) Workflow \| None Classify an instrument name; None for unknown or ambiguous (e.g. NovaSeq 6000)
parse_workflow(value) Workflow \| None Coerce a CLI string ("a" / "b") to Workflow
reverse_complement(seq) str Reverse-complement a DNA sequence (preserves N, case-preserving)
WORKFLOW_A_INSTRUMENTS frozenset[str] Normalised names of workflow-A instruments
WORKFLOW_B_INSTRUMENTS frozenset[str] Normalised names of workflow-B instruments
AMBIGUOUS_INSTRUMENTS frozenset[str] Instruments whose workflow depends on chemistry and require an explicit override

SampleSheetValidator

Method Returns Description
validate(sheet, *, min_hamming_distance=3, check_color_balance=False, instrument=None, color_balance_mode="vendor_faithful", min_signal_fraction=0.1) ValidationResult Run all checks; returns structured result

Color-balance checking is opt-in via check_color_balance=True. The instrument is read from the sheet header when present, or supplied with instrument=; the check is skipped silently for unknown instruments. color_balance_mode is "vendor_faithful" (default) or "conservative" (see ColorBalanceMode). It emits COLOR_BALANCE_NO_SIGNAL (error), COLOR_BALANCE_LOW (warning), and COLOR_BALANCE_ADVISORY (warning, AVITI) issues — see Validation → Color-balance checking.

ValidationResult

Attribute / method Type Description
is_valid bool False if any errors present
errors list[ValidationIssue] Structured error records
warnings list[ValidationIssue] Structured warning records
summary() str One-line human-readable summary

ValidationIssue

Attribute Type Description
code str e.g. "DUPLICATE_INDEX"
message str Human-readable description
context dict Relevant sample IDs, lane, etc.

SampleSheetDiff

Method Returns Description
compare() DiffResult Full comparison across header, reads, settings, and samples

DiffResult

Attribute / method Type Description
has_changes bool True if any difference detected
summary() str Human-readable one-paragraph summary
header_changes list[HeaderChange] Header, reads, and settings diffs
samples_added list[dict] Records present in new sheet only
samples_removed list[dict] Records present in old sheet only
sample_changes list[SampleChange] Per-sample field-level diffs
source_version SampleSheetVersion Format of the old sheet
target_version SampleSheetVersion Format of the new sheet

SampleSheetWriter

Method / attribute Returns Description
SampleSheetWriter(version=) Instantiate for SampleSheetVersion.V1 or .V2
from_sheet(sheet, version=) SampleSheetWriter Load a parsed sheet for editing; optionally change format
set_header(*, run_name, platform, ...) self Set header fields (fluent)
set_reads(*, read1, read2, index1, index2) self Set read cycle counts (fluent)
set_adapter(adapter_read1, adapter_read2) self Set adapter sequences (fluent)
set_override_cycles(override) self Set OverrideCycles — V2 only (fluent)
set_software_version(version) self Set SoftwareVersion — V2 only (fluent)
set_setting(key, value) self Set an arbitrary settings key/value (fluent)
add_sample(sample_id, *, index, ...) self Append a sample row (fluent)
remove_sample(sample_id, *, lane=) self Remove sample(s) by ID, optionally scoped to a lane (fluent)
update_sample(sample_id, *, lane=, **fields) self Update fields on an existing sample in-place (fluent)
clear_samples() self Remove all samples while preserving header/reads/settings (fluent)
write(path, *, validate=True) Path Serialise to disk; validates first by default
to_string() str Serialise to string without writing to disk
.sample_count int Number of samples currently in the writer
.sample_ids list[str] Sample IDs currently in the writer

SampleSheetMerger

Method / attribute Returns Description
SampleSheetMerger(target_version=, min_hamming_distance=3) Instantiate with target format and optional Hamming threshold
add(path) self Register an input sheet path (fluent)
merge(output_path, *, validate=True, abort_on_conflicts=True) MergeResult Run the merge and write output

MergeResult

Attribute / method Type Description
has_conflicts bool True if any conflict recorded
sample_count int Samples in the merged output
output_path Path \| None Path written; None if write was aborted
source_versions dict[str, str] Per-input-file detected version
conflicts list[MergeConflict] Structured conflict records
warnings list[MergeConflict] Structured warning records
summary() str One-line human-readable summary

SampleSheetSplitter

Method / attribute Returns Description
SampleSheetSplitter(path, *, by="project", target_version=None, unassigned_label="unassigned") Instantiate with input path and grouping strategy
split(output_dir, *, prefix="", suffix="_SampleSheet.csv", validate=True) SplitResult Parse input and write one file per group

SplitResult

Attribute / method Type Description
output_files dict[str, Path] Group key → path of the written file
sample_counts dict[str, int] Group key → number of samples written
warnings list[str] Non-fatal issues (incomplete records, unassigned samples)
source_version str "V1" or "V2"
summary() str One-line human-readable summary

SampleSheetFilter

Method / attribute Returns Description
SampleSheetFilter(path, *, target_version=None) Instantiate with input path
filter(output_path, *, project=None, lane=None, sample_id=None, validate=True) FilterResult Write filtered copy to output_path; at least one criterion required

sample_id supports glob patterns (e.g. "CTRL_*") via fnmatch.fnmatchcase — matching is always case-sensitive.

FilterResult

Attribute / method Type Description
matched_count int Samples that passed all filter criteria
total_count int Total samples in the input sheet
output_path Path \| None Path written; None when no samples matched
source_version str "V1" or "V2"
summary() str One-line human-readable summary

ElementRunManifest

Non-Illumina parser for Element Biosciences AVITI RunManifest.csv files. It satisfies the SampleSheetParser protocol, so it works with the validator, diff, splitter, and filter just like the Illumina parsers. The factory auto-detects manifests, so you rarely instantiate this directly.

from samplesheet_parser import ElementRunManifest

sheet = ElementRunManifest("RunManifest.csv")
sheet.parse()
Method / attribute Returns Description
parse(do_clean=True) None Parse [RUNVALUES] / [SETTINGS] / [SAMPLES] sections
samples() list[dict] Manifest rows mapped to the shared schema (sample_id, index, index2, lane, sample_project)
index_type() str "dual", "single", or "none"
parse_custom_section(name, *, required=False) dict[str, str] Parse any manifest section as key/value pairs
.adapters list[str] Adapter sequences from [SETTINGS]
is_manifest(path) bool Static detector used by the factory ([SAMPLES] + [RUNVALUES]/SampleName)

Manifest columns map as SampleName → sample_id, Index1 → index, Index2 → index2, Lane → lane, Project → sample_project; other columns (e.g. ExternalID) are preserved as passthrough fields.


samplesheet_parser.chemistry

Per-cycle color-balance analysis against an instrument's optical detection chemistry.

from samplesheet_parser import (
    Chemistry,
    ColorBalanceMode,
    chemistry_for_instrument,
    analyze_color_balance,
    ColorBalanceReport,
)
Name Kind Description
Chemistry str, Enum ONE_CHANNEL / TWO_CHANNEL / FOUR_CHANNEL (Illumina two-laser) / AVIDITY (Element AVITI)
ColorBalanceMode str, Enum VENDOR_FAITHFUL (default, published rule) / CONSERVATIVE (stricter)
chemistry_for_instrument(name) Chemistry \| None Resolve an instrument name to its chemistry; None if unknown. "AVITI"AVIDITY
analyze_color_balance(index1, index2=None, *, chemistry, mode="vendor_faithful", min_signal_fraction=0.1) ColorBalanceReport Score index pools cycle-by-cycle

Rules by chemistry: 2-/1-channel — all-G cycle fails (both modes); a single-channel cycle fails only in conservative (Illumina's minimum is "at least one channel"). 4-channel — a cycle missing the green {G,T} or red {A,C} laser fails (both modes). avidity — low diversity is an advisory in vendor_faithful, a failure in conservative.

ColorBalanceReport

Attribute / method Type Description
chemistry Chemistry Chemistry the pool was scored against
mode ColorBalanceMode Mode the pool was scored under
pool_size int Number of indexes analysed
cycles list[CycleBalance] Per-cycle signal breakdown
dark_cycles list[CycleBalance] Failing cycles (no signal / laser absent / conservative escalations)
weak_cycles list[CycleBalance] Non-failing warnings (single-channel or below-threshold)
advisory_cycles list[CycleBalance] AVITI low-diversity advisories (vendor_faithful)
is_balanced bool True when there are no dark (failing) cycles
chem = chemistry_for_instrument("NovaSeq X")          # Chemistry.TWO_CHANNEL
report = analyze_color_balance(["ATGC", "CAGT", "TCGA", "GATG"], chemistry=chem)
for cb in report.dark_cycles:
    print(cb.read, cb.cycle, cb.base_counts)

normalize_index_lengths

normalize_index_lengths(
    samples: list[dict],
    strategy: str,                  # "trim" or "pad"
    index1_key: str | None = None,  # auto-detected if None
    index2_key: str | None = None,  # auto-detected if None
) -> list[dict]

Normalizes index sequence lengths across a list of sample dicts. See Index Utilities for details.


hamming_distance

from samplesheet_parser import hamming_distance

Computes the Hamming distance between two equal-length index sequences, counting N wildcards as matches:

hamming_distance("ATTACTCG", "ATTACTCG")   # 0
hamming_distance("ATTACTCG", "ATTACTCC")   # 1
hamming_distance("ATTNCNCG", "ATTACTCG")   # 0  — N matches anything

Used internally by SampleSheetValidator when checking index collision thresholds. Accepts sequences of different lengths (returns None) but in practice all samples in a valid sheet have uniform index lengths after normalization.


Enums

from samplesheet_parser.enums import SampleSheetVersion, InstrumentPlatform, UMILocation

SampleSheetVersion.V1              # IEM / bcl2fastq
SampleSheetVersion.V2              # BCLConvert
SampleSheetVersion.ELEMENT_AVITI   # Element AVITI RunManifest (non-Illumina)