agentsclimarketplace

Python advanced sql

Skill Pavel-Kravchenko/Bioinformatics/Skills/python-advanced-sql

Write Python decorators/context managers/dataclasses and query gene/variant tables with sqlite3/pandas SQL (JOIN, GROUP BY, HAVING). Use for retry/caching/validation wrappers or SQL against Ensembl/UCSC-style schemas.From its SKILL.md

Install
npx -y skills add Pavel-Kravchenko/Bioinformatics --skill python-advanced-sql

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.3 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it

Advanced Python & SQL for Bioinformatics

When to Use

  • Modeling sequences, genes, variants, or alignments as Python classes (dunders, @dataclass)
  • Adding caching, timing, input validation, or retry logic to pipeline functions via decorators
  • Safely handling files, DB connections, or temp files with context managers (with / contextlib)
  • Querying biological databases (SQLite, Ensembl MySQL dumps, UCSC tables) with SQL joins/aggregation
  • Joining gene/variant/expression tables to answer "which genes are X and Y" questions

Version Compatibility

  • Python >= 3.10 (uses dataclasses, functools, PEP 604 type hints)
  • sqlite3 — stdlib, ships with Python (no install needed)
  • pandas >= 2.0 (for pd.read_sql_query)

Prerequisites

  • pip install pandas
  • Comfortable with plain functions and basic classes
  • For fetching real Ensembl/UCSC/NCBI data before loading it into SQLite, see bio-database-access-entrez-fetch or bio-database-access-batch-downloads

Quick Reference

OOP Dunders

MethodPurpose
__init__Constructor
__str__ / __repr__User / debug string
__len__len(obj)
__eq__, __lt__Comparison / sorting
__contains__"ATG" in seq syntax
__enter__ / __exit__Context manager

SQL Clauses

ClauseUse
WHERE biotype = 'protein_coding'Filter rows
GROUP BY tissue, conditionAggregate groups
HAVING AVG(tpm) > 50Filter after grouping
INNER JOINMatching rows only
LEFT JOINAll left rows, NULLs for no match
Subquery with IN (SELECT ...)Multi-condition filter

Key Patterns

Decorators for pipeline functions

Goal: add cross-cutting behavior (timing, memoization, input validation, retry-on-failure) to pipeline functions without rewriting each one.

Approach: write a decorator factory that wraps the target function, always use @functools.wraps to preserve __name__/__doc__, and stack decorators bottom-up (the one closest to def runs first).

import functools
import time


