Skip to content
Open
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
11 changes: 10 additions & 1 deletion src/predectorutils/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
for each database release. The latest version will always we the default one.
"""

from pkg_resources import resource_filename
from importlib.resources import files

import json
import xgboost as xgb


def resource_filename(package: str, resource: str) -> str:
"""Return the filesystem path to a data file bundled in the package.

Drop-in replacement for the deprecated
``pkg_resources.resource_filename``.
"""
return str(files(package).joinpath(resource))


def get_interesting_pfam_ids(version: str = "latest") -> list[str]:
fname = resource_filename(__name__, "hmms_of_interest.json")
with open(fname, "r") as handle:
Expand Down
18 changes: 13 additions & 5 deletions src/predectorutils/gff.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,19 @@ def parse(
if string.strip() in (".", ""):
return cls()

fields = (
f.split("=", maxsplit=1)
for f
in string.strip(" ;").split(";")
)
# Split on ';', then on '='. Some tools (e.g. funannotate) emit values
# containing unescaped ';' characters, which is invalid GFF3 (';' in a
# value should be percent-encoded as %3B) but common in practice --
# e.g. gene names like "SULTR3;4". A piece without '=' is treated as a
# continuation of the previous value with the split ';' restored,
# rather than crashing.
fields: list[list[str]] = []
for piece in string.strip(" ;").split(";"):
if "=" in piece:
fields.append(piece.split("=", maxsplit=1))
elif len(fields) > 0:
fields[-1][1] = f"{fields[-1][1]};{piece}"
# A leading piece with no '=' has nothing to attach to; skip it.

if strip_quote:
kvpairs: dict[str, str] = {
Expand Down
41 changes: 32 additions & 9 deletions src/predectorutils/subcommands/ipr_to_gff.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ def cli(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--namespace",
type=str,
default=NAMESPACE,
help="The XML dom namespace. This is mostly included for developers."
default=None,
help=(
"The XML dom namespace. By default this is auto-detected from "
"the root element of the XML file. This is mostly included for "
"developers."
)
)
return

Expand All @@ -63,10 +67,17 @@ def get_source(

def get_tag(
element: ET.Element,
namespace: str
namespace: Optional[str] = None
) -> str:
assert element.tag.startswith(namespace), element
return element.tag[len(namespace):]
# Strip any leading "{namespace-uri}" prefix to get the local tag name.
# We intentionally ignore the specific namespace URI so that the parser
# keeps working when InterProScan changes it between versions (e.g.
# http://www.ebi.ac.uk/interpro/resources/schemas/interproscan5 ->
# https://ftp.ebi.ac.uk/pub/software/unix/iprscan/5/schemas).
tag = element.tag
if tag.startswith("{"):
return tag[tag.index("}") + 1:]
return tag


class Signature(NamedTuple):
Expand Down Expand Up @@ -333,7 +344,9 @@ def process_hmmer3(
itree: Iterator[tuple[str, ET.Element]],
ips_version: Optional[str],
query_id: Optional[str],
namespace: str = NAMESPACE
namespace: str = NAMESPACE,
match_tag: str = "hmmer3-match",
location_tag: str = "hmmer3-location",
) -> Iterator[GFFRecord]:
assert ips_version is not None
assert query_id is not None
Expand All @@ -348,7 +361,7 @@ def process_hmmer3(
event, element = next(itree)
tag = get_tag(element, namespace)

if (event == "end") and (tag == "hmmer3-match"):
if (event == "end") and (tag == match_tag):
break
if (event == "start") and (tag == "signature"):
signature = process_signature(element, itree)
Expand All @@ -361,7 +374,7 @@ def process_hmmer3(
goterms = signature.goterms
kind = signature.kind

elif (event == "start") and (tag == "hmmer3-location"):
elif (event == "start") and (tag == location_tag):
start = element.attrib["start"]
end = element.attrib["end"]
env_start = element.attrib["env-start"]
Expand Down Expand Up @@ -1357,7 +1370,7 @@ def inner(records: Iterable[GFFRecord]):
def inner( # noqa: C901
infile: TextIO,
outfile: TextIO,
namespace: str,
namespace: Optional[str] = None,
):
itree = ET.iterparse(
infile,
Expand Down Expand Up @@ -1399,6 +1412,16 @@ def inner( # noqa: C901
elif (event == "start") and (tag == "hmmer3-match"):
hmm = process_hmmer3(element, itree, ips_version, query_id)
printer(hmm)
elif (event == "start") and (tag == "funfamhmmer3-match"):
hmm = process_hmmer3(
element,
itree,
ips_version,
query_id,
match_tag="funfamhmmer3-match",
location_tag="funfamhmmer3-location",
)
printer(hmm)
elif (event == "start") and (tag == "panther-match"):
hmm = process_panther(element, itree, ips_version, query_id)
printer(hmm)
Expand Down