Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/en/alphafold.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Path to folder to save prediction results in (str). Default: "./[date_time]_gget

`-jhd` `--jackhmmer_savedir`
Path to a parent directory in which to store the temporary jackhmmer files (str). By default, `gget alphafold` creates a "tmp" folder in your home directory (`~/tmp/jackhmmer/`), which can take up to ~2 GB of disk space. Use this argument to place these temporary files elsewhere, e.g. on a disk with more free space. Default: None.
`-msa` `--msa`
Path to a custom multiple sequence alignment (MSA) file to use instead of running the internal jackhmmer search (str). Accepts a3m (`.a3m`) or aligned FASTA (`.fasta`, `.fa`, `.afa`) files. The first sequence in the MSA must be the query (i.e. match the input `sequence`, ignoring gaps). When provided, `gget alphafold` skips the jackhmmer search entirely, so no genetic databases are downloaded. This is useful for submitting a manually curated MSA to improve folding predictions. Currently supported for single-sequence (monomer) predictions only. Default: None.

**Flags**
`-mfm` `--multimer_for_monomer`
Expand Down
1 change: 1 addition & 0 deletions docs/src/en/updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#### *gget* officially became part of [*scverse*](https://scverse.org/) on June 9, 2026. 🥳🥳🥳

**Version ≥ 0.30.9** (XXX XX, 2026):
- [`gget alphafold`](alphafold.md): Added a new `msa` argument (`-msa`/`--msa` on the command line) that lets you provide a custom multiple sequence alignment (a3m or aligned FASTA) instead of running the internal jackhmmer search. The first sequence in the MSA must be the query. This enables folding from a manually curated MSA and skips the genetic database download entirely (currently supported for single-sequence/monomer predictions). Resolves [issue 52](https://github.com/scverse/gget/issues/52).

**Version ≥ 0.30.8** (Jun 28, 2026):
- [`gget g2p`](g2p.md): Either `gene` or `--uniprot_id` is now sufficient — whichever is missing is resolved via UniProt and cached. Gene→UniProt picks the canonical reviewed human Swiss-Prot entry; the resolution and its limitations are logged. The canonical pair is **always** prepended to the result as `gene_name` / `uniprot_id` columns (and stored on `df.attrs`), so the output schema is invariant regardless of input mode. Existing call sites continue to work.
Expand Down
334 changes: 248 additions & 86 deletions gget/gget_alphafold.py

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions gget/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,18 @@ def main() -> None:
"which can take up to ~2 GB of disk space. Use this argument to place them elsewhere."
),
)
parser_alphafold.add_argument(
"-msa",
"--msa",
type=str,
default=None,
required=False,
help=(
"Path to a custom multiple sequence alignment (MSA) file (a3m or aligned FASTA) to use\n"
"instead of running the internal jackhmmer search. The first sequence in the MSA must be\n"
"the query (matching the input sequence, ignoring gaps). Single-sequence predictions only."
),
)
parser_alphafold.add_argument(
"-q",
"--quiet",
Expand Down Expand Up @@ -3716,6 +3728,7 @@ def main() -> None:
show_sidechains=False,
verbose=args.quiet,
jackhmmer_savedir=args.jackhmmer_savedir,
msa=args.msa,
)

## pdb return
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/test_alphafold_msa.a3m
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
>query
MKVLAAGSTKDEFGHIKLMN
>sp|homolog1
MKVLAAGSTKDEFGHIKLMN
>sp|homolog2
MKVLAAGSTKDEF-HIKLMN
>sp|homolog3_with_insertion
MKVefLAAGSTKDEFGHIKLMN
6 changes: 6 additions & 0 deletions tests/fixtures/test_alphafold_msa.fasta
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
>query
MKVLAAGSTKDEFGHIKLMN
>homolog1
MKVLAAGSTKDEF-HIKLMN
>homolog2
MKVLAAGSTKDEFGHIKLMN
91 changes: 89 additions & 2 deletions tests/test_alphafold.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,27 @@
from unittest.mock import patch

import gget.gget_alphafold as gget_alphafold
from gget.gget_alphafold import clean_up, get_jackhmmer_dir
from gget.gget_alphafold import (
clean_up,
detect_msa_format,
get_jackhmmer_dir,
parse_custom_msa,
)
from gget.gget_setup import UUID

# AlphaFold requires heavy third-party dependencies (alphafold, openmm, jackhmmer, model
# parameters) that are not available in the CI/test environment, so a full prediction run
# cannot be exercised here. These tests validate the user-facing jackhmmer save-directory
# option (https://github.com/scverse/gget/issues/49) at the argument/path-handling level.
# option (https://github.com/scverse/gget/issues/49) at the argument/path-handling level and
# the user-provided custom MSA input feature (https://github.com/scverse/gget/issues/52) at
# the parsing/validation level (the logic that turns a custom a3m/FASTA file into AlphaFold
# MSA features).

FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
A3M_FIXTURE = os.path.join(FIXTURE_DIR, "test_alphafold_msa.a3m")
FASTA_FIXTURE = os.path.join(FIXTURE_DIR, "test_alphafold_msa.fasta")

QUERY = "MKVLAAGSTKDEFGHIKLMN"


class TestAlphafoldJackhmmerSavedir(unittest.TestCase):
Expand Down Expand Up @@ -75,5 +89,78 @@ def test_cli_exposes_jackhmmer_savedir_flag(self):
self.assertIn("--jackhmmer_savedir", result.stdout)


class TestAlphafoldCustomMSA(unittest.TestCase):
def test_detect_msa_format(self):
self.assertEqual(detect_msa_format("alignment.a3m"), "a3m")
self.assertEqual(detect_msa_format("/path/to/ALIGNMENT.A3M"), "a3m")
self.assertEqual(detect_msa_format("alignment.fasta"), "fasta")
self.assertEqual(detect_msa_format("alignment.fa"), "fasta")
self.assertEqual(detect_msa_format("alignment.afa"), "fasta")

def test_detect_msa_format_unsupported(self):
with self.assertRaises(ValueError):
detect_msa_format("alignment.txt")
with self.assertRaises(ValueError):
detect_msa_format("alignment.sto")

def test_parse_a3m_fixture(self):
with open(A3M_FIXTURE) as f:
aligned_sequences, deletion_matrix, descriptions = parse_custom_msa(f.read())

# Four sequences, the first of which is the query.
self.assertEqual(len(aligned_sequences), 4)
self.assertEqual(len(descriptions), 4)
self.assertEqual(aligned_sequences[0], QUERY)

# After removing a3m insertions, every aligned row has the query's length.
for seq in aligned_sequences:
self.assertEqual(len(seq), len(QUERY))

# Deletion matrix rows align 1:1 with the aligned columns.
for seq, deletions in zip(aligned_sequences, deletion_matrix, strict=True):
self.assertEqual(len(deletions), len(seq))

# The query and the gapped homolog carry no insertions.
self.assertEqual(sum(deletion_matrix[0]), 0)
self.assertEqual(sum(deletion_matrix[2]), 0)

# homolog3 has two lowercase insertion characters ("ef") after the third residue.
self.assertEqual(sum(deletion_matrix[3]), 2)
self.assertEqual(deletion_matrix[3][3], 2)

def test_parse_fasta_fixture_has_no_insertions(self):
with open(FASTA_FIXTURE) as f:
aligned_sequences, deletion_matrix, _ = parse_custom_msa(f.read())

self.assertEqual(aligned_sequences[0], QUERY)
# Aligned FASTA contains no lowercase insertions -> deletion matrix is all zeros.
for deletions in deletion_matrix:
self.assertEqual(sum(deletions), 0)

def test_query_matches_first_msa_sequence(self):
# Mirrors the validation gget performs: first MSA sequence (gaps removed) == query.
with open(A3M_FIXTURE) as f:
aligned_sequences, _, _ = parse_custom_msa(f.read())
query_in_msa = aligned_sequences[0].replace("-", "").upper()
self.assertEqual(query_in_msa, QUERY)

def test_parse_empty_msa_raises(self):
with self.assertRaises(ValueError):
parse_custom_msa("")

def test_parse_non_fasta_raises(self):
with self.assertRaises(ValueError):
parse_custom_msa("MKVLAAG\nNOHEADERLINE\n")

def test_cli_exposes_msa_flag(self):
"""The command-line interface exposes the --msa option for gget alphafold."""
result = subprocess.run(
[sys.executable, "-m", "gget", "alphafold", "--help"],
capture_output=True,
text=True,
)
self.assertIn("--msa", result.stdout)


if __name__ == "__main__":
unittest.main()
Loading