---
name: k-dense-ai/pysam
source: https://app.decimal.ai/s/k-dense-ai-pysam@1/SKILL.md
source_sha256: 987a83a22d7b
---

# pysam

## Overview

Use pysam for low-level, streaming access to HTSlib-supported genomic formats:

- `AlignmentFile` and `AlignedSegment` for SAM/BAM/CRAM
- `VariantFile`, `VariantHeader`, and `VariantRecord` for VCF/BCF
- `FastaFile` for indexed FASTA and `FastxFile` for sequential FASTA/FASTQ
- `TabixFile` for BGZF-compressed, tabix-indexed BED/GFF/GTF/custom tables
- `pysam.samtools` and `pysam.bcftools` for wrapped command dispatchers

Current upstream baseline: **pysam 0.24.0** (27 April 2026), wrapping
HTSlib/samtools/bcftools 1.23.1. Read `references/sources.md` before updating
version-specific guidance.

## Installation

Use the pinned release for reproducible work:

```bash
uv pip install "pysam==0.24.0"
```

Confirm the runtime:

```python
import pysam

print(pysam.__version__)           # 0.24.0
print(pysam.__samtools_version__)  # 1.23.1
```

Prebuilt wheels are available for supported macOS and Linux platforms. A
source build needs a C compiler and HTSlib build dependencies; read the
official installation guide linked from `references/sources.md`.

## First Decide

Before writing code:

1. Identify the real format, compression, sort order, and available index.
2. Decide whether coordinates are numeric Python coordinates or a region
   string. Do not mix them.
3. For CRAM, identify the exact reference assembly and FASTA.
4. Prefer indexed region access; use sequential iteration only when intended.
5. Preserve headers when writing and write to a new path by default.
6. State filtering semantics: mapping/base quality, flags, overlap handling,
   duplicate handling, and pileup depth cap.

For unfamiliar files, start with the bundled read-only inspector:

```bash
python scripts/inspect_hts.py sample.bam
python scripts/inspect_hts.py cohort.vcf.gz
python scripts/inspect_hts.py reference.fa
```

## Bundled Scripts

| Script | Purpose | Typical call |
|---|---|---|
| `scripts/inspect_hts.py` | Metadata-only inspection for alignment, variant, FASTA, FASTQ, and tabix files | `python scripts/inspect_hts.py sample.cram --reference ref.fa` |
| `scripts/alignment_qc.py` | Streaming aggregate read/QC counts as JSON | `python scripts/alignment_qc.py sample.bam --max-records 100000` |
| `scripts/variant_summary.py` | Streaming variant, FILTER, and genotype summary as JSON | `python scripts/variant_summary.py cohort.vcf.gz --region chr1:1-1000000` |
| `scripts/filter_alignments.py` | Filter SAM/BAM/CRAM without changing record order | `python scripts/filter_alignments.py input.bam output.bam --exclude-secondary` |

All scripts refuse to overwrite existing outputs. Run each with `--help` for
coordinate, index, and privacy notes.

## Coordinate Contract

**Numeric coordinates accepted by pysam APIs are 0-based, half-open.** This
includes numeric `AlignmentFile.fetch()`, `VariantFile.fetch()`,
`FastaFile.fetch()`, `TabixFile.fetch()`, and `pileup()` arguments.

**Region strings are samtools-style: 1-based and inclusive.**

```python
# The same 100 bases:
bam.fetch("chr1", 99, 199)          # [99, 199)
bam.fetch(region="chr1:100-199")    # 1-based inclusive
```

VCF text uses 1-based `POS`, while record properties expose both systems:

```python
record.pos    # 1-based
record.start  # 0-based inclusive
record.stop   # 0-based exclusive
```

Read `references/coordinates_and_indexing.md` for format conversions, overlap
semantics, index choices, and contig-name checks.

## Alignment Files

Use context managers and explicit modes:

```python
import pysam

with pysam.AlignmentFile("sample.bam", "rb", threads=4) as bam:
    for read in bam.fetch("chr1", 1_000, 2_000):
        if (
            not read.is_unmapped
            and not read.is_secondary
            and not read.is_supplementary
            and read.mapping_quality >= 30
        ):
            print(read.query_name, read.reference_start, read.cigarstring)
```

Use `fetch(until_eof=True)` to stream every record in file order, including
unplaced unmapped reads, without requiring an index:

```python
with pysam.AlignmentFile("sample.bam", "rb") as bam:
    for read in bam.fetch(until_eof=True):
        ...
```

Important distinctions:

- `fetch()` returns alignment records overlapping a region.
- `count()` counts records and defaults to `read_callback="nofilter"`.
- `count_coverage()` returns A/C/G/T base counts and defaults to base quality
  15 plus `read_callback="all"`.
- `pileup()` exposes per-column reads and has its own filtering, base-quality,
  overlap, orphan, and `max_depth=8000` defaults.

For exact-region pileups, set `truncate=True` and explicit filters:

```python
with pysam.FastaFile("reference.fa") as fasta, pysam.AlignmentFile(
    "sample.bam", "rb"
) as bam:
    for column in bam.pileup(
        "chr1",
        1_000,
        2_000,
        truncate=True,
        stepper="samtools",
        fastafile=fasta,
        min_mapping_quality=20,
        min_base_quality=20,
        max_depth=100_000,
    ):
        print(column.reference_pos, column.get_num_aligned())
```

Read `references/alignment_files.md` for flags, CIGAR operations, tags,
modified bases, writing records, pileup details, and iterator lifetime.

## Variant Files

Input format is auto-detected. Numeric fetch coordinates remain 0-based:

