Skip to content

Commit

Permalink
feat: Migrate to Typer for CLI (#22)
Browse files Browse the repository at this point in the history
* feat: integrate typer

* tests
  • Loading branch information
gromdimon authored Mar 28, 2024
1 parent 3b38cda commit 8723900
Show file tree
Hide file tree
Showing 21 changed files with 194 additions and 114 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ flake8:
lint-mypy:
MYPYPATH=$(PWD)/stubs pipenv run mypy --check-untyped-defs $(DIRS_PYTHON)

#pipenv run python -m src.main 4-113568536-G-GA --genome_release hg19
.PHONY: example_run
example_run:
pipenv run python -m src.main 4-113568536-G-GA --genome_release hg19
pipenv run python -m src.cli "4-113568536-G-GA" --genome-release hg19

.PHONY: test
test:
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ sphinx = "*"
datamodel-code-generator = "*"
requests = "*"
responses = "*"
typer = {extras = ["all"], version = "*"}

[dev-packages]
debugpy = "*"
Expand Down
50 changes: 49 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/api/annonars.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from pydantic import ValidationError

from src.core.config import settings
from src.models.annonars import AnnonarsRangeResponse
from src.seqvar import SeqVar
from src.types.annonars import AnnonarsRangeResponse

#: Annonars API base URL
ANNONARS_API_BASE_URL = f"{settings.API_REEV_URL}/annonars"
Expand Down
2 changes: 1 addition & 1 deletion src/api/dotty.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from src.core.config import settings
from src.genome_builds import GenomeRelease
from src.models.dotty import DottySpdiResponse
from src.types.dotty import DottySpdiResponse

#: Dotty API base URL
DOTTI_API_BASE_URL = f"{settings.API_REEV_URL}/dotty"
Expand Down
2 changes: 1 addition & 1 deletion src/api/mehari.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from src.core.config import settings
from src.genome_builds import GenomeRelease
from src.models.mehari import GeneTranscripts, TranscriptsSeqVar
from src.seqvar import SeqVar
from src.types.mehari import GeneTranscripts, TranscriptsSeqVar

#: Mehari API base URL
MEHARI_API_BASE_URL = f"{settings.API_REEV_URL}/mehari"
Expand Down
53 changes: 28 additions & 25 deletions src/autoPVS1.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
"""Implementations of the PVS1 algorithm."""

import logging
from typing import Dict, List, Tuple, Union

import typer

from src.api.mehari import MehariClient
from src.core.config import settings
from src.enums import PVS1Prediction, SeqVarConsequence
from src.genome_builds import GenomeRelease
from src.models.autopvs1 import TranscriptInfo
from src.models.mehari import TranscriptGene, TranscriptSeqvar
from src.seqvar import SeqVar, SeqVarResolver
from src.seqvar_pvs1 import SeqVarPVS1

# Setup logging
logging_level = logging.DEBUG if settings.DEBUG else logging.INFO
logging.basicConfig(level=logging_level)
logger = logging.getLogger(__name__)

from src.types.autopvs1 import TranscriptInfo
from src.types.enums import PVS1Prediction, SeqVarConsequence
from src.types.mehari import TranscriptGene, TranscriptSeqvar

#: Mapping of consequence from transcript info to SeqVarConsequence
SeqvarConsequenceMapping = {
Expand Down Expand Up @@ -69,15 +64,15 @@ def resolve_variant(self) -> SeqVar | None:
# Try to resolve as Sequence variant
seqvar_resolver = SeqVarResolver()
seqvar: SeqVar = seqvar_resolver.resolve_seqvar(self.variant_name, self.genome_release)
logger.debug(f"Resolved variant: {seqvar}.")
typer.echo(f"Resolved variant: {seqvar}.")
return seqvar
except Exception as e:
logger.error(e)
typer.secho(e, err=True, fg=typer.colors.RED)
return None

def predict(self):
"""Run the AutoPVS1 algorithm."""
logger.info(f"Running AutoPVS1 for variant {self.variant_name}.")
typer.echo(f"Running AutoPVS1 for variant {self.variant_name}.")
variant = self.resolve_variant()

if isinstance(variant, SeqVar):
Expand All @@ -92,10 +87,10 @@ def predict(self):
self.consequence: SeqVarConsequence = SeqVarConsequence.NotSet
self.prediction: PVS1Prediction = PVS1Prediction.NotPVS1

logger.debug(f"Retrieving transcripts.")
typer.echo(f"Retrieving transcripts.")
self._get_transcripts_info()
if not self.seqvar_transcript or not self.gene_transcript:
logger.error("No transcripts found for the variant.")
typer.secho("No transcripts found for the variant.", err=True, fg=typer.colors.RED)
return
else:
try:
Expand All @@ -105,19 +100,23 @@ def predict(self):
)
self.pvs1.verify_PVS1()
self.prediction = self.pvs1.prediction
logger.info(
typer.echo(
f"PVS1 prediction for {self.pvs1.seqvar.user_representation}: {self.pvs1.prediction}"
)
except Exception as e:
logger.error(
f"Failed to predict PVS1 for variant {self.seqvar.user_representation}."
typer.secho(
f"Failed to predict PVS1 for variant {self.seqvar.user_representation}.",
err=True,
fg=typer.colors.RED,
)
logger.error(e)
typer.echo(e, err=True)
elif isinstance(variant, str):
# TODO: Add Structure variants PVS1 prediction
pass
else:
logger.error(f"Failed to resolve variant {self.variant_name}.")
typer.secho(
f"Failed to resolve variant {self.variant_name}.", err=True, fg=typer.colors.RED
)
return

@staticmethod
Expand Down Expand Up @@ -185,8 +184,10 @@ def _choose_transcript(
def _get_transcripts_info(self):
"""Get all transcripts for the given sequence variant from Mehari."""
if not self.seqvar:
logger.error(
"No sequence variant specified. Assure that the variant is resolved before fetching transcripts."
typer.secho(
"No sequence variant specified. Assure that the variant is resolved before fetching transcripts.",
err=True,
fg=typer.colors.RED,
)
return
try:
Expand Down Expand Up @@ -221,7 +222,9 @@ def _get_transcripts_info(self):
self.gene_transcript = None

except Exception as e:
logger.error(
f"Failed to get transcripts for variant {self.seqvar.user_representation}."
typer.secho(
f"Failed to get transcripts for variant {self.seqvar.user_representation}.",
err=True,
fg=typer.colors.RED,
)
logger.error(e)
typer.secho(e, err=True)
51 changes: 51 additions & 0 deletions src/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Entry point for the command line interface."""

import typer
from typing_extensions import Annotated

from src.autoPVS1 import AutoPVS1
from src.genome_builds import GenomeRelease

app = typer.Typer()

#: Allowed genome releases
ALLOWED_GENOME_RELEASES = ["GRCh37", "GRCh38", "hg19", "hg38", "grch37", "grch38"]
#: Allowed sequence variant formats
ALLOWED_SEQVAR_FORMATS = ["Canonical SPDI", "gnomAD", "relaxed SPDI", "dbSNP", "ClinVar"]


@app.command()
def classify(
variant: Annotated[
str,
typer.Argument(
help=f"Variant to be classified, e.g., 'NM_000038.3:c.797G>A'. Accepted formats: {', '.join(ALLOWED_SEQVAR_FORMATS)}"
),
],
genome_release: Annotated[
str,
typer.Option(
"--genome-release",
"-g",
help=f"Accepted genome Releases: {', '.join(ALLOWED_GENOME_RELEASES)}",
),
] = "GRCh38",
):
"""
Classify a variant using the specified genome release.
"""
try:
genome_release_enum = GenomeRelease.from_string(genome_release)
if not genome_release_enum:
raise ValueError(
f"Invalid genome release: {genome_release}. Please use one of {', '.join(ALLOWED_GENOME_RELEASES)}."
)

auto_pvs1 = AutoPVS1(variant, genome_release_enum)
auto_pvs1.predict()
except Exception as e:
typer.secho(f"Error: {e}", err=True, fg=typer.colors.RED)


if __name__ == "__main__":
app()
68 changes: 0 additions & 68 deletions src/main.py

This file was deleted.

Loading

0 comments on commit 8723900

Please sign in to comment.