def timer(func):
    """Print the wall-clock time a function call took."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"[timer] {func.__name__}: {time.perf_counter() - t0:.4f}s")
        return result
    return wrapper


def memoize(func):
    """Cache results by argument tuple; only safe for hashable args."""
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    wrapper.cache = cache
    return wrapper


def validate_sequence(valid_chars: str, seq_type: str = "DNA"):
    """Decorator factory: reject sequences with characters outside valid_chars."""
    valid_set = set(valid_chars.upper())
    def decorator(func):
        @functools.wraps(func)
        def wrapper(seq, *args, **kwargs):
            invalid = set(seq.upper()) - valid_set
            if invalid:
                raise ValueError(f"Invalid {seq_type} characters {invalid} in {func.__name__}()")
            return func(seq, *args, **kwargs)
        return wrapper
    return decorator


def retry(max_attempts: int = 3, delay: float = 0.5):
    """Decorator factory: retry a flaky call (e.g. a network fetch) with backoff."""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_err = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_err = e
                    if attempt < max_attempts:
                        time.sleep(delay)
            raise last_err
        return wrapper
    return decorator


# Stacking: applied bottom-up -- validate_sequence runs first, timer wraps around it
@timer
@validate_sequence('ATGC', seq_type='DNA')
def gc_content(seq: str) -> float:
    """Return GC percentage of a validated DNA sequence."""
    seq = seq.upper()
    return (seq.count('G') + seq.count('C')) / len(seq) * 100

Context managers for safe resource handling

Goal: guarantee files, DB connections, and temp files are closed/removed even when an exception is raised mid-pipeline.

Approach: implement __enter__/__exit__ for stateful resources, or use @contextlib.contextmanager for simple one-shot setup/teardown.

import os
import tempfile
from contextlib import contextmanager


class FastaWriter:
    """Class-based context manager: opens a FASTA file, wraps sequences at line_width."""

    def __init__(self, filename: str, line_width: int = 80):
        self.filename, self.line_width = filename, line_width
        self.file = None

    def __enter__(self):
        self.file = open(self.filename, 'w')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False  # never suppress exceptions

    def write_record(self, seq_id: str, seq: str, desc: str = ""):
        header = f">{seq_id}" + (f" {desc}" if desc else "")
        self.file.write(header + "\n")
        for i in range(0, len(seq), self.line_width):
            self.file.write(seq[i:i + self.line_width] + "\n")


@contextmanager
def temp_fasta(sequences: dict[str, str]):
    """Function-based context manager: write sequences to a temp FASTA, delete on exit."""
    fd, path = tempfile.mkstemp(suffix='.fasta')
    try:
        with os.fdopen(fd, 'w') as f:
            for sid, seq in sequences.items():
                f.write(f">{sid}\n{seq}\n")
        yield path
    finally:
        os.unlink(path)

GeneAnnotation dataclass

Goal: model a genomic interval (gene/feature) as a comparable, sortable object without boilerplate __init__/__eq__/__lt__.

Approach: use @dataclass(order=True) and mark non-key fields compare=False so sorting/equality is based only on genomic position.

from dataclasses import dataclass, field


@dataclass(order=True)
class GeneAnnotation:
    """A genomic feature comparable/sortable by (chromosome, start, end)."""
    chromosome: str
    start: int
    end: int
    name: str = field(compare=False, default="")
    strand: str = field(compare=False, default='+')
    gene_type: str = field(compare=False, default="protein_coding")

    @property
    def length(self) -> int:
        return self.end - self.start

    def overlaps(self, other: "GeneAnnotation") -> bool:
        return (self.chromosome == other.chromosome
                and self.start < other.end
                and other.start < self.end)

SQL — schema, seed data, and bio queries

Goal: load gene/variant/expression tables into SQLite and answer real bio questions with joins and aggregation.

Approach: build the schema with executescript, load rows with executemany (never string-format values into SQL), then query with pd.read_sql_query for tabular results.

import sqlite3
import pandas as pd


def build_demo_db() -> sqlite3.Connection:
    """Create an in-memory SQLite DB with genes/variants/expression tables."""
    conn = sqlite3.connect(':memory:')
    conn.executescript('''
        CREATE TABLE genes (
            gene_id INTEGER PRIMARY KEY, symbol TEXT, chromosome TEXT,
            start_pos INTEGER, end_pos INTEGER, strand TEXT, biotype TEXT
        );
        CREATE TABLE expression (
            expr_id INTEGER PRIMARY KEY, gene_id INTEGER REFERENCES genes(gene_id),
            sample_id TEXT, tissue TEXT, tpm REAL, condition TEXT
        );
        CREATE TABLE variants (
            variant_id INTEGER PRIMARY KEY, gene_id INTEGER REFERENCES genes(gene_id),
            position INTEGER, ref_allele TEXT, alt_allele TEXT, clinical_significance TEXT
        );
    ''')
    return conn


def genes_with_pathogenic_and_high_tumor_expression(conn: sqlite3.Connection) -> pd.DataFrame:
    """Genes highly expressed in tumor samples (avg TPM > 50) AND carrying a pathogenic variant."""
    return pd.read_sql_query("""
        SELECT symbol FROM genes
        WHERE gene_id IN (
            SELECT gene_id FROM expression WHERE condition = 'tumor'
            GROUP BY gene_id HAVING AVG(tpm) > 50
        ) AND gene_id IN (
            SELECT gene_id FROM variants WHERE clinical_significance = 'pathogenic'
        )
    """, conn)


def variants_for_gene(conn: sqlite3.Connection, symbol: str) -> pd.DataFrame:
    """Look up variants for a gene using a parameterized query (safe against SQL injection)."""
    return pd.read_sql_query(
        """SELECT v.* FROM variants v JOIN genes g ON g.gene_id = v.gene_id
           WHERE g.symbol = ?""",
        conn, params=(symbol,),
    )


# Other common bio queries against the same schema:
# pd.read_sql_query("SELECT symbol, (end_pos-start_pos) AS length FROM genes "
#                    "WHERE (end_pos-start_pos) > 100000 ORDER BY length DESC", conn)
# pd.read_sql_query("SELECT tissue, condition, ROUND(AVG(tpm),2) AS avg_tpm, COUNT(*) AS n "
#                    "FROM expression GROUP BY tissue, condition", conn)
# pd.read_sql_query("SELECT g.symbol, COUNT(v.variant_id) AS n_variants FROM genes g "
#                    "LEFT JOIN variants v ON g.gene_id = v.gene_id "
#                    "GROUP BY g.symbol ORDER BY n_variants DESC", conn)

Pitfalls

  • Missing @functools.wraps: decorated function loses __name__ and __doc__, breaking introspection and logging.
  • Bare except:: catches SystemExit and KeyboardInterrupt; always catch specific exception types.
  • __exit__ returning True: suppresses all exceptions silently — only do this intentionally.
  • @lru_cache on instance methods: caches self, leaking instances and preventing garbage collection; use on module-level or static functions only.
  • Stacking decorators: applied bottom-up — @timer above @validate_sequence means validation runs first, timer measures the whole stack.
  • SQL HAVING vs WHERE: WHERE filters rows before grouping; HAVING filters after aggregation — using WHERE AVG(tpm) > 50 is a syntax error.
  • LEFT JOIN counts: use COUNT(v.variant_id) (a column), not COUNT(*), so genes with zero matches count as 0, not 1.
  • String-formatting values into SQL: f"WHERE symbol = '{symbol}'" is a SQL-injection and quoting-bug risk — always use parameterized queries (? placeholders + params=).
  • raise ... from e: preserves the original traceback; omitting from e inside an except block hides the root cause.
  • Properties without a _-prefixed backing attribute: self.sequence = value inside a sequence setter recurses infinitely; store to self._sequence.

See Also

  • bio-database-access-entrez-fetch — pull real gene/variant records before loading them into these tables
  • bio-expression-matrix-counts-ingest — load real RNA-seq count matrices instead of the toy expression table
  • bio-variant-calling-vcf-basics — parse real VCF records into a variants-style table
  • polars — a faster DataFrame alternative to pandas for the same SQL-style joins/aggregations

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.