```python
import pysam

with pysam.VariantFile("cohort.vcf.gz", threads=4) as variants:
    for record in variants.fetch("chr1", 999_999, 2_000_000):
        print(record.contig, record.pos, record.ref, record.alts)
        for sample_name, call in record.samples.items():
            print(sample_name, call.get("GT"))
```

Subset samples **before retrieving records**:

```python
with pysam.VariantFile("cohort.bcf") as variants:
    variants.subset_samples(["sample_A", "sample_B"])
    for record in variants:
        ...
```

When changing a header, copy each record and translate it to the destination
header before assigning newly declared INFO/FORMAT/FILTER fields. Do not
manually clear and rebuild `header.samples`.

Read `references/variant_files.md` for safe headers, writing, sample
subsetting, missing genotypes, symbolic alleles, filtering, translation, and
indexing.

## FASTA, FASTQ, and Tabix

Indexed FASTA uses numeric 0-based coordinates:

```python
with pysam.FastaFile("reference.fa") as fasta:
    sequence = fasta.fetch("chr1", 999, 1_099)
```

`FastxFile` is sequential. `persist=False` is faster but yielded records become
invalid after iteration advances:

```python
with pysam.FastxFile("reads.fastq.gz", persist=False) as reads:
    for read in reads:
        qualities = read.get_quality_array()
        ...
```

Tabix input must be coordinate-sorted and BGZF-compressed, not ordinary gzip.
Use a non-destructive two-step workflow:

```python
pysam.tabix_compress("regions.bed", "regions.bed.gz")
pysam.tabix_index("regions.bed.gz", preset="bed")

with pysam.TabixFile("regions.bed.gz", parser=pysam.asBed()) as tbx:
    for interval in tbx.fetch("chr1", 1_000, 2_000):
        print(interval.contig, interval.start, interval.end)
```

Read `references/sequence_files.md` for FASTA/FASTQ records and safe tabix
creation.

## CRAM, Remote I/O, and Threads

pysam 0.24 changed inherited HTSlib behavior:

- Newly written CRAM defaults to CRAM 3.1, not 3.0.
- HTSlib no longer contacts the EBI reference server by default.
- Prefer `reference_filename="reference.fa"` for deterministic local reads and
  writes.

```python
with pysam.AlignmentFile(
    "sample.cram",
    "rc",
    reference_filename="reference.fa",
    threads=4,
) as cram:
    for read in cram.fetch("chr1", 1_000, 2_000):
        ...
```

Only configure `REF_PATH`/`REF_CACHE` when reference-by-MD5 lookup is
intentional. Do not assume a CRAM is self-contained. `threads=` accelerates
compression/decompression; it does not parallelize Python analysis.

Read `references/cram_and_performance.md` before CRAM conversion, remote access,
or concurrent iteration.

## Wrapped samtools and bcftools

Import command modules explicitly. Pass each command-line token as a separate
string:

```python
import pysam.samtools
import pysam.bcftools

pysam.samtools.sort(
    "-@", "4", "-o", "sorted.bam", "input.bam", catch_stdout=False
)
pysam.samtools.index("-@", "4", "sorted.bam", catch_stdout=False)

pysam.bcftools.index("--csi", "variants.vcf.gz", catch_stdout=False)
```

Dispatchers capture stdout by default. For large or binary output, use the
tool's `-o` option with `catch_stdout=False`, or `save_stdout=...`, rather than
returning the complete output in memory.

```python
try:
    pysam.samtools.quickcheck("-v", "sample.bam")
except pysam.SamtoolsError as error:
    messages = pysam.samtools.quickcheck.get_messages()
    raise RuntimeError(messages or str(error)) from error
```

Use the Python API for record-level logic and dispatchers for mature bulk
operations such as sort, index, merge, view, and normalization. Never compose
dispatcher arguments by splitting an untrusted shell command.

## Writing Rules

- Copy or construct a valid header before opening output.
- Write to a new path; do not use `force=True` unless replacement is explicit.
- Preserve sort order if the output will be indexed.
- Set `query_sequence` before `query_qualities`.
- Prefer `pysam.CIGAR_OPS` enum members; top-level constants such as
  `pysam.CMATCH` are compatibility aliases slated for future removal.
- Validate outputs with `pysam.samtools.quickcheck()` for alignments and reopen
  variant/sequence outputs before downstream use.
- Use CSI rather than BAI/TBI when references or coordinates exceed legacy
  index limits.

## Reference Map

| Need | Read |
|---|---|
| Alignment API, flags, CIGAR, pileup, modified bases | `references/alignment_files.md` |
| VCF/BCF headers, records, samples, writing | `references/variant_files.md` |
| FASTA/FASTQ and tabix-indexed tables | `references/sequence_files.md` |
| Coordinate conversion and index selection | `references/coordinates_and_indexing.md` |
| CRAM references, remote I/O, threads, performance | `references/cram_and_performance.md` |
| Correct integrated analysis patterns | `references/common_workflows.md` |
| Compact current API signatures and defaults | `references/api_reference.md` |
| Upgrade notes for existing environments | `references/migration_to_0_24.md` |
| Official docs, specifications, and release sources | `references/sources.md` |

## Common Failure Modes

- Treating numeric `VariantFile.fetch()` coordinates as 1-based
- Using ordinary gzip where BGZF plus tabix/CSI is required
- Calling region fetch without an index
- Assuming `fetch()` includes unplaced unmapped alignments
- Forgetting `truncate=True` for an exact pileup interval
- Ignoring pileup defaults such as base quality 13 and depth cap 8000
- Sharing one file handle across active iterators or threads
- Decoding CRAM without its exact reference
- Assigning a new VCF field before declaring it in the output header
- Capturing large samtools/bcftools output in memory
- Using a SNP base-counting method for indels or symbolic alleles