Skip to content

molforge.io

io

File I/O for molforge.

This subpackage provides parsers and writers for the file formats you'll encounter across structural-biology workflows. The top-level entry points are :func:load, :func:save, and :func:fetch, which dispatch to the appropriate handler based on the file extension.

Supported formats:

  • PDB (.pdb, .ent) — full read/write, the universal default.
  • mmCIF / PDBx (.cif, .mmcif) — full read/write; recommended for structures with >99,999 atoms (PDB's hard limit).
  • FASTA (.fasta, .fa, .faa, .fna) — sequence read/write.
  • SDF (.sdf, .mol) — small-molecule exchange; full read/write of V2000 (coordinates, elements, title, property block). V3000 is not yet supported.
  • MOL2 (.mol2) — Tripos small-molecule exchange; full read/write of the ATOM section (coordinates, elements via Tripos type prefix, atom names, partial charges, substructure info).
  • PDBQT (.pdbqt) — AutoDock / Vina format; full read/write of ATOM records with per-atom partial charges and AutoDock atom types, reusing the PDB reader for the leading columns. ROOT / BRANCH / TORSDOF rotatable-bond markers are read-tolerated; round-tripping preserves coordinates, charges, and types.
  • PQR (.pqr) — APBS / PDB2PQR with explicit per-atom charges and radii. The leading PDB-compatible columns are parsed as fixed-position; the charge and radius are whitespace-split from the trailing fields (PQR is not strictly fixed-column past the coordinates). Radii are attached to protein.metadata["radii"].

For MD trajectories:

  • :func:read_trajectory / :func:iter_trajectory / :func:write_trajectory — eager and streaming I/O for binary MD trajectories (.xtc, .trr, .dcd, .nc, .h5, plus multi-MODEL PDB). Trajectories are kept off the :func:load / :func:save dispatcher because they need an explicit topology argument and return :class:molforge.md.Trajectory rather than :class:molforge.core.Protein.

Convenience helpers:

  • :func:fetch / :func:fetch_many — pull one or many structures by PDB ID from RCSB or AlphaFold.
  • :func:search_rcsb — full-text search the RCSB PDB for entry IDs, ready to hand to :func:fetch_many.
  • :func:fetch_chembl / :func:fetch_chembl_many — pull one or many small molecules from ChEMBL by ID as :class:~molforge.core.Molecule objects.
  • :func:load_alphafold — load an AlphaFold prediction, exposing pLDDT as a first-class field rather than buried in B-factor.
Example

import molforge as mf protein = mf.load("1ubq.pdb") mf.save(protein, "1ubq_clean.pdb")

FastaRecord dataclass

FastaRecord(
    id: str,
    sequence: str,
    description: str = "",
    metadata: dict[str, str] = dict(),
)

A single record from a FASTA file.

Attributes:

Name Type Description
id str

The first whitespace-delimited token after > on the header line.

description str

The rest of the header line, if any.

sequence str

The concatenated, whitespace-stripped sequence.

metadata dict[str, str]

Free-form metadata (e.g. for downstream tools).

header property

header: str

The full header line including ID and description.

CIFParseError

Bases: ValueError

Raised when an mmCIF file cannot be parsed.

CIFWriteError

Bases: ValueError

Raised when an in-memory structure cannot be serialized to mmCIF.

PDBParseError

Bases: ValueError

Raised when a PDB file cannot be parsed.

PDBWriteError

Bases: ValueError

Raised when an in-memory structure cannot be serialized to PDB.

fetch_chembl

fetch_chembl(
    chembl_id: str,
    *,
    timeout: float = 30.0,
    sanitize: bool = True,
) -> Molecule

Fetch one compound from ChEMBL by ID as a :class:Molecule.

Downloads the entry from the ChEMBL REST API and builds a molecule from its canonical SMILES.

Parameters:

Name Type Description Default
chembl_id str

A ChEMBL molecule ID, e.g. "CHEMBL25".

required
timeout float

Network timeout in seconds.

30.0
sanitize bool

Run RDKit sanitization when parsing the SMILES.

True

Returns:

Name Type Description
A Molecule

class:Molecule whose name is ChEMBL's preferred name (or the

Molecule

ID when there's none), with metadata["source"] == "chembl" and the

Molecule

chembl_id recorded.

Raises:

Type Description
ValueError

If chembl_id is empty, or the entry carries no small-molecule structure (e.g. a biotherapeutic).

OSError

If the download fails — network error, timeout, or a non-2xx response (a 404 for an unknown ID).

RDKitNotInstalledError

If RDKit isn't installed.

Example

from molforge.io import fetch_chembl aspirin = fetch_chembl("CHEMBL25")

fetch_chembl_many

fetch_chembl_many(
    chembl_ids: Iterable[str],
    *,
    timeout: float = 30.0,
    sanitize: bool = True,
    on_error: str = "raise",
) -> list[Molecule]

Fetch several ChEMBL compounds by ID, one :func:fetch_chembl per ID.

Parameters:

Name Type Description Default
chembl_ids Iterable[str]

The ChEMBL IDs to fetch, in the order you want them back.

required
timeout float

Per-download network timeout in seconds.

30.0
sanitize bool

Run RDKit sanitization when parsing each SMILES.

True
on_error str

"raise" (default) stops at the first ID that fails; "skip" drops IDs that fail — a download error or an entry with no small-molecule structure — and returns the rest.

'raise'

Returns:

Type Description
list[Molecule]

The fetched molecules, in input order (minus any dropped under

list[Molecule]

on_error="skip").

Raises:

Type Description
ValueError

If on_error is not "raise" or "skip".

OSError

On a download failure when on_error="raise".

RDKitNotInstalledError

If RDKit isn't installed.

fetch

fetch(
    pdb_id: str,
    *,
    source: str = "rcsb",
    format: str = "pdb",
    timeout: float = 30.0,
) -> Protein

Fetch a structure by ID from a remote source.

Downloads the structure over HTTPS and parses it into a :class:~molforge.core.Protein. Uses only the standard library (:mod:urllib), so it adds no dependency.

Parameters:

Name Type Description Default
pdb_id str

4-character PDB ID (for source="rcsb") or UniProt accession (for source="alphafold"). Case-insensitive for RCSB.

required
source str

"rcsb" for the RCSB Protein Data Bank, or "alphafold" for the AlphaFold Protein Structure Database.

'rcsb'
format str

"pdb" or "cif". AlphaFold DB only serves "pdb" and "cif"; both are supported.

'pdb'
timeout float

Network timeout in seconds for the download.

30.0

Returns:

Name Type Description
A Protein

class:~molforge.core.Protein parsed from the downloaded

Protein

file.

Raises:

Type Description
ValueError

If source or format is unrecognized, or pdb_id is empty.

OSError

If the download fails — network error, timeout, or a non-existent ID (which the server returns as HTTP 404). The underlying :class:urllib.error.URLError / :class:~urllib.error.HTTPError is chained as the cause.

Example

from molforge.io import fetch protein = fetch("1ABC") # RCSB, PDB format af = fetch("P00520", source="alphafold") # AlphaFold DB

fetch_many

fetch_many(
    pdb_ids: Iterable[str],
    *,
    source: str = "rcsb",
    format: str = "pdb",
    timeout: float = 30.0,
    on_error: str = "raise",
) -> list[Protein]

Fetch several structures by ID, one :func:fetch per ID.

A thin convenience over :func:fetch for pulling a whole set — for example the hits from :func:search_rcsb. Downloads are sequential (the servers rate-limit, and this keeps the dependency to the standard library).

Parameters:

Name Type Description Default
pdb_ids Iterable[str]

The IDs to fetch, in the order you want them back.

required
source str

"rcsb" or "alphafold" (applied to every ID).

'rcsb'
format str

"pdb" or "cif" (applied to every ID).

'pdb'
timeout float

Per-download network timeout in seconds.

30.0
on_error str

"raise" (default) stops at the first ID that fails; "skip" drops IDs that fail — e.g. a 404 for a non-existent entry — and returns the structures that did download.

'raise'

Returns:

Type Description
list[Protein]

The fetched proteins, in input order (minus any dropped under

list[Protein]

on_error="skip").

Raises:

Type Description
ValueError

If on_error is not "raise" or "skip".

OSError

On a download failure when on_error="raise".

load

load(
    path: str | PathLike[str],
    *,
    format: str | None = None,
    **kwargs: object,
) -> object

Load a structure or sequence file.

Format is inferred from the extension unless format is given. Additional kwargs are forwarded to the underlying reader.

Returns:

Name Type Description
A object

class:molforge.core.Protein for structure formats, a list of

object

class:molforge.io.FastaRecord for FASTA.

save

save(
    obj: object,
    path: str | PathLike[str],
    *,
    format: str | None = None,
    **kwargs: object,
) -> None

Save a structure or list of FASTA records to disk.

Format is inferred from the extension unless format is given.

read_fasta

read_fasta(path: str | PathLike[str]) -> list[FastaRecord]

Read a FASTA file from disk.

Parameters:

Name Type Description Default
path str | PathLike[str]

Path to a .fasta / .fa / .faa / .fna file. .gz suffix triggers gzip decompression.

required

Returns:

Type Description
list[FastaRecord]

A list of :class:FastaRecord objects, in file order.

read_fasta_string

read_fasta_string(text: str) -> Iterator[FastaRecord]

Parse FASTA-formatted text, yielding :class:FastaRecord objects.

Memory-efficient: yields records one at a time rather than building a list up front.

write_fasta

write_fasta(
    records: Iterable[FastaRecord | tuple[str, str]],
    path: str | PathLike[str],
    *,
    line_width: int = 80,
) -> None

Write FASTA records to disk.

Parameters:

Name Type Description Default
records Iterable[FastaRecord | tuple[str, str]]

Iterable of :class:FastaRecord or (id, sequence) tuples.

required
path str | PathLike[str]

Destination path; .gz triggers gzip.

required
line_width int

Maximum sequence characters per line. Set to 0 to emit each sequence on a single line.

80

write_fasta_string

write_fasta_string(
    records: Iterable[FastaRecord | tuple[str, str]],
    *,
    line_width: int = 80,
) -> str

Serialize records as FASTA-formatted text.

read_cif

read_cif(
    path: str | PathLike[str],
    *,
    include_hydrogens: bool = True,
    altloc: str = "highest_occupancy",
) -> Protein

Read an mmCIF / PDBx file from disk.

Parameters:

Name Type Description Default
path str | PathLike[str]

Path to a .cif or .mmcif file. .gz extension triggers gzip decompression.

required
include_hydrogens bool

If False, drop hydrogen atoms during parsing.

True
altloc str

Altloc-resolution strategy (same as :func:read_pdb): "highest_occupancy", "first", "all", or a single alternate-location identifier (e.g. "A").

'highest_occupancy'

Returns:

Name Type Description
A Protein

class:Protein with the parsed structure. metadata is

Protein

populated with pdb_id, title, experimental_method,

Protein

and resolution where available.

Raises:

Type Description
CIFParseError

If the file is malformed or has no _atom_site loop.

FileNotFoundError

If the path doesn't exist.

read_cif_string

read_cif_string(
    text: str,
    *,
    include_hydrogens: bool = True,
    altloc: str = "highest_occupancy",
) -> Protein

Parse mmCIF-formatted text into a :class:Protein.

See :func:read_cif for argument semantics.

write_cif

write_cif(
    protein: Protein, path: str | PathLike[str]
) -> None

Write a :class:Protein to an mmCIF file.

write_cif_string

write_cif_string(protein: Protein) -> str

Serialize a :class:Protein as mmCIF text.

Produces a compact CIF with a data_<id> header, the structure's metadata (where present), and a complete _atom_site loop. Round-trips cleanly through :func:read_cif_string.

iter_molecules

iter_molecules(
    path: str | PathLike[str],
    *,
    format: str | None = None,
    sanitize: bool = True,
) -> Iterator[Molecule]

Stream a molecule file into :class:Molecule objects, one at a time.

The lazy counterpart to :func:read_molecules: SDF is streamed with RDKit's ForwardSDMolSupplier and SMILES line by line, so a file larger than memory can be processed without materializing it. The format is resolved eagerly (so a bad extension or format raises right away), while per-record parsing stays lazy.

Parameters:

Name Type Description Default
path str | PathLike[str]

The file to read.

required
format str | None

"sdf" or "smiles"; inferred from the extension when omitted.

None
sanitize bool

Run RDKit sanitization on each molecule.

True

Returns:

Type Description
Iterator[Molecule]

A lazy iterator of molecules, in file order; each records the source

Iterator[Molecule]

file in its metadata and takes its name from the record.

Raises:

Type Description
RDKitNotInstalledError

If RDKit isn't installed (raised when the iterator is first consumed).

ValueError

On an unknown format (raised eagerly).

iter_smiles

iter_smiles(
    text: str,
    *,
    sanitize: bool = True,
    source: str = "<string>",
) -> Iterator[Molecule]

Stream a SMILES block into molecules, one line at a time.

The lazy counterpart to :func:read_smiles — same SMILES [name] per-line format (blank lines and # comments skipped), but molecules are yielded as each line is parsed rather than collected into a list.

Parameters:

Name Type Description Default
text str

The SMILES text.

required
sanitize bool

Run RDKit sanitization on each molecule.

True
source str

Recorded in each molecule's metadata["source"].

'<string>'

Yields:

Name Type Description
One Molecule

class:~molforge.core.Molecule per non-comment line, in order.

Raises:

Type Description
RDKitNotInstalledError

If RDKit isn't installed.

ValueError

If a SMILES string can't be parsed.

read_molecules

read_molecules(
    path: str | PathLike[str],
    *,
    format: str | None = None,
    sanitize: bool = True,
) -> list[Molecule]

Read a molecule file into chemistry-aware :class:Molecule objects.

Supports SDF (.sdf / .mol) and SMILES (.smi / .smiles). SDF records RDKit can't parse are skipped so one bad entry doesn't sink a bulk read. Each molecule records the source file in its metadata and takes its name from the record (the SDF title or the SMILES name column).

Parameters:

Name Type Description Default
path str | PathLike[str]

The file to read.

required
format str | None

"sdf" or "smiles"; inferred from the extension when omitted.

None
sanitize bool

Run RDKit sanitization on each molecule.

True

Returns:

Type Description
list[Molecule]

The molecules, in file order.

Raises:

Type Description
RDKitNotInstalledError

If RDKit isn't installed.

ValueError

On an unknown format.

read_smiles

read_smiles(
    text: str,
    *,
    sanitize: bool = True,
    source: str = "<string>",
) -> list[Molecule]

Parse a SMILES block into molecules.

One molecule per line, SMILES [name] (whitespace-separated); blank lines and # comments are skipped.

Parameters:

Name Type Description Default
text str

The SMILES text.

required
sanitize bool

Run RDKit sanitization on each molecule.

True
source str

Recorded in each molecule's metadata["source"].

'<string>'

Returns:

Type Description
list[Molecule]

The parsed molecules, in file order.

Raises:

Type Description
RDKitNotInstalledError

If RDKit isn't installed.

ValueError

If a SMILES string can't be parsed.

read_pdb

read_pdb(
    path: str | PathLike[str],
    *,
    model: int | None = None,
    include_hydrogens: bool = True,
    altloc: str = "highest_occupancy",
) -> Protein

Read a PDB file from disk.

Parameters:

Name Type Description Default
path str | PathLike[str]

Path to a .pdb file (may be gzipped if extension is .gz).

required
model int | None

Which model to load from a multi-model file. None (default) loads all models. 0 is the first model. Pass an int to load a specific model.

None
include_hydrogens bool

If False, drop hydrogen atoms during parsing.

True
altloc str

Strategy for resolving alternate location indicators.

  • "highest_occupancy" (default): keep the altloc with the highest occupancy per atom name.
  • "first": keep the first altloc encountered, drop the rest.
  • "all": keep all altlocs (atoms will share residue_id but differ on altloc field).
  • A single-character string (e.g. "A"): keep only that altloc and the default (blank).
'highest_occupancy'

Returns:

Name Type Description
A Protein

class:Protein holding the parsed structure. The protein's

Protein

metadata dict is populated with any HEADER, TITLE, RESOLUTION,

Protein

and EXPDTA records found.

Raises:

Type Description
PDBParseError

If the file is malformed.

FileNotFoundError

If the path doesn't exist.

read_pdb_string

read_pdb_string(
    text: str,
    *,
    model: int | None = None,
    include_hydrogens: bool = True,
    altloc: str = "highest_occupancy",
) -> Protein

Parse a PDB-formatted string into a :class:Protein.

See :func:read_pdb for argument semantics.

write_pdb

write_pdb(
    protein: Protein,
    path: str | PathLike[str],
    *,
    write_end: bool = True,
) -> None

Write a :class:Protein to a PDB file.

Parameters:

Name Type Description Default
protein Protein

the structure to serialize.

required
path str | PathLike[str]

destination path. .gz suffix triggers gzip compression.

required
write_end bool

emit a final END record.

True

Raises:

Type Description
PDBWriteError

If the structure exceeds PDB's hard limits (>99,999 atoms or >9,999 residues per chain).

write_pdb_string

write_pdb_string(
    protein: Protein, *, write_end: bool = True
) -> str

Serialize a :class:Protein into a PDB-formatted string.

is_alphafold_pdb

is_alphafold_pdb(text_or_path: str | PathLike[str]) -> bool

Detect whether a PDB file/string is an AlphaFold prediction.

Heuristic: looks for ALPHAFOLD, PREDICTED MODEL, or ESMFOLD in the first 100 lines of HEADER / TITLE / REMARK records.

load_alphafold

load_alphafold(path: str | PathLike[str]) -> Protein

Load an AlphaFold prediction, exposing pLDDT as metadata.

The protein is read via :func:molforge.io.read_pdb, then its metadata is populated with confidence information under two sets of keys:

  • Uniform folding-engine keys (preferred) — the same keys every molforge folding-engine wrapper sets, so downstream code can read confidence without caring which engine ran: confidence_per_atom, confidence_per_residue, mean_confidence, and engine (= "AlphaFold").
  • Legacy AlphaFold-specific keys (retained for backward compatibility): plddt, plddt_per_residue, mean_plddt, source (= "alphafold").

The two sets carry the same values; new code should prefer the uniform keys. See :mod:molforge.core.metadata_keys for the documented vocabulary.

The B-factor column is left intact for compatibility with downstream tools that still expect to find pLDDT there.

search_rcsb

search_rcsb(
    query: str, *, limit: int = 25, timeout: float = 30.0
) -> list[str]

Full-text search the RCSB PDB, returning matching entry IDs.

Runs a full-text query against the RCSB Search API and returns the PDB IDs of the best matches, most relevant first — feed them to :func:molforge.io.fetch_many to download the structures.

Parameters:

Name Type Description Default
query str

Free-text query, e.g. "hemoglobin" or "CRISPR Cas9".

required
limit int

Maximum number of IDs to return (the top limit hits).

25
timeout float

Network timeout in seconds.

30.0

Returns:

Type Description
list[str]

Up to limit PDB IDs ranked by relevance; empty if nothing matches.

Raises:

Type Description
ValueError

If query is empty or limit is less than 1.

OSError

If the search request fails — network error, timeout, or a non-2xx response from RCSB.

Example

from molforge.io import search_rcsb, fetch_many ids = search_rcsb("hemoglobin", limit=5) structures = fetch_many(ids)

iter_trajectory

iter_trajectory(
    path: str | PathLike[str],
    *,
    topology: Protein | str | PathLike[str] | None = None,
    chunk_size: int = 100,
    stride: int = 1,
    atom_indices: list[int] | ndarray | None = None,
    fmt: str | None = None,
) -> Iterator[Trajectory]

Stream a trajectory in chunks of frames.

Use this for trajectories larger than RAM. Each yielded object is a :class:molforge.md.Trajectory holding chunk_size frames (the last chunk may be shorter); memory usage is bounded by chunk_size × n_atoms × 12 bytes.

Parameters:

Name Type Description Default
path str | PathLike[str]

Trajectory file.

required
topology Protein | str | PathLike[str] | None

As for :func:read_trajectory.

None
chunk_size int

Number of frames per yielded Trajectory. Default 100; balance memory vs iteration overhead.

100
stride int

Read every stride-th frame.

1
atom_indices list[int] | ndarray | None

Read only these atom indices.

None

Yields:

Name Type Description
A Trajectory

class:molforge.md.Trajectory per chunk, in file order.

Raises:

Type Description
MDEngineNotInstalledError

If mdtraj is not installed.

ValueError

If topology is missing for a format that requires it.

Example

for chunk in iter_trajectory("big.xtc", topology=top, chunk_size=500): ... # process chunk.coordinates here; memory bounded ... pass

read_trajectory

read_trajectory(
    path: str | PathLike[str],
    *,
    topology: Protein | str | PathLike[str] | None = None,
    stride: int = 1,
    atom_indices: list[int] | ndarray | None = None,
    fmt: str | None = None,
) -> Trajectory

Read a trajectory file into a :class:molforge.md.Trajectory.

Parameters:

Name Type Description Default
path str | PathLike[str]

Path to the trajectory. Format inferred from the extension unless fmt is given. Supported: .xtc, .trr, .dcd, .nc, .netcdf, .h5, .h5md, .pdb.

required
topology Protein | str | PathLike[str] | None

The topology to attach. Required for formats that don't embed topology (.xtc, .trr, .dcd, .nc). Accepts a :class:molforge.core.Protein or a path to a PDB. May be None for .pdb and .h5 files, which carry their own topology.

None
stride int

Read every stride-th frame (default 1, all frames). Useful when full time resolution isn't needed.

1
atom_indices list[int] | ndarray | None

Read only these atom indices (0-based). Useful when an analysis only touches a subset (e.g. backbone atoms). When given, the resulting :class:Trajectory's topology is sliced to match.

None
fmt str | None

Override the format inference. Passed to mdtraj as the file extension (without the leading dot).

None

Returns:

Name Type Description
A Trajectory

class:molforge.md.Trajectory with coordinates in Å, times

Trajectory

in picoseconds when available, and the topology as a

Trajectory

class:molforge.core.Protein. The whole file is loaded into

Trajectory

memory — use :func:iter_trajectory for files too large to

Trajectory

fit.

Raises:

Type Description
MDEngineNotInstalledError

If mdtraj is not installed.

ValueError

If topology is missing for a format that requires it, or if the atom count in the trajectory disagrees with the topology.

Example

from molforge.io import read_pdb, read_trajectory topology = read_pdb("system.pdb") traj = read_trajectory("md.xtc", topology=topology) traj.n_frames 1000 traj.coordinates.shape # (n_frames, n_atoms, 3) in Å (1000, 24512, 3)

write_trajectory

write_trajectory(
    trajectory: Trajectory,
    path: str | PathLike[str],
    *,
    fmt: str | None = None,
) -> None

Write a :class:Trajectory to disk.

The format is inferred from the path's extension. Coordinates are converted from Å (molforge convention) to nm (mdtraj's convention) on the way out.

Parameters:

Name Type Description Default
trajectory Trajectory

The :class:molforge.md.Trajectory to write.

required
path str | PathLike[str]

Output path. Format inferred from the extension.

required

Raises:

Type Description
MDEngineNotInstalledError

If mdtraj is not installed.

Example

from molforge.io import write_trajectory write_trajectory(traj, "out.xtc")