FunVIP 0.3.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
src/validate_option.py ADDED
@@ -0,0 +1,1609 @@
1
+ # Validate option parsed from CommandParser in command.py
2
+ import copy
3
+ import os
4
+ import sys
5
+ import yaml
6
+ import builtins
7
+ import datetime
8
+ import re
9
+ import psutil
10
+ from funvip.src.logics import isvalidcolor
11
+ from funvip.src.tool import check_avx
12
+
13
+
14
+ # FunVIP Option class definition
15
+ class Option:
16
+ # Method option
17
+ class Method_Option:
18
+ def __init__(self):
19
+ self.search = "blast"
20
+ self.alignment = "mafft"
21
+ self.trim = "trimal"
22
+ self.modeltest = "none"
23
+ self.tree = "fasttree"
24
+
25
+ # Visualize option
26
+ class Visualize_Option:
27
+ def __init__(self):
28
+ self.bscutoff = 70
29
+ self.highlight = "#aa0000"
30
+ self.heightmultiplier = 6
31
+ self.maxwordlength = 48
32
+ self.backgroundcolor = ["#ffe0e0", "#ffefef"]
33
+ self.outgroupcolor = "#999999"
34
+ self.ftype = "Arial"
35
+ self.fsize = 10
36
+ self.fsize_bootstrap = 9
37
+
38
+ # Cluster option
39
+ class Cluster_Option:
40
+ def __init__(self):
41
+ self.cutoff = 0.95
42
+ self.evalue = 10
43
+ self.wordsize = 7
44
+ self.outgroupoffset = 20
45
+
46
+ # MAFFT option
47
+ class MAFFT_Option:
48
+ def __init__(self):
49
+ self.algorithm = "auto"
50
+ self.op = 1.3
51
+ self.ep = 0.1
52
+
53
+ # TrimAl option
54
+ class TrimAl_Option:
55
+ def __init__(self):
56
+ self.algorithm = "gt"
57
+ self.gt = 0.2 # Should be found
58
+
59
+ # Option will be generated by file, and iteratively
60
+ def __init__(self):
61
+ # Running options
62
+ self.query = []
63
+ self.db = []
64
+ self.gene = []
65
+ self.email = ""
66
+ self.api = ""
67
+ self.test = None
68
+ self.thread = "auto"
69
+ self.memory = f"{int(psutil.virtual_memory().total / (1024 ** 3))}G"
70
+ self.outdir = None
71
+ self.runname = None
72
+ self.mode = "identification"
73
+ self.continue_from_previous = False
74
+ self.criterion = "BIC"
75
+ self.allow_innertrimming = False
76
+ self.step = ""
77
+ self.level = "genus"
78
+ self.queryonly = True
79
+ self.confident = True
80
+ self.verbose = 2
81
+ self.maxoutgroup = 3
82
+ self.collapsedistcutoff = 0.01
83
+ self.collapsebscutoff = 101
84
+ self.bootstrap = 100
85
+ self.solveflat = True
86
+ self.regex = None
87
+ self.avx = True
88
+ self.cachedb = True
89
+ self.usecache = True
90
+ self.tableformat = "csv"
91
+ self.nosearchresult = False
92
+
93
+ # Method options
94
+ self.method = self.Method_Option()
95
+
96
+ # Visualization options
97
+ self.visualize = self.Visualize_Option()
98
+
99
+ # Cluster options
100
+ self.cluster = self.Cluster_Option()
101
+
102
+ # MAFFT options
103
+ self.mafft = self.MAFFT_Option()
104
+
105
+ # TrimAl options
106
+ self.trimal = self.TrimAl_Option()
107
+
108
+ # update values from given preset
109
+ def update_from_preset(self, preset_file):
110
+ # Try to parser preset file
111
+ with open(preset_file) as f:
112
+ try:
113
+ parser_dict = yaml.safe_load(f)
114
+ except:
115
+ print(f"{preset_file} is not a valid preset yaml file")
116
+ raise Exception
117
+
118
+ # Update loaded preset
119
+ for key in parser_dict:
120
+ # Basic options
121
+ if key.lower() in ("query"):
122
+ self.query = parser_dict[key]
123
+ elif key.lower() in ("db"):
124
+ self.db = parser_dict[key]
125
+ elif key.lower() in ("gene"):
126
+ self.gene = parser_dict[key]
127
+ elif key.lower() in ("email"):
128
+ self.email = parser_dict[key]
129
+ elif key.lower() in ("api"):
130
+ self.api = parser_dict[key]
131
+ elif key.lower() in ("thread"):
132
+ self.thread = parser_dict[key]
133
+ elif key.lower() in ("memory"):
134
+ self.memory = parser_dict[key]
135
+ elif key.lower() in ("outdir"):
136
+ self.outdir = parser_dict[key]
137
+ elif key.lower() in ("runname"):
138
+ self.runname = parser_dict[key]
139
+ elif key.lower() in ("mode"):
140
+ self.mode = parser_dict[key]
141
+ elif key.lower() in ("continue"):
142
+ self.continue_from_previous = parser_dict[key]
143
+ elif key.lower() in ("step"):
144
+ self.step = parser_dict[key]
145
+ elif key.lower() in ("level"):
146
+ self.level = parser_dict[key]
147
+ elif key.lower() in ("queryonly", "all"):
148
+ if key.lower() == "queryonly":
149
+ self.queryonly = parser_dict[key]
150
+ elif key.lower() == "all":
151
+ self.queryonly = ~parser_dicy[key]
152
+ elif key.lower() in ("verbose"):
153
+ self.verbose = parser_dict[key]
154
+ elif key.lower() in ("maxoutgroup"):
155
+ self.maxoutgroup = parser_dict[key]
156
+ elif key.lower() in ("collapsedistcutoff"):
157
+ self.collapsedistcutoff = parser_dict[key]
158
+ elif key.lower() in ("collapsebscutoff"):
159
+ self.collapsebscutoff = parser_dict[key]
160
+ elif key.lower() in ("bootstrap"):
161
+ self.bootstrap = parser_dict[key]
162
+ elif key.lower() in ("solveflat"):
163
+ self.solveflat = parser_dict[key]
164
+ elif key.lower() in ("regex"):
165
+ self.regex = parser_dict[key]
166
+ elif key.lower() in ("avx"):
167
+ self.avx = parser_dict[key]
168
+ elif key.lower() in ("allow-innertrimming"):
169
+ self.allow_innertrimming = parser_dict[key]
170
+ elif key.lower() in ("criterion"):
171
+ self.criterion = parser_dict[key]
172
+ elif key.lower() in ("cachedb"):
173
+ self.cachedb = parser_dict[key]
174
+ elif key.lower() in ("usecache"):
175
+ self.usecache = parser_dict[key]
176
+ elif key.lower() in ("tableformat"):
177
+ self.tableformat = parser_dict[key]
178
+ elif key.lower() in ("nosearchresult"):
179
+ self.nosearchresult = parser_dict[key]
180
+ elif key.lower() in ("confident"):
181
+ self.confident = parser_dict[key]
182
+
183
+ # Method options
184
+ elif key.lower() in ("search"):
185
+ self.method.search = parser_dict[key]
186
+ elif key.lower() in ("alignment"):
187
+ self.method.alignment = parser_dict[key]
188
+ elif key.lower() in ("trim"):
189
+ self.method.trim = parser_dict[key]
190
+ elif key.lower() in ("modeltest"):
191
+ self.method.modeltest = parser_dict[key]
192
+ elif key.lower() in ("tree"):
193
+ self.method.tree = parser_dict[key]
194
+
195
+ # Visualize options
196
+ elif key.lower() in ("bscutoff"):
197
+ self.visualize.bscutoff = parser_dict[key]
198
+ elif key.lower() in ("bootstrapcutoff"):
199
+ self.visualize.bscutoff = parser_dict[key]
200
+ elif key.lower() in ("highlight"):
201
+ self.visualize.highlight = parser_dict[key]
202
+ elif key.lower() in ("heightmultiplier"):
203
+ self.visualize.heightmultiplier = parser_dict[key]
204
+ elif key.lower() in ("maxwordlength"):
205
+ self.visualize.maxwordlength = parser_dict[key]
206
+ elif key.lower() in ("backgroundcolor"):
207
+ self.visualize.backgroundcolor = parser_dict[key]
208
+ elif key.lower() in ("outgroupcolor"):
209
+ self.visualize.outgroupcolor = parser_dict[key]
210
+ elif key.lower() in ("ftype"):
211
+ self.visualize.ftype = parser_dict[key]
212
+ elif key.lower() in ("fsize"):
213
+ self.visualize.fsize = parser_dict[key]
214
+ elif key.lower() in ("fsize_bootstrap"):
215
+ self.visualize.fsize_bootstrap = parser_dict[key]
216
+
217
+ # Cluster options
218
+ elif key.lower() in ("cluster-cutoff"):
219
+ self.cluster.evalue = parser_dict[key]
220
+ elif key.lower() in ("evalue", "cluster-evalue"):
221
+ self.cluster.evalue = parser_dict[key]
222
+ elif key.lower() in ("wordsize"):
223
+ self.cluster.wordsize = parser_dict[key]
224
+ elif key.lower() in ("outgroupoffset"):
225
+ self.cluster.wordsize = parser_dict[key]
226
+
227
+ # MAFFT options
228
+ elif key.lower() in ("mafft-algorithm"):
229
+ self.mafft.algorithm = parser_dict[key]
230
+ elif key.lower() in ("mafft-op"):
231
+ self.mafft.op = parser_dict[key]
232
+ elif key.lower() in ("mafft-ep"):
233
+ self.mafft.ep = parser_dict[key]
234
+
235
+ # TrimAl options
236
+ elif key.lower() in ("trimal-algorithm"):
237
+ self.trimal.algorithm = parser_dict[key]
238
+ elif key.lower() in ("trimal-gt"):
239
+ self.trimal.gt = parser_dict[key]
240
+ else:
241
+ print(f"cannot recoginze {key} in preset file as valid variable")
242
+
243
+ # update values from parser object
244
+ def update_from_parser(self, parser):
245
+ # Parsed terms will have higher priority from preset file
246
+ # test should not be parsed here (should be parsed in initialize option)
247
+
248
+ try:
249
+ if not parser.query is None:
250
+ self.query = parser.query
251
+ except:
252
+ pass
253
+
254
+ try:
255
+ if not parser.db is None:
256
+ self.db = parser.db
257
+ except:
258
+ pass
259
+
260
+ try:
261
+ if not parser.gene is None:
262
+ self.gene = parser.gene
263
+ except:
264
+ pass
265
+
266
+ try:
267
+ if not parser.email is None:
268
+ self.email = parser.email
269
+ except:
270
+ pass
271
+
272
+ try:
273
+ if not parser.api is None:
274
+ self.api = parser.api
275
+ except:
276
+ pass
277
+
278
+ try:
279
+ if not parser.thread is None:
280
+ self.thread = parser.thread
281
+ except:
282
+ pass
283
+
284
+ try:
285
+ if not parser.memory is None:
286
+ self.memory = parser.memory
287
+ except:
288
+ pass
289
+
290
+ try:
291
+ if not parser.runname is None:
292
+ self.runname = parser.runname
293
+ except:
294
+ pass
295
+
296
+ # Create runname directory in outdir location
297
+ try:
298
+ if not parser.outdir is None:
299
+ self.outdir = parser.outdir
300
+ except:
301
+ pass
302
+
303
+ try:
304
+ if not parser.mode is None:
305
+ self.mode = parser.mode
306
+ except:
307
+ pass
308
+
309
+ try:
310
+ if not parser.continue_from_previous is None:
311
+ self.continue_from_previous = parser.continue_from_previous
312
+ except:
313
+ pass
314
+
315
+ try:
316
+ if not parser.step is None:
317
+ self.step = parser.step
318
+ except:
319
+ pass
320
+
321
+ try:
322
+ if not parser.level is None:
323
+ self.level = parser.level
324
+ except:
325
+ pass
326
+
327
+ try:
328
+ if parser.all is True:
329
+ self.queryonly = not (parser.all)
330
+ except:
331
+ pass
332
+
333
+ try:
334
+ if not parser.confident is None:
335
+ self.confident = parser.confident
336
+ except:
337
+ pass
338
+
339
+ try:
340
+ if not parser.search is None:
341
+ self.method.search = parser.search
342
+ except:
343
+ pass
344
+
345
+ try:
346
+ if not parser.alignment is None:
347
+ self.method.alignment = parser.alignment
348
+ except:
349
+ pass
350
+
351
+ try:
352
+ if not parser.trim is None:
353
+ self.method.trim = parser.trim
354
+ except:
355
+ pass
356
+
357
+ try:
358
+ if not parser.modeltest is None:
359
+ self.method.modeltest = parser.modeltest
360
+ except:
361
+ pass
362
+
363
+ try:
364
+ if not parser.tree is None:
365
+ self.method.tree = parser.tree
366
+ except:
367
+ pass
368
+
369
+ try:
370
+ if not parser.bscutoff is None:
371
+ self.visualize.bscutoff = parser.bscutoff
372
+ except:
373
+ pass
374
+
375
+ try:
376
+ if not parser.highlight is None:
377
+ self.visualize.highlight = parser.highlight
378
+ except:
379
+ pass
380
+
381
+ try:
382
+ if not parser.heightmultiplier is None:
383
+ self.visualize.heightmultiplier = parser.heightmultiplier
384
+ except:
385
+ pass
386
+
387
+ try:
388
+ if not parser.maxwordlength is None:
389
+ self.visualize.maxwordlength = parser.maxwordlength
390
+ except:
391
+ pass
392
+
393
+ try:
394
+ if not parser.backgroundcolor is None:
395
+ self.visualize.backgroundcolor = parser.backgroundcolor
396
+ except:
397
+ pass
398
+
399
+ try:
400
+ if not parser.outgroupcolor is None:
401
+ self.visualize.outgroupcolor = parser.outgroupcolor
402
+ except:
403
+ pass
404
+
405
+ try:
406
+ if not parser.ftype is None:
407
+ self.visualize.ftype = parser.ftype
408
+ except:
409
+ pass
410
+
411
+ try:
412
+ if not parser.fsize is None:
413
+ self.visualize.fsize = parser.fsize
414
+ except:
415
+ pass
416
+
417
+ try:
418
+ if not parser.fsize_bootstrap is None:
419
+ self.visualize.fsize_bootstrap = parser.fsize_bootstrap
420
+ except:
421
+ pass
422
+
423
+ try:
424
+ if not parser.verbose is None:
425
+ self.verbose = parser.verbose
426
+ except:
427
+ pass
428
+
429
+ try:
430
+ if not parser.maxoutgroup is None:
431
+ self.maxoutgroup = parser.maxoutgroup
432
+ except:
433
+ pass
434
+
435
+ try:
436
+ if not parser.collapsedistcutoff is None:
437
+ self.collapsedistcutoff = parser.collapsedistcutoff
438
+ except:
439
+ pass
440
+
441
+ try:
442
+ if not parser.collapsebscutoff is None:
443
+ self.collapsebscutoff = parser.collapsebscutoff
444
+ except:
445
+ pass
446
+
447
+ try:
448
+ if not parser.bootstrap is None:
449
+ self.bootstrap = parser.bootstrap
450
+ except:
451
+ pass
452
+
453
+ try:
454
+ if parser.solveflat is True:
455
+ self.solveflat = parser.solveflat
456
+ except:
457
+ pass
458
+
459
+ try:
460
+ if not parser.regex is None:
461
+ self.regex = parser.regex
462
+ except:
463
+ pass
464
+
465
+ try:
466
+ if not parser.cluster_cutoff is None:
467
+ self.cluster.cutoff = parser.cluster_cutoff
468
+ except:
469
+ pass
470
+
471
+ try:
472
+ if not parser.cluster_evalue is None:
473
+ self.cluster.evalue = parser.cluster_evalue
474
+ except:
475
+ pass
476
+
477
+ try:
478
+ if not parser.cluster_wordsize is None:
479
+ self.cluster.wordsize = parser.cluster_wordsize
480
+ except:
481
+ pass
482
+
483
+ try:
484
+ if not parser.cluster_outgroupoffset is None:
485
+ self.cluster.outgroupoffset = parser.cluster_outgroupoffset
486
+ except:
487
+ pass
488
+
489
+ try:
490
+ if not parser.mafft_algorithm is None:
491
+ self.mafft.algorithm = parser.mafft_algorithm
492
+ except:
493
+ pass
494
+
495
+ try:
496
+ if not parser.mafft_op is None:
497
+ self.mafft.op = parser.mafft_op
498
+ except:
499
+ pass
500
+
501
+ try:
502
+ if not parser.mafft_ep is None:
503
+ self.mafft.ep = parser.mafft_ep
504
+ except:
505
+ pass
506
+
507
+ try:
508
+ if not parser.trimal_algorithm is None:
509
+ self.trimal.algorithm = parser.trimal_algorithm
510
+ except:
511
+ pass
512
+
513
+ try:
514
+ if not parser.trimal_gt is None:
515
+ self.trimal.gt = parser.trimal_gt
516
+ except:
517
+ pass
518
+
519
+ try:
520
+ if parser.noavx is True:
521
+ self.avx = False
522
+ except:
523
+ pass
524
+
525
+ try:
526
+ if not parser.criterion is None:
527
+ self.criterion = parser.criterion
528
+ except:
529
+ pass
530
+
531
+ try:
532
+ if not parser.allow_innertrimming is None:
533
+ self.allow_innertrimming = parser.allow_innertrimming
534
+ except:
535
+ pass
536
+
537
+ try:
538
+ if parser.cachedb is True:
539
+ self.cachedb = parser.cachedb
540
+ except:
541
+ pass
542
+
543
+ try:
544
+ if parser.usecache is True:
545
+ self.usecache = parser.usecache
546
+ except:
547
+ pass
548
+
549
+ try:
550
+ if not parser.tableformat is None:
551
+ self.tableformat = parser.tableformat
552
+ except:
553
+ pass
554
+
555
+ try:
556
+ if parser.nosearchresult is True:
557
+ self.nosearchresult = parser.nosearchresult
558
+ except:
559
+ pass
560
+
561
+ # Valiate current status preset
562
+ def validate(self):
563
+ list_info = []
564
+ list_error = []
565
+ list_warning = []
566
+
567
+ # query
568
+ # Check if query files are in valid directories
569
+ if not (type(self.query) is list):
570
+ list_error.append(f"Type for query should be list format")
571
+ else:
572
+ flag_query = 0
573
+ for query in self.query:
574
+ if not (type(query) is str):
575
+ list_error.append(f"query {query} is not a valid string format")
576
+ flag_query = 1
577
+
578
+ if flag_query == 0:
579
+ # Adjust query location if query is in test dataset
580
+ if not (self.test is None):
581
+ path_query = os.path.abspath(
582
+ f"{os.path.dirname(__file__)}/../test_dataset/{self.test}/Query"
583
+ )
584
+
585
+ self.query = [f"{path_query}/{query}" for query in self.query]
586
+
587
+ for query in self.query:
588
+ # Check if query location is valid
589
+ try:
590
+ if not (os.path.exists(f"{query}")):
591
+ list_error.append(f"{query} is not a valid query path")
592
+ except:
593
+ pass
594
+
595
+ # db
596
+ # Check if db files are in valid
597
+ if not (type(self.db) is list):
598
+ list_error.append(f"Type for db should be list format")
599
+ else:
600
+ flag_db = 0
601
+ for db in self.db:
602
+ if not (type(db) is str):
603
+ list_error.append(f"db {db} is not a valid string format")
604
+ flag_db = 1
605
+
606
+ if flag_db == 0:
607
+ # Adjust db location if db is in test dataset
608
+ if not (self.test is None):
609
+ path_db = os.path.abspath(
610
+ f"{os.path.dirname(__file__)}/../test_dataset/{self.test}/DB"
611
+ )
612
+ self.db = [f"{path_db}/{db}" for db in self.db]
613
+
614
+ for db in self.db:
615
+ # Check if DB location is valid
616
+ try:
617
+ if not (os.path.exists(f"{db}")):
618
+ list_error.append(f"{db} is not a valid db path")
619
+ except:
620
+ pass
621
+
622
+ # Change all db path into linux style
623
+
624
+ # gene
625
+ # Check if gene names are valid strings (comparing with db will be performed in parsing)
626
+ if not (type(self.gene) is list):
627
+ list_error.append(f"Type for gene should be list format")
628
+ elif len(self.gene) < 1:
629
+ list_error.append(f"At least one gene should be designated")
630
+ else:
631
+ self.gene = [g.strip() for g in self.gene]
632
+ for gene in self.gene:
633
+ if not (type(gene) is str):
634
+ list_error.append(f"gene {gene} is not a valid string format")
635
+ else:
636
+ if sys.platform == "win32" and " " in gene:
637
+ list_error.append(
638
+ f"You cannot use space for genename in windows platform : {gene}"
639
+ )
640
+
641
+ # email
642
+ # Check if email is in valid format
643
+ if self.email is None:
644
+ pass
645
+ else:
646
+ if not (type(self.email) is str):
647
+ list_error.append(f"Type for email should be string")
648
+ email_pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
649
+ if not (re.match(email_pattern, self.email)):
650
+ if self.email == "":
651
+ list_warning.append(
652
+ f"Email not provided. If your database includes GenBank accession, please provide email"
653
+ )
654
+ else:
655
+ list_error.append(f"Email {self.email} is not a valid email")
656
+
657
+ # api
658
+ # Check if api number is in valid format
659
+ # Connect to entrez once to check api is valid
660
+ if self.api is None:
661
+ pass
662
+ else:
663
+ if not (type(self.api) is str):
664
+ list_error.append(f"Type for api should be string")
665
+
666
+ # Check either email or api exists
667
+ if self.email is None and self.api is None:
668
+ list_warning.append(
669
+ f"Email and API both were not given. May cause error when downloading sequence"
670
+ )
671
+
672
+ # thread
673
+ # Check if thread is valid int
674
+ # If thread is over system thread, 0, or negative, adjust it to maximum
675
+ if not (type(self.thread) is int):
676
+ if not (type(self.thread) is str):
677
+ list_warning.append(
678
+ f"Type for thread should be int but {self.thread} was given. Using {os.cpu_count()} for default"
679
+ )
680
+ else:
681
+ if not (self.thread.lower() == "auto"):
682
+ list_warning.append(
683
+ f"Type for thread should be int but {self.thread} was given. Using {os.cpu_count()} for default"
684
+ )
685
+
686
+ self.thread = os.cpu_count()
687
+ elif self.thread <= 0:
688
+ list_info.append(f"thread adjusted to {os.cpu_count()}")
689
+ self.thread = os.cpu_count()
690
+ elif self.thread >= os.cpu_count():
691
+ list_info.append(
692
+ f"thread exceeded system maximum. Adjusting to {os.cpu_count()}"
693
+ )
694
+ self.thread = os.cpu_count()
695
+
696
+ # memory
697
+ # Check if memory is in valid format
698
+ # memory should be in XXG format like '16G'
699
+ # If memory is over system memory adjust it to maximum
700
+ # If memory is less than 4G, adjust it to 4G
701
+ if type(self.memory) is int or type(self.memory) is float:
702
+ list_warning.append(
703
+ f"Type for memory should be in format of 'nG' such as '16G', but {self.memory} was given. Considering {self.memory} as gigabytes"
704
+ )
705
+ self.memory = f"{int(self.memory)}G"
706
+
707
+ elif not type(self.memory) is str:
708
+ list_warning.append(
709
+ f"Type for memory should be in format of 'nG' such as '16G', but {self.memory} was given. Using maximum system memory"
710
+ )
711
+ self.memory = f"{int(psutil.virtual_memory().total / (1024 ** 3))}G"
712
+ elif not self.memory.endswith("G"):
713
+ list_warning.append(
714
+ f"Type for memory should be in format of 'nG' such as '16G', but {self.memory} was given. Using maximum system memory"
715
+ )
716
+ self.memory = f"{int(psutil.virtual_memory().total / (1024 ** 3))}G"
717
+ else:
718
+ try:
719
+ float(self.memory[:-1])
720
+ except:
721
+ list_warning.append(
722
+ f"Type for memory should be in format of 'nG' such as '16G', but {self.memory} was given. Using maximum system memory"
723
+ )
724
+ self.memory = f"{int(psutil.virtual_memory().total / (1024 ** 3))}G"
725
+
726
+ if float(self.memory[:-1]) < 4:
727
+ list_warning.append(
728
+ f"At least 4G of memory required for FunVIP. Try using 4G"
729
+ )
730
+ if psutil.virtual_memory().total / (1024**3) < 4:
731
+ list_error.append(f"Less than 4G of memory (RAM) detected ! Aborted")
732
+ else:
733
+ self.memory = "4G"
734
+
735
+ # outdir
736
+ # Check if outdirectory is valid path
737
+ # If outdir does not exists, try making directory
738
+ if self.outdir:
739
+ if not (os.path.exists(self.outdir)):
740
+ list_warning.append(
741
+ f"Out directory location {self.outdir} does not exists try making it"
742
+ )
743
+ try:
744
+ os.makedirs(self.outdir, mode=0o755, exist_ok=True)
745
+ except:
746
+ list_error.append(
747
+ f"Failed making outdir location {self.outdir}. Try checking directory or permissions"
748
+ )
749
+
750
+ # Change outdir to absolute path
751
+ self.outdir = str(os.path.abspath(self.outdir))
752
+ else:
753
+ self.outdir = os.getcwd()
754
+
755
+ # continue - continue should be validated before runname designated
756
+ # If continue is not None, give True, else, give False
757
+ # If continue is true, check if runname directory is valid
758
+ # If outdir is not distinctively selected, warn that it will overwrite previous run
759
+ if self.continue_from_previous is False or self.continue_from_previous is None:
760
+ self.continue_from_previous = False
761
+ else:
762
+ self.continue_from_previous = True
763
+
764
+ # runname
765
+ # Check if runname is valid string
766
+ # Check for existing runname
767
+ invalid_char = r'[\\/:*?"<>|]|\.|\s$'
768
+
769
+ # Use current time stamp if no runname given
770
+ if self.runname is None:
771
+ now = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
772
+ self.runname = now
773
+
774
+ if not (type(self.runname) is str):
775
+ list_error.append(f"runname should be string")
776
+ elif re.search(invalid_char, self.runname.strip()):
777
+ list_error.append(f"invalid characters in runname")
778
+ elif " " in self.runname.strip() and sys.platform == "win32":
779
+ list_error.append(
780
+ f"You should not include space for runname in windows platform"
781
+ )
782
+ else: # if valid runname
783
+ if self.continue_from_previous is True:
784
+ # Check if continue available
785
+ if not (os.path.exists(f"{self.outdir}/{self.runname}")):
786
+ list_error.append(
787
+ f"continue option selected, but previous run directory {self.runname} not found in {self.outdir}"
788
+ )
789
+ else:
790
+ # If already previous path exists
791
+ if os.path.exists(f"{self.outdir}/{self.runname}"):
792
+ # memo user designated runname
793
+ ori_runname = self.runname
794
+ # if same name exists, try to add numbers at the end to discriminate
795
+ i = 1
796
+ while 1:
797
+ if os.path.exists(f"{self.outdir}/{ori_runname}_{i}"):
798
+ i += 1
799
+ else:
800
+ self.runname = f"{ori_runname}_{i}"
801
+ break
802
+
803
+ # step
804
+ # Check if continue is True. If False, warn that step will be ignored
805
+ # Check if step is valid :
806
+ step = [
807
+ "setup",
808
+ "search",
809
+ "cluster",
810
+ "align",
811
+ "trim",
812
+ "concatenate",
813
+ "modeltest",
814
+ "tree",
815
+ "visualize",
816
+ "report",
817
+ ]
818
+
819
+ if self.continue_from_previous is True:
820
+ if not (type(self.step) is str):
821
+ list_error.append(f"step should be string")
822
+ elif not (self.step in step):
823
+ list_error.append(f"step should be one of {str(step)}")
824
+ else:
825
+ if not self.step is None:
826
+ list_warning.append(
827
+ f"--continue is not designated, --step will be ignored"
828
+ )
829
+
830
+ # level
831
+ """
832
+ # Check for valid level : subseries, series, subsection, section, subtribe,
833
+ tribe, subfamily, family, suborder, order, subclass, class, subdivision,
834
+ division, subphylum, phylum, subkingdom, kingdom
835
+ """
836
+ level = [
837
+ "subseries",
838
+ "series",
839
+ "subsection",
840
+ "section",
841
+ "genus",
842
+ "subtribe",
843
+ "tribe",
844
+ "subfamily",
845
+ "family",
846
+ "suborder",
847
+ "order",
848
+ "subclass",
849
+ "class",
850
+ "subdivision",
851
+ "division",
852
+ "subphylum",
853
+ "phylum",
854
+ "subkingdom",
855
+ "kingdom",
856
+ ]
857
+
858
+ if not (type(self.level) is str):
859
+ list_error.append(f"--level should be string")
860
+ elif not (self.level in level):
861
+ list_error.append(
862
+ f"--level should be one of {str(level)}, not {self.level}"
863
+ )
864
+
865
+ # mode
866
+ # Check for valid mode : identificaion or validation
867
+ mode = ["identification", "validation"]
868
+ if not (type(self.mode) is str):
869
+ list_error.append(f"--mode should be string")
870
+ elif not (self.mode in mode):
871
+ list_error.append(f"--mode should be one of {str(mode)}")
872
+
873
+ # queryonly
874
+ # If queryonly is not None, give True, else, give False
875
+ # If no query is empty, raise Exception
876
+ if not (self.queryonly is None or self.queryonly is False):
877
+ self.queryonly = True
878
+ else:
879
+ self.queryonly = False
880
+
881
+ if self.query:
882
+ if len(self.query) == 0:
883
+ list_warning.append(f"No query detected, activating --all")
884
+ self.queryonly = False
885
+ else:
886
+ list_warning.append(f"No query detected, activating --all")
887
+ self.queryonly = False
888
+
889
+ # confident
890
+ # If confident is not None, give True, else, give False
891
+ # "confident" option can be only used when queryonly is True
892
+ if not (self.confident is None or self.confident is False):
893
+ if self.queryonly is True:
894
+ self.confident = True
895
+ else:
896
+ list_warning.append(
897
+ f"Option --confident can be only used without --all. Ignoring it"
898
+ )
899
+ self.confident = False
900
+ else:
901
+ self.confident = False
902
+
903
+ # search
904
+ # Check if search method is one of default, blast, mmseqs
905
+ # Adjust misspellings
906
+ # If given value is default, change it to mmseqs
907
+ # If does not met to any of above, raise Exception
908
+ search = ["blast", "mmseqs"]
909
+ search_adjust = {
910
+ "default": "blast",
911
+ "blast": "blast",
912
+ "blastn": "blast",
913
+ "mmseq": "mmseqs",
914
+ "mmseqs": "mmseqs",
915
+ "mmseq2": "mmseqs",
916
+ "mmseqs2": "mmseqs",
917
+ }
918
+ if not (type(self.method.search) is str):
919
+ list_error.append(f"search method should be string")
920
+ else:
921
+ self.method.search = search_adjust[self.method.search.lower()]
922
+ if not (self.method.search in search):
923
+ list_error.append(f"search method should be one of {str(search)}")
924
+
925
+ # alignment
926
+ # Check if alignment method is one of default, mafft
927
+ # Adjust misspellings
928
+ # If given value is default, change it to mafft
929
+ # If does not met to any of above, raise Exception
930
+ alignment = ["mafft"]
931
+ alignment_adjust = {
932
+ "default": "mafft",
933
+ "mafft": "mafft",
934
+ }
935
+ if not (type(self.method.alignment) is str):
936
+ list_error.append(f"align method should be string")
937
+ else:
938
+ self.method.alignment = alignment_adjust[self.method.alignment.lower()]
939
+ if not (self.method.alignment in alignment):
940
+ list_error.append(f"align method should be one of {str(alignment)}")
941
+
942
+ # trim
943
+ # Check if trimming method is one of default, none, trimal, gblocks
944
+ # Adjust misspellings
945
+ # If given value is default, change it to none
946
+ # If does not met to any of above, raise Exception
947
+ trim = ["none", "trimal", "gblocks"]
948
+ trim_adjust = {
949
+ "default": "none",
950
+ "trimal": "trimal",
951
+ "trimai": "trimal",
952
+ "gblocks": "gblocks",
953
+ "gblock": "gblocks",
954
+ "none": "none",
955
+ }
956
+
957
+ if self.method.trim is None:
958
+ self.method.trim = "none"
959
+
960
+ if not (type(self.method.trim) is str):
961
+ list_error.append(f"trim method should be string")
962
+ else:
963
+ self.method.trim = trim_adjust[self.method.trim.lower()]
964
+ if not (self.method.trim in trim):
965
+ list_error.append(
966
+ f"trim method should be one of {str(trim_adjust.keys())}"
967
+ )
968
+
969
+ # model
970
+ # Check if search method is one of default, none, modeltest-ng, iqtree
971
+ # Adjust misspellings
972
+ # If given value is default, change it to none
973
+ # If does not met to any of above, raise Exception
974
+ modeltest = ["none", "modeltest-ng", "iqtree"]
975
+ modeltest_adjust = {
976
+ "default": "none",
977
+ "modeltest": "modeltest-ng",
978
+ "modeltestng": "modeltest-ng",
979
+ "modeltest-ng": "modeltest-ng",
980
+ "jmodeltest": "modeltest-ng",
981
+ "iqtree": "iqtree",
982
+ "modelfinder": "iqtree",
983
+ "none": "none",
984
+ }
985
+
986
+ if self.method.modeltest is None:
987
+ self.method.modeltest = "none"
988
+
989
+ if not (type(self.method.modeltest) is str):
990
+ list_error.append(f"modeltest method should be string")
991
+
992
+ else:
993
+ if self.method.modeltest.lower() == "jmodeltest":
994
+ list_warning.append(
995
+ f"option jmodeltest will be substituted to modeltest-ng"
996
+ )
997
+ self.method.modeltest = modeltest_adjust[self.method.modeltest.lower()]
998
+ if not (self.method.modeltest in modeltest):
999
+ list_error.append(f"modeltest method should be one of {str(modeltest)}")
1000
+ if self.method.modeltest == "modeltest-ng" and sys.platform == "win32":
1001
+ list_error.append(
1002
+ f"Modeltest-ng is currently not available in windows platform. Please select other modeltest methods or use linux platform"
1003
+ )
1004
+
1005
+ # tree
1006
+ # Check if search method is one of default, fasttree, raxml, iqtree
1007
+ # Adjust misspellings
1008
+ # If given value is default, change it to fasttree
1009
+ # If does not met to any of above, raise Exception
1010
+ tree = ["fasttree", "iqtree", "raxml"]
1011
+ tree_adjust = {
1012
+ "default": "fasttree",
1013
+ "fasttree": "fasttree",
1014
+ "iqtree": "iqtree",
1015
+ "raxml": "raxml",
1016
+ }
1017
+
1018
+ if not (type(self.method.tree) is str):
1019
+ list_error.append(f"tree method should be string")
1020
+ else:
1021
+ self.method.tree = tree_adjust[self.method.tree.lower()]
1022
+ if not (self.method.tree in tree):
1023
+ list_error.append(f"tree method should be one of {str(tree)}")
1024
+
1025
+ # bscutoff
1026
+ # Check if bootstrap cutoff is in optimal range 0~
1027
+ # If lower than 0, set to 0
1028
+ # If upper than 100, warn that bootstrap will not be shown
1029
+ # If between 0 and 1, multiply 100 and warn
1030
+ if not (type(self.visualize.bscutoff) is int):
1031
+ try:
1032
+ if self.visualize.bscutoff > 0 and self.visualize.bscutoff < 1:
1033
+ list_warning.append(
1034
+ f"bscutoff should be in integer range, but given one seems to between 0 and 1. Automatically multiplying 100"
1035
+ )
1036
+ self.visualize.bscutoff = int(self.visualize.bscutoff * 100)
1037
+ # If failed solving
1038
+ if self.visualize.bscutoff > 0 and self.visualize.bscutoff < 1:
1039
+ list_error.append(f"Failed to solve bscutoff range")
1040
+
1041
+ else:
1042
+ self.visualize.bscutoff = int(self.visualize.bscutoff)
1043
+
1044
+ if self.visualize.bscutoff < 0:
1045
+ self.visualize.bscutoff = 0
1046
+ if self.visualize.bscutoff > 100:
1047
+ list_warning.append(
1048
+ f"bscutoff is over 100, all bootstrap will not seen"
1049
+ )
1050
+
1051
+ except:
1052
+ list_error.append(f"--bscutoff should be integer")
1053
+
1054
+ # highlight
1055
+ # highlight should be availabe svg colors or unicode
1056
+ if not (type(self.visualize.highlight) is str):
1057
+ list_error.append(f"--highlight should be string")
1058
+ else:
1059
+ if not (isvalidcolor(self.visualize.highlight)):
1060
+ list_error.append(
1061
+ f"in --highlight, color {color} does not seems to be valid svg color nor hex code"
1062
+ )
1063
+ else:
1064
+ self.visualize.highlight = self.visualize.highlight.lower()
1065
+
1066
+ # heightmultiplier
1067
+ # heightmultiplier should be positive float
1068
+ try:
1069
+ self.visualize.heightmultiplier = float(self.visualize.heightmultiplier)
1070
+ if self.visualize.heightmultiplier <= 0:
1071
+ list_warning.append(
1072
+ "--heightmultiplier should be positive, setting to default value, 6"
1073
+ )
1074
+ self.visualize.heightmultiplier = 6
1075
+ except:
1076
+ list_error.append(
1077
+ "--heightmultiplier should be positive floating point number"
1078
+ )
1079
+
1080
+ # maxwordlength
1081
+ # maxwordlength should be positive int
1082
+ try:
1083
+ self.visualize.maxwordlength = int(self.visualize.maxwordlength)
1084
+ if self.visualize.maxwordlength <= 0:
1085
+ list_warning.append(
1086
+ "--maxwordlength should be positive int, setting to default value, 48"
1087
+ )
1088
+ self.visualize.maxwordlength = 48
1089
+ except:
1090
+ list_error.append(
1091
+ "--maxwordlength should be positive floating point number"
1092
+ )
1093
+
1094
+ # backgroundcolor
1095
+ # backgroundcolor should be list of colors
1096
+ flag_backgroundcolor = 0
1097
+ if not (type(self.visualize.backgroundcolor) is list):
1098
+ list_error.append(f"--backgroundcolor should be list")
1099
+ flag_backgroundcolor += 1
1100
+ elif len(self.visualize.backgroundcolor) < 1:
1101
+ list_error.append(
1102
+ f"At least one color should be designated for --backgroundcolor"
1103
+ )
1104
+ flag_backgroundcolor += 1
1105
+ else:
1106
+ for color in self.visualize.backgroundcolor:
1107
+ if not isvalidcolor(color):
1108
+ list_error.append(
1109
+ f"color {color} does not seems to be valid svg color nor hex code"
1110
+ )
1111
+ flag_backgroundcolor += 1
1112
+ if flag_backgroundcolor == 0:
1113
+ self.visualize.backgroundcolor = [
1114
+ x.lower() for x in self.visualize.backgroundcolor
1115
+ ]
1116
+
1117
+ # outgroupcolor
1118
+ # outgroupcolor should be availabe svg colors or unicode
1119
+ if not (type(self.visualize.outgroupcolor) is str):
1120
+ list_error.append(f"--outgroupcolor should be string")
1121
+ else:
1122
+ if not (isvalidcolor(self.visualize.outgroupcolor)):
1123
+ list_error.append(
1124
+ f"in --outgroupcolor, color {color} does not seems to be valid svg color nor hex code"
1125
+ )
1126
+ else:
1127
+ self.visualize.outgroupcolor = self.visualize.outgroupcolor.lower()
1128
+
1129
+ # ftype
1130
+ if not (type(self.visualize.ftype) is str):
1131
+ list_error.append(f"--ftype should be valid font name (string)")
1132
+ else:
1133
+ # Fix this when enough data has been collected
1134
+ pass
1135
+
1136
+ # fsize
1137
+ try:
1138
+ float(self.visualize.fsize)
1139
+ if self.visualize.fsize < 0:
1140
+ list_warning.append(
1141
+ f"--fsize should be positive float. Setting to default, 10"
1142
+ )
1143
+ self.visualize.fsize = 10
1144
+ except:
1145
+ list_error.append(f"--fsize should be positive float")
1146
+
1147
+ # fsize_bootstrap
1148
+ try:
1149
+ float(self.visualize.fsize_bootstrap)
1150
+ if self.visualize.fsize_bootstrap < 0:
1151
+ list_warning.append(
1152
+ f"--fsize_bootstrap should be positive float. Setting to default, 10"
1153
+ )
1154
+ self.visualize.fsize_bootstrap = 9
1155
+ except:
1156
+ list_error.append(f"--fsize_bootstrap should be positive float")
1157
+
1158
+ # verbose
1159
+ # If not 0,1,2,3 raise error with warning
1160
+ # 0: quiet, 1: info, 2: warning, 3: debug, default : 2
1161
+ try:
1162
+ self.verbose = int(self.verbose)
1163
+ if not self.verbose in (0, 1, 2, 3):
1164
+ list_error.append(
1165
+ f"verbose should be one of 0,1,2,3 - 0: only error, 1: warning, 2: info, 3: debug"
1166
+ )
1167
+ except:
1168
+ list_error.append(
1169
+ f"verbose should be one of 0,1,2,3 - 0: only error, 1: warning, 2: info, 3: debug"
1170
+ )
1171
+
1172
+ # maxoutgroup
1173
+ # Check if maxoutgroup is in optimal range 0~
1174
+ # if lower than 1, change into 1 and warn
1175
+ try:
1176
+ self.maxoutgroup = int(self.maxoutgroup)
1177
+ if self.maxoutgroup < 1:
1178
+ list_warning.append(f"invalid maxoutgroup, automatically selecting 1")
1179
+ except:
1180
+ list_warning.append(f"invalid maxoutgroup, automatically selecting 1")
1181
+ self.maxoutgroup = 1
1182
+
1183
+ # collapsedistcutoff
1184
+ # Check if collapse distance cutoff is in optimal range 0~
1185
+ # if lower than 0, change into 0 and warn
1186
+ try:
1187
+ self.collapsedistcutoff = float(self.collapsedistcutoff)
1188
+ except:
1189
+ list_warning.append(f"invalid collapsedistcutoff, change into 0")
1190
+ self.collapsedistcutoff = 0
1191
+
1192
+ # collapsebscutoff
1193
+ # Check if collapse bootstrap cutoff is in optimal range 0~
1194
+ # If higher than 101, change into 100 and warn
1195
+ # If lower than 0, change into 0 and warn
1196
+ if not (type(self.collapsebscutoff) is int):
1197
+ try:
1198
+ if self.collapsebscutoff > 0 and self.collapsebscutoff < 1:
1199
+ list_warning.append(
1200
+ f"collapsebscutoff should be in integer range, but given one seems to between 0 and 1. Automatically multiplying 100"
1201
+ )
1202
+ self.collapsebscutoff = int(self.collapsebscutoff * 100)
1203
+ # If failed solving
1204
+ if self.collapsebscutoff > 0 and self.collapsebscutoff < 1:
1205
+ list_error.append(f"Failed to solve collapse bscutoff range")
1206
+
1207
+ else:
1208
+ self.collapsebscutoff = int(self.collapsebscutoff)
1209
+
1210
+ if self.collapsebscutoff < 0:
1211
+ self.collapsebscutoff = 0
1212
+ if self.collapsebscutoff > 100:
1213
+ list_warning.append(
1214
+ f"collapsebscutoff is over 100, all bootstrap will not seen"
1215
+ )
1216
+
1217
+ except:
1218
+ list_error.append(f"bscutoff should be integer")
1219
+
1220
+ # bootstrap
1221
+ # bootstrap number should be int
1222
+ # If IQTREE selected and bootstrap number under 1000, change to 1000
1223
+ if self.method.tree == "fasttree":
1224
+ list_warning.append(
1225
+ f"Fasttree does not supports bootstrap. --bootstrap will be ignored"
1226
+ )
1227
+ self.bootstrap = None
1228
+ else:
1229
+ try:
1230
+ self.bootstrap = int(self.bootstrap)
1231
+ if self.method.tree == "iqtree" and self.bootstrap < 1000:
1232
+ list_warning.append(
1233
+ "iqtree requires at least 1000 bootstrap. adjusting to 1000"
1234
+ )
1235
+ self.bootstrap = 1000
1236
+ elif self.method.tree == "raxml" and self.bootstrap < 0:
1237
+ list_warning.append(
1238
+ "bootstrap should not be negative. adjusting to 1"
1239
+ )
1240
+ self.bootstrap = 1
1241
+
1242
+ except:
1243
+ list_error.append(
1244
+ f"bootstrap should be integer when iqtree or raxml are selected"
1245
+ )
1246
+
1247
+ # solveflat
1248
+ # If solveflat is not None, give True, else, give False
1249
+ if self.solveflat is False or self.solveflat is None:
1250
+ self.solveflat = False
1251
+ else:
1252
+ self.solveflat = True
1253
+
1254
+ # regex
1255
+ # Regex list for queries
1256
+ # Validate if regex are valid regex
1257
+ if self.regex is None:
1258
+ pass
1259
+ elif not (type(self.regex)) is list:
1260
+ list_error.append("regex should be given in list of pattern")
1261
+ else:
1262
+ for regex in self.regex:
1263
+ try:
1264
+ re.compile(regex)
1265
+ except:
1266
+ list_error.append(f"regex {regex} is not a valid python regex")
1267
+
1268
+ # cluster-evalue
1269
+ # E-value cutoff for clustering - should be positive
1270
+ try:
1271
+ self.cluster.evalue = float(self.cluster.evalue)
1272
+ if self.cluster.evalue < 0:
1273
+ list_warning.append("evalue should be positive, setting to 1")
1274
+ self.cluster.evalue = 1
1275
+ except:
1276
+ list_error.append("evalue should be positive floating point number")
1277
+
1278
+ # cluster-wordsize
1279
+ # Wordsize option for clustering - should be int and not less than 7
1280
+ try:
1281
+ self.cluster.wordsize = int(self.cluster.wordsize)
1282
+ if self.cluster.wordsize < 7:
1283
+ list_warning.append(
1284
+ "wordsize should be int not less than 7. Changing to 7"
1285
+ )
1286
+ self.cluster.wordsize = 7
1287
+ except:
1288
+ list_error.append("wordsize should be int not less than 7. Changing to 7")
1289
+
1290
+ # cluster-outgroupoffset
1291
+ # outgroupoffset for clustering - should be 0 or positive
1292
+ try:
1293
+ self.cluster.outgroupoffset = int(self.cluster.outgroupoffset)
1294
+ if self.cluster.outgroupoffset < 0:
1295
+ list_warning.append(
1296
+ "outgroupoffset should be 0 or positive, setting to 0"
1297
+ )
1298
+ self.cluster.outgroupoffset = 0
1299
+ except:
1300
+ list_error.append("outgroupoffset should be 0 or positive integer")
1301
+
1302
+ # mafft-algorithm
1303
+ # mafft-algorithm - auto, l-ins-i
1304
+ # mafft algorithm commands should be revised
1305
+ if self.method.alignment == "mafft":
1306
+ try:
1307
+ self.mafft.algorithm = str(self.mafft.algorithm)
1308
+ if not (
1309
+ self.mafft.algorithm.lower()
1310
+ in (
1311
+ "auto",
1312
+ "l-ins-i",
1313
+ "linsi",
1314
+ "localpair",
1315
+ "g-ins-i",
1316
+ "ginsi",
1317
+ "globalpair",
1318
+ )
1319
+ ):
1320
+ list_error.append(
1321
+ f"Invalid mafft algorithm {self.mafft.algorithm}. Currently available algorithms are auto and l-ins-i"
1322
+ )
1323
+ elif self.mafft.algorithm.lower() in ("l-ins-i", "linsi", "localpair"):
1324
+ self.mafft.algorithm = "localpair"
1325
+ elif self.mafft.algorithm.lower() in ("g-ins-i", "ginsi", "globalpair"):
1326
+ self.mafft.algorithm = "globalpair"
1327
+
1328
+ except:
1329
+ list_error.append(
1330
+ f"Invalid mafft algorithm {self.mafft.algorithm}. Currently available algorithms are auto and l-ins-i"
1331
+ )
1332
+
1333
+ # mafft-op
1334
+ # mafft gap opening penalty should be 0 or positive
1335
+ if self.method.alignment == "mafft":
1336
+ try:
1337
+ self.mafft.op = float(self.mafft.op)
1338
+ if self.mafft.op < 0:
1339
+ list_warning.append(
1340
+ "mafft op value should be positive, setting to 1.2"
1341
+ )
1342
+ self.mafft.op = 1.2
1343
+ except:
1344
+ list_error.append("mafft op value should be 0 or positive")
1345
+
1346
+ # mafft-ep
1347
+ # mafft gap extension penalty should be 0 or positive
1348
+ if self.method.alignment == "mafft":
1349
+ try:
1350
+ self.mafft.ep = float(self.mafft.ep)
1351
+ if self.mafft.ep < 0:
1352
+ list_warning.append(
1353
+ "mafft ep value should be positive, setting to 1.2"
1354
+ )
1355
+ self.mafft.ep = 0.1
1356
+ except:
1357
+ list_error.append("mafft ep value should be 0 or positive")
1358
+
1359
+ # trimal-algorithm
1360
+ # trimal algorithm, should be either gt
1361
+ # if auto, set gt
1362
+ if self.method.trim.lower() == "trimal":
1363
+ try:
1364
+ self.trimal.algorithm = str(self.trimal.algorithm)
1365
+ if not (self.trimal.algorithm.lower() in ("auto", "gt")):
1366
+ list_warning(
1367
+ f"Invalid trimal algorithm {self.trimal.algorithm}. Chaniging to gt"
1368
+ )
1369
+ self.trimal.algorithm = "gt"
1370
+ except:
1371
+ list_error.append(
1372
+ f"Invalid trimal algorithm {self.trimal.algorithm}. Currently avilable algorithms are auto and gt"
1373
+ )
1374
+
1375
+ # trimal-gt
1376
+ # trimal gt value, should be between 0 and 1
1377
+ # If trimming method is not trimal or trimal-algorithm is not 0, warn
1378
+ # Change to default value
1379
+ if self.method.trim.lower() == "trimal":
1380
+ try:
1381
+ self.trimal.gt = float(self.trimal.gt)
1382
+ if self.trimal.gt < 0 and self.trimal.gt >= 1:
1383
+ list_warning.append(
1384
+ f"trimal gt value should be between 1 and 0, setting to 0.2"
1385
+ )
1386
+ self.trimal.gt = 0.2
1387
+ except:
1388
+ list_error.append(
1389
+ f"Invalid trimal gt value {self.trimal.gt}. gt should be between 1 and 0"
1390
+ )
1391
+
1392
+ # noavx
1393
+ # default : False
1394
+ # if used, set True
1395
+ # if avx is not available command, set True
1396
+ # Negative boolean and save as avx
1397
+ if check_avx() is False and self.avx is True:
1398
+ list_warning.append(f"AVX is not available. Changing --noavx to True")
1399
+ self.avx = False
1400
+
1401
+ # criterion
1402
+ # default : BIC
1403
+ # Should be one of AIC, AICc, BIC
1404
+ try:
1405
+ self.criterion = str(self.criterion)
1406
+ if not (self.criterion.lower() in ("aic", "aicc", "bic")):
1407
+ list_error.append(
1408
+ "Modeltest criterion should be one of AIC, AICc and BIC"
1409
+ )
1410
+ else:
1411
+ # For prevent capital errors
1412
+ if self.criterion.lower() == "aic":
1413
+ self.criterion = "AIC"
1414
+ elif self.criterion.lower() == "aicc":
1415
+ self.criterion = "AICc"
1416
+ elif self.criterion.lower() == "bic":
1417
+ self.criterion = "BIC"
1418
+ else:
1419
+ list_error.append(
1420
+ f"Somewhat error in parsing criterion, {self.criterion}"
1421
+ )
1422
+
1423
+ except:
1424
+ list_error.append("Modeltest criterion should be one of AIC, AICc and BIC")
1425
+
1426
+ # cachedb
1427
+ # If cachedb is not None, give True, else, give False
1428
+ if self.cachedb is False or self.cachedb is None:
1429
+ self.cachedb = False
1430
+ else:
1431
+ self.cachedb = True
1432
+
1433
+ # usecache
1434
+ # If usecache is not None, give True, else, give False
1435
+ if self.usecache is False or self.usecache is None:
1436
+ self.usecache = False
1437
+ else:
1438
+ self.usecache = True
1439
+
1440
+ # tableformat
1441
+ # should be either [csv, xlsx, parquet, feather]
1442
+ # if ftr, change it to feather
1443
+ # if excel, change it to xlsx
1444
+ try:
1445
+ self.tableformat = str(self.tableformat)
1446
+ if not (
1447
+ self.tableformat.lower()
1448
+ in ("csv", "tsv", "xlsx", "parquet", "ftr", "feather")
1449
+ ):
1450
+ list_error.append(
1451
+ "tableformat should be one of csv, tsv, xlsx, parquet, ftr, or feather"
1452
+ )
1453
+
1454
+ except:
1455
+ list_error.append(
1456
+ "tableformat should be one of csv, tsv, xlsx, parquet, ftr, or feather"
1457
+ )
1458
+
1459
+ # nosearchresult
1460
+ # If nosearchresult is not None, give True, else, give False
1461
+ if self.nosearchresult is True:
1462
+ self.nosearchresult = True
1463
+ else:
1464
+ self.nosearchresult = False
1465
+
1466
+ # Printing logs while parsing validate options
1467
+ # Written in print functions, because logging can be loaded after option parsing
1468
+ print("--INFO--")
1469
+ if len(list_info) > 0:
1470
+ for info in list_info:
1471
+ print(f"[INFO] {info}")
1472
+ else:
1473
+ print(f"[INFO] No information to declare during input validation")
1474
+
1475
+ print("\n")
1476
+ print("--WARNING--")
1477
+ if len(list_warning) > 0:
1478
+ for warning in list_warning:
1479
+ print(f"[WARNING] {warning}")
1480
+ else:
1481
+ print(f"[INFO] No warnings to declare during input validation")
1482
+
1483
+ print("\n")
1484
+ print("--ERROR--")
1485
+ if len(list_error) > 0:
1486
+ for error in list_error:
1487
+ print(f"[ERROR] {error}")
1488
+ else:
1489
+ print(f"[INFO] No errors to declare during input validation")
1490
+
1491
+ print("\n\n")
1492
+
1493
+ if len(list_error) > 0:
1494
+ raise Exception
1495
+
1496
+ # Returning option parsing logs
1497
+ return list_info, list_warning, list_error
1498
+
1499
+
1500
+ ### Main function in validate_option.py
1501
+ def initialize_option(parser, path_run):
1502
+ #### Before start
1503
+ # Backup original parser
1504
+ ori_parser = copy.deepcopy(parser)
1505
+
1506
+ ### Initialize option
1507
+ opt = Option()
1508
+
1509
+ ### test
1510
+ # 1. Check if test name is valid (Avaliable list : Penicillium)
1511
+ # 2. Change preset to test
1512
+
1513
+ path_test = f"{os.path.dirname(__file__)}/../test_dataset"
1514
+ path_preset = f"{os.path.dirname(__file__)}/../preset"
1515
+
1516
+ overwrite_preset = None
1517
+ # *1 - if test option selected
1518
+ if not (parser.test is None):
1519
+ # If preset should be overwrited, save it
1520
+ if not (parser.preset is None):
1521
+ overwrite_preset = copy.deepcopy(parser.preset)
1522
+
1523
+ # *1 - if test value is valid dataset
1524
+ if parser.test.lower() in os.listdir(path_test):
1525
+ if os.path.exists(f"{path_test}/{parser.test.lower()}/preset.yaml"):
1526
+ parser.preset = f"{path_test}/{parser.test.lower()}/preset.yaml"
1527
+ print(f"test dataset {parser.preset} selected")
1528
+ else:
1529
+ print("Something wrong with test dataset option")
1530
+ raise Exception
1531
+
1532
+ # Set runname to current time
1533
+ now = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
1534
+
1535
+ # Give default locations if no outdir and runname given
1536
+ if parser.outdir is None:
1537
+ opt.outdir = f"{path_run}"
1538
+ if parser.runname is None:
1539
+ f"{parser.test.lower()}_{now}"
1540
+
1541
+ opt.test = parser.test.lower()
1542
+
1543
+ else:
1544
+ print("Invalid test dataset. Please --test option")
1545
+ raise Exception
1546
+
1547
+ ### preset
1548
+ # 1. Check if lower case is fast or accurate
1549
+ # 2. Else, check if preset is parsable YAML file
1550
+ # 3. If parsable json file, parse it and update parser
1551
+ if not (parser.preset is None):
1552
+ if str(parser.preset).lower() == "fast": # *1 - fast mode
1553
+ parser.preset = f"{path_preset}/fast.yaml" # - connect to fast.yaml
1554
+ print("Using fast preset as default option")
1555
+ opt.update_from_preset(parser.preset)
1556
+ elif str(parser.preset).lower() == "accurate": # *1 - accurate mode
1557
+ parser.preset = f"{path_preset}/accurate.yaml" # - connect to accurate.yaml
1558
+ print("Using accurate preset as default option")
1559
+ opt.update_from_preset(parser.preset)
1560
+ else:
1561
+ if os.path.exists(parser.preset):
1562
+ print(f"[DEBUG] {parser.preset}")
1563
+ opt.update_from_preset(parser.preset)
1564
+ else:
1565
+ print(f"Cannot find preset file : {parser.preset}")
1566
+ raise Exception
1567
+
1568
+ # Overwrite test preset if needed
1569
+ if not (overwrite_preset is None):
1570
+ if str(overwrite_preset).lower() == "fast": # *1 - fast mode
1571
+ overwrite_preset = f"{path_preset}/fast.yaml" # - connect to fast.yaml
1572
+ print("Using fast preset as default option")
1573
+ opt.update_from_preset(overwrite_preset)
1574
+ elif str(overwrite_preset).lower() == "accurate": # *1 - accurate mode
1575
+ overwrite_preset = (
1576
+ f"{path_preset}/accurate.yaml" # - connect to accurate.yaml
1577
+ )
1578
+ print("Using accurate preset as default option")
1579
+ opt.update_from_preset(overwrite_preset)
1580
+ else:
1581
+ if os.path.exists(overwrite_preset):
1582
+ print(f"[DEBUG] {overwrite_preset}")
1583
+ opt.update_from_preset(overwrite_preset)
1584
+ else:
1585
+ print(f"Cannot find preset file : {overwrite_preset}")
1586
+ raise Exception
1587
+
1588
+ ### Then, update other options in parser
1589
+ opt.update_from_parser(ori_parser)
1590
+
1591
+ ### validate
1592
+ list_info, list_warning, list_error = opt.validate()
1593
+
1594
+ ### stdout input options
1595
+ # print("[OPTIONS INPUT]")
1596
+
1597
+ ## Print and log output options
1598
+ for attr, value in opt.__dict__.items():
1599
+ if isinstance(value, (str, float, bool, int, list, type(None))):
1600
+ list_info.append(f"Option {attr} : {value}")
1601
+ # print(f"Option {attr} : {value}")
1602
+ else:
1603
+ for attr_, value_ in value.__dict__.items():
1604
+ list_info.append(f"Option {attr}-{attr_}: {value_}")
1605
+ # print(f"Option {attr}-{attr_}: {value_}")
1606
+
1607
+ print("------------------------------------")
1608
+
1609
+ return opt, list_info, list_warning, list_error