Skip to content
Back to skills

Python Bio Error Handling

ASecurity

Robust error handling for bioinformatics pipelines — custom exception hierarchy, try/except/else/finally, exception chaining, strict FASTA parsing.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
data-aipythongo

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add Pavel-Kravchenko/Bioinformatics --skill python-bio-error-handling --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Bio Error Handling?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Bio Error Handling
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pavel-kravchenko-python-bio-error-handling/badge)](https://www.skillsdirectory.com/skills/pavel-kravchenko-python-bio-error-handling)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: python-bio-error-handling
description: Robust error handling for bioinformatics pipelines — custom exception hierarchy, try/except/else/finally, exception chaining, strict FASTA parsing.
tool_type: python
primary_tool: Python
---

# Error Handling for Bioinformatics

## Custom Exception Hierarchy

```python
class BioinformaticsError(Exception):
    """Base for all pipeline errors."""

class InvalidSequenceError(BioinformaticsError):
    def __init__(self, invalid_chars, seq_type="DNA"):
        self.invalid_chars = invalid_chars
        super().__init__(f"Invalid {seq_type} characters: {invalid_chars}")

class SequenceLengthError(BioinformaticsError):
    def __init__(self, actual, minimum=None, maximum=None):
        if minimum and actual < minimum:
            msg = f"Sequence too short: {actual} < {minimum}"
        elif maximum and actual > maximum:
            msg = f"Sequence too long: {actual} > {maximum}"
        else:
            msg = f"Invalid sequence length: {actual}"
        super().__init__(msg)

class FastaParseError(BioinformaticsError):
    def __init__(self, message, filename=None, line_number=None, line_content=None):
        parts = [message]
        if filename:    parts.append(f"file={filename}")
        if line_number: parts.append(f"line={line_number}")
        if line_content: parts.append(f"content='{line_content[:50]}'")
        super().__init__(" | ".join(parts))

class TranslationError(BioinformaticsError): pass
```

## try/except/else/finally Pattern

```python
def read_fasta(filename):
    sequences = {}
    try:
        f = open(filename, 'r')
    except FileNotFoundError:
        raise  # let caller handle missing file
    except PermissionError as e:
        raise FastaParseError(f"Cannot read '{filename}'") from e
    else:
        # Only runs if open() succeeded
        for line in f:
            line = line.strip()
            if line.startswith('>'):
                current_id = line[1:].split()[0]
                sequences[current_id] = []
            elif current_id:
                sequences[current_id].append(line)
        sequences = {k: ''.join(v) for k, v in sequences.items()}
    finally:
        f.close()  # always runs
    return sequences
```

## Exception Chaining

```python
def parse_gff_line(line, line_number=None):
    fields = line.strip().split('\t')
    if len(fields) != 9:
        raise FastaParseError(
            f"Expected 9 fields, got {len(fields)}",
            line_number=line_number, line_content=line.strip()
        )
    try:
        return {'start': int(fields[3]), 'end': int(fields[4]), ...}
    except ValueError as e:
        raise FastaParseError(
            "Cannot parse coordinates",
            line_number=line_number, line_content=line.strip()
        ) from e   # preserves original traceback
```

## Strict FASTA Parser

```python
def strict_fasta_parser(filename: str) -> dict[str, str]:
    sequences: dict[str, str] = {}
    current_id: str | None = None
    current_seq: list[str] = []
    VALID = set('ATGCNRYSWKMBDHV.-')

    with open(filename, 'r') as f:
        for line_num, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            if line.startswith('>'):
                if current_id is not None:
                    seq = ''.join(current_seq)
                    if not seq:
                        raise FastaParseError("Empty sequence", filename, line_num, f">{current_id}")
                    sequences[current_id] = seq
                if len(line) == 1:
                    raise FastaParseError("Empty sequence ID", filename, line_num, line)
                current_id = line[1:].split()[0]
                current_seq = []
            else:
                if current_id is None:
                    raise FastaParseError("Sequence data before first header", filename, line_num, line)
                invalid = set(line.upper()) - VALID
                if invalid:
                    raise FastaParseError(f"Invalid characters: {invalid}", filename, line_num, line)
                current_seq.append(line.upper())

    if current_id is not None:
        seq = ''.join(current_seq)
        if not seq:
            raise FastaParseError("Empty sequence for last record", filename)
        sequences[current_id] = seq

    return sequences
```

## Batch Processing with Graceful Degradation

```python
def batch_gc_analysis(sequences: dict[str, str], strict: bool = False):
    results, errors = {}, []
    for seq_id, seq in sequences.items():
        try:
            if not seq:
                raise SequenceLengthError(0, minimum=1)
            invalid = set(seq.upper()) - set('ATGCN')
            if invalid:
                raise InvalidSequenceError(invalid)
            s = seq.upper()
            results[seq_id] = round((s.count('G') + s.count('C')) / len(s) * 100, 2)
        except BioinformaticsError as e:
            if strict:
                raise
            errors.append((seq_id, str(e)))
    return results, errors
```

## Pitfalls

- **Bare `except:`**: catches `KeyboardInterrupt` and `SystemExit` — impossible to stop the script with Ctrl+C. Always name the exception.
- **`assert` for input validation**: disabled with `python -O`. Use `if not condition: raise ValueError(...)` instead.
- **`raise NewError(...) from original`**: omitting `from original` hides the root cause in tracebacks.
- **`else` on `try`**: runs only when *no* exception occurred — separates "might fail" from "should only run on success". Commonly forgotten.
- **`finally` runs even on `return`**: useful for cleanup (file close, temp removal), but mutating state in `finally` can mask real errors.
- **Catching `BioinformaticsError` too broadly**: catch the most specific subclass first; put the base class catch last as a fallback.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.