33import logging
44import json
55import itertools as it
6- from typing import Dict
6+ from typing import Dict , Iterable
77
88import pandas as pd
99from Bio import SeqIO
1010from Bio .SeqRecord import SeqRecord
11+ from Bio .SeqFeature import SeqFeature
1112from Bio .Seq import Seq
1213
1314
@@ -68,6 +69,54 @@ def calculate_ns_sites(codons: dict, genetic_code: dict) -> pd.DataFrame:
6869 return pd .DataFrame ({"feature" : features , "N" : N_sites , "S" : S_sites })
6970
7071
72+ def iter_features_filtering (features : Iterable [SeqFeature ], included : Dict [str , str ], excluded : Dict [str , str ]) -> Iterable [SeqFeature ]:
73+ # No filters
74+ if len (included ) == 0 and len (excluded ) == 0 :
75+ logging .debug ("Selecting all features" )
76+ return iter (features )
77+ # Only inclusion filter
78+ elif len (included ) == 0 and len (excluded ) != 0 :
79+ logging .debug (f"Selecting features excluding all of { excluded } " )
80+ return (
81+ feature for feature in features
82+ if all (
83+ (qualifier_value not in excluded .get (qualifier_key , []))
84+ for qualifier_key in excluded .keys ()
85+ for qualifier_value in feature .qualifiers .get (qualifier_key , [])
86+ )
87+ )
88+ # Only exclusion filter
89+ elif len (included ) != 0 and len (excluded ) == 0 :
90+ logging .debug (f"Selecting features including any of { included } " )
91+ return (
92+ feature for feature in features
93+ if any (
94+ (qualifier_value in included .get (qualifier_key , []))
95+ for qualifier_key in included .keys ()
96+ for qualifier_value in feature .qualifiers .get (qualifier_key , [])
97+ )
98+ )
99+ # Inclusion then exclusion filter
100+ else :
101+ logging .debug (f"Selecting features including any of { included } and then excluding all of { excluded } " )
102+ included_features = (
103+ feature for feature in features
104+ if any (
105+ (qualifier_value in included .get (qualifier_key , []))
106+ for qualifier_key in included .keys ()
107+ for qualifier_value in feature .qualifiers .get (qualifier_key , [])
108+ )
109+ )
110+ return (
111+ feature for feature in included_features
112+ if all (
113+ (qualifier_value not in excluded .get (qualifier_key , []))
114+ for qualifier_key in excluded .keys ()
115+ for qualifier_value in feature .qualifiers .get (qualifier_key , [])
116+ )
117+ )
118+
119+
71120def main ():
72121
73122 logging .basicConfig (
@@ -85,29 +134,11 @@ def main():
85134 logging .info ("Reading input FASTA" )
86135 record = read_monofasta (snakemake .input .fasta )
87136
88- if len (snakemake .params .features ) == 0 :
89- logging .debug ("Selecting all features" )
90- feature_iterator = iter (gb .features )
91- else :
92- included = snakemake .params .features .get ("INCLUDE" , {})
93- excluded = snakemake .params .features .get ("EXCLUDE" , {})
94- logging .debug (f"Selecting features including any of { included } and excluding all of { excluded } " )
95- feature_iterator = (
96- feature for feature in gb .features
97- if any (
98- (qualifier_value in included .get (qualifier_key , []))
99- for qualifier_key in included .keys ()
100- for qualifier_value in feature .qualifiers .get (qualifier_key , [])
101- ) and all (
102- (qualifier_value not in excluded .get (qualifier_key , []))
103- for qualifier_key in excluded .keys ()
104- for qualifier_value in feature .qualifiers .get (qualifier_key , [])
105- )
106- )
107-
108137 logging .info ("Extracting CDS" )
109138 coding_records = {}
110- for feature in feature_iterator :
139+ included = snakemake .params .features .get ("INCLUDE" , {})
140+ excluded = snakemake .params .features .get ("EXCLUDE" , {})
141+ for feature in iter_features_filtering (gb .features , included , excluded ):
111142 logging .debug (
112143 "Processing SeqFeature: "
113144 f"ID={ feature .id } type={ feature .type } location={ feature .location } "
@@ -121,6 +152,7 @@ def main():
121152 elif identifier in coding_records :
122153 logging .warning (f"Identifier '{ identifier } ' is already among coding records and will not be replaced by feature at { feature .location } " )
123154 else :
155+ logging .debug (f"Adding feature" )
124156 coding_records [identifier ] = feature .extract (record )
125157
126158 logging .info (f"Splitting { len (coding_records )} records into codons" )
0 commit comments