Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NMD prediction #101

Merged
merged 5 commits into from
May 21, 2024
Merged
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
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ format: \

.PHONY: format-isort
format-isort:
pipenv run isort --profile=black --line-length 110 $(DIRS_PYTHON)
pipenv run isort --profile=black --line-length 100 $(DIRS_PYTHON)

.PHONY: format-black
format-black:
pipenv run black --line-length 110 $(DIRS_PYTHON)
pipenv run black --line-length 100 $(DIRS_PYTHON)

.PHONY: lint
lint: \
Expand All @@ -53,15 +53,15 @@ lint: \

.PHONY: lint-isort
lint-isort:
pipenv run isort --profile=black --line-length 110 --check-only --diff $(DIRS_PYTHON)
pipenv run isort --profile=black --line-length 100 --check-only --diff $(DIRS_PYTHON)

.PHONY: lint-black
lint-black:
pipenv run black --check --line-length 110 --diff $(DIRS_PYTHON)
pipenv run black --check --line-length 100 --diff $(DIRS_PYTHON)

.PHONY: lint-flake8
flake8:
pipenv run flake8 --max-line-length 110 $(DIRS_PYTHON)
pipenv run flake8 --max-line-length 100 $(DIRS_PYTHON)

.PHONY: lint-mypy
lint-mypy:
Expand Down
4 changes: 3 additions & 1 deletion src/api/annonars.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class AnnonarsClient:
def __init__(self, *, api_base_url: Optional[str] = None):
self.api_base_url = api_base_url or ANNONARS_API_BASE_URL

def get_variant_from_range(self, seqvar: SeqVar, start: int, stop: int) -> AnnonarsRangeResponse:
def get_variant_from_range(
self, seqvar: SeqVar, start: int, stop: int
) -> AnnonarsRangeResponse:
"""Pull all variants within a range.

Args:
Expand Down
4 changes: 3 additions & 1 deletion src/api/dotty.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class DottyClient:
def __init__(self, *, api_base_url: Optional[str] = None):
self.api_base_url = api_base_url or DOTTI_API_BASE_URL

def to_spdi(self, query: str, assembly: GenomeRelease = GenomeRelease.GRCh38) -> DottySpdiResponse | None:
def to_spdi(
self, query: str, assembly: GenomeRelease = GenomeRelease.GRCh38
) -> DottySpdiResponse | None:
"""
Converts a variant to SPDI format.

Expand Down
4 changes: 2 additions & 2 deletions src/api/mehari.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ def get_seqvar_transcripts(self, seqvar: SeqVar) -> TranscriptsSeqVar:
f"genome_release={seqvar.genome_release.name.lower()}"
f"&chromosome={seqvar.chrom}"
f"&position={seqvar.pos}"
f"&reference={seqvar.insert}"
f"&alternative={seqvar.delete}"
f"&reference={seqvar.delete}"
f"&alternative={seqvar.insert}"
)
logger.debug("GET request to: {}", url)
response = requests.get(url)
Expand Down
4 changes: 3 additions & 1 deletion src/auto_acmg.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ def predict(self):
)
self.seqvar: SeqVar = variant
self.seqvar_pvs1_prediction: PVS1Prediction = PVS1Prediction.NotSet
self.seqvar_pvs1_prediction_path: PVS1PredictionSeqVarPath = PVS1PredictionSeqVarPath.NotSet
self.seqvar_pvs1_prediction_path: PVS1PredictionSeqVarPath = (
PVS1PredictionSeqVarPath.NotSet
)
self.seqvar_ps1: Optional[bool] = None
self.seqvar_pm5: Optional[bool] = None

Expand Down
14 changes: 11 additions & 3 deletions src/auto_ps1_pm5.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
class AutoPS1PM5:
"""Predicts PS1 criteria for sequence variants."""

def __init__(self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None):
def __init__(
self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None
):
#: Configuration to use.
self.config = config or Config()
# Attributes to be set
Expand Down Expand Up @@ -113,8 +115,14 @@ def predict(self) -> Optional[PS1PM5]:
self.prediction = PS1PM5()

primary_info = self._get_variant_info(self.seqvar)
if not primary_info or not primary_info.result.dbnsfp or not primary_info.result.dbnsfp.HGVSp_VEP:
raise MissingDataError("No valid primary variant information for PS1/PM5 prediction.")
if (
not primary_info
or not primary_info.result.dbnsfp
or not primary_info.result.dbnsfp.HGVSp_VEP
):
raise MissingDataError(
"No valid primary variant information for PS1/PM5 prediction."
)

primary_aa_change = self._parse_HGVSp(primary_info.result.dbnsfp.HGVSp_VEP)
if not primary_aa_change:
Expand Down
8 changes: 6 additions & 2 deletions src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
class Settings(BaseSettings):
"""Settings for the autopvs1 CLI."""

model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", case_sensitive=True)
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", case_sensitive=True
)

# --- app-specific settings ---

Expand Down Expand Up @@ -48,5 +50,7 @@ def _set_base_urls(cls, data: Any) -> Any:
if isinstance(data, dict):
data["api_base_url"] = data.get("api_base_url", settings.API_REEV_URL)
for key in ("annonars", "mehari", "dotty"):
data[f"api_base_url_{key}"] = data.get(f"api_base_url_{key}", f"{data['api_base_url']}/{key}")
data[f"api_base_url_{key}"] = data.get(
f"api_base_url_{key}", f"{data['api_base_url']}/{key}"
)
return data
27 changes: 25 additions & 2 deletions src/defs/auto_pvs1.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ class AlteredRegionMode(AutoAcmgBaseEnum):
Exon = auto()


class GenomicStrand(AutoAcmgBaseEnum):
"""Enumeration for genomic strand."""

Plus = auto()
Minus = auto()

@staticmethod
def from_string(value: str):
"""Converts string to enum member if possible, otherwise returns None."""
strand_mapping = {
"STRAND_PLUS": "Plus",
"STRAND_MINUS": "Minus",
}
value_mapped = strand_mapping.get(value, value)
for member in GenomicStrand:
if member.name == value_mapped:
return member
return None


class TranscriptInfo(BaseModel):
"""Information about a transcript."""

Expand Down Expand Up @@ -143,7 +163,9 @@ class PVS1PredictionStrucVarPath(AutoAcmgBaseEnum):


#: Mapping of PVS1 prediction path to description for sequence variant
PVS1PredictionPathMapping: Dict[Union[PVS1PredictionSeqVarPath, PVS1PredictionStrucVarPath], str] = {
PVS1PredictionPathMapping: Dict[
Union[PVS1PredictionSeqVarPath, PVS1PredictionStrucVarPath], str
] = {
PVS1PredictionSeqVarPath.NotSet: "Not Set",
PVS1PredictionSeqVarPath.PTEN: "Special guideline for PTEN -> Predicted to undergo NMD",
PVS1PredictionSeqVarPath.NF1: (
Expand All @@ -153,7 +175,8 @@ class PVS1PredictionStrucVarPath(AutoAcmgBaseEnum):
"Predicted to undergo NMD -> Exon is absent from biologically-relevant transcript(s)"
),
PVS1PredictionSeqVarPath.NF3: (
"Not predicted to undergo NMD -> " "Truncated/altered region is critical to protein function"
"Not predicted to undergo NMD -> "
"Truncated/altered region is critical to protein function"
),
PVS1PredictionSeqVarPath.NF4: (
"Not predicted to undergo NMD -> "
Expand Down
14 changes: 10 additions & 4 deletions src/defs/seqvar.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
#: Regular expression for dbSNP
REGEX_DBSNP_ID = re.compile(r"^rs\d+$", re.IGNORECASE)
#: Regular expression for ClinVar
REGEX_CLINVAR_ID = re.compile(r"^(?P<accession>(?:RCV|VCV)\d{9})(?:\.(?P<version>\d+))?$", re.IGNORECASE)
REGEX_CLINVAR_ID = re.compile(
r"^(?P<accession>(?:RCV|VCV)\d{9})(?:\.(?P<version>\d+))?$", re.IGNORECASE
)


class SeqVar:
Expand All @@ -52,7 +54,9 @@ def __init__(
self.delete = delete.upper()
self.insert = insert.upper()
self.user_repr = (
user_repr if user_repr else f"{genome_release.name}-{self.chrom}-{pos}-{delete}-{insert}"
user_repr
if user_repr
else f"{genome_release.name}-{self.chrom}-{pos}-{delete}-{insert}"
)

def _normalize_chromosome(self, chrom: str) -> str:
Expand Down Expand Up @@ -140,7 +144,9 @@ def _parse_separated_seqvar(
raise ParseError(f"Unable to parse colon/hyphen separated seqvar: {value}")

genome_build_value = match.group("genome_build")
genome_build = GenomeRelease[genome_build_value] if genome_build_value else default_genome_release
genome_build = (
GenomeRelease[genome_build_value] if genome_build_value else default_genome_release
)
chrom = self._normalize_chrom(match.group("chrom"))
pos = int(match.group("pos"))
delete = match.group("delete").upper()
Expand Down Expand Up @@ -173,7 +179,7 @@ def _parse_canonical_spdi_seqvar(self, value: str) -> SeqVar:
raise ParseError(f"Unable to parse canonical SPDI variant: {value}")

sequence = match.group("sequence").upper()
pos = int(match.group("pos"))
pos = int(match.group("pos")) + 1 # SPDI is 0-based
delete = match.group("delete").upper()
insert = match.group("insert").upper()

Expand Down
4 changes: 3 additions & 1 deletion src/defs/strucvar.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def __init__(
self.start = start
self.stop = stop
self.user_repr = (
user_repr if user_repr else f"{sv_type.name}-{genome_release.name}-{self.chrom}-{start}-{stop}"
user_repr
if user_repr
else f"{sv_type.name}-{genome_release.name}-{self.chrom}-{start}-{stop}"
)

def _normalize_chromosome(self, chrom: str) -> str:
Expand Down
4 changes: 3 additions & 1 deletion src/pvs1/auto_pvs1.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ def predict(
strucvar_pvs1 = StrucVarPVS1(self.strucvar)
strucvar_pvs1.initialize()
strucvar_pvs1.verify_PVS1()
self.strucvar_prediction, self.strucvar_prediction_path = strucvar_pvs1.get_prediction()
self.strucvar_prediction, self.strucvar_prediction_path = (
strucvar_pvs1.get_prediction()
)
return self.strucvar_prediction, self.strucvar_prediction_path
except AutoAcmgBaseException as e:
logger.exception("Error occurred: {}", e)
Expand Down
Loading