|
2 | 2 | Utilities for common IgSeq tasks. |
3 | 3 | """ |
4 | 4 |
|
| 5 | +import os |
5 | 6 | import sys |
6 | 7 | import argparse |
7 | 8 | import logging |
|
17 | 18 | from . import vdj_gather |
18 | 19 | from . import vdj_match |
19 | 20 | from . import convert |
| 21 | +from . import identity |
20 | 22 | from . import show |
21 | 23 | from .util import IgSeqError |
22 | 24 | from .version import __version__ |
@@ -72,22 +74,42 @@ def main(arglist=None): |
72 | 74 | prefix = "[DRYRUN] " |
73 | 75 | _setup_log(args.verbose, args.quiet, prefix) |
74 | 76 | try: |
75 | | - if args_extra: |
76 | | - # If there were unparsed arguments, see if we're in one of the |
77 | | - # commands (currently just igblast) that can take extra |
78 | | - # pass-through arguments. If so pass them along, but if not, error |
79 | | - # out. |
80 | | - if args.func in [_main_igblast]: |
81 | | - args.func(args, args_extra) |
| 77 | + try: |
| 78 | + if args_extra: |
| 79 | + # If there were unparsed arguments, see if we're in one of the |
| 80 | + # commands (currently just igblast) that can take extra |
| 81 | + # pass-through arguments. If so pass them along, but if not, |
| 82 | + # error out. |
| 83 | + if args.func in [_main_igblast]: |
| 84 | + args.func(args, args_extra) |
| 85 | + else: |
| 86 | + parser.parse_args(args_extra) |
82 | 87 | else: |
83 | | - parser.parse_args(args_extra) |
84 | | - else: |
85 | | - args.func(args) |
86 | | - except IgSeqError as err: |
87 | | - sys.stderr.write( |
88 | | - f"\nigseq failed because: {err.message}\n" |
89 | | - "Considering adding -v or -vv to the command if the problem isn't clear.\n") |
90 | | - sys.exit(1) |
| 88 | + args.func(args) |
| 89 | + except IgSeqError as err: |
| 90 | + sys.stderr.write( |
| 91 | + f"\nigseq failed because: {err.message}\n" |
| 92 | + "Considering adding -v or -vv to the command if the problem isn't clear.\n") |
| 93 | + sys.exit(1) |
| 94 | + sys.stdout.flush() |
| 95 | + sys.stderr.flush() |
| 96 | + except BrokenPipeError: |
| 97 | + # If stdout and/or stderr were writing to a pipe and that pipe is now |
| 98 | + # closed, we'll swap in /dev/null for whichever it is to handle this |
| 99 | + # quietly and to prevent it from arising again when Python tries to |
| 100 | + # flush file handles on exit. |
| 101 | + # Adapted from |
| 102 | + # https://stackoverflow.com/questions/26692284 |
| 103 | + # https://docs.python.org/3/library/signal.html#note-on-sigpipe |
| 104 | + devnull = os.open(os.devnull, os.O_WRONLY) |
| 105 | + try: |
| 106 | + sys.stdout.flush() |
| 107 | + except BrokenPipeError: |
| 108 | + os.dup2(devnull, sys.stdout.fileno()) |
| 109 | + try: |
| 110 | + sys.stderr.flush() |
| 111 | + except BrokenPipeError: |
| 112 | + os.dup2(devnull, sys.stderr.fileno()) |
91 | 113 |
|
92 | 114 | def _main_getreads(args): |
93 | 115 | if args.no_counts: |
@@ -207,6 +229,17 @@ def _main_convert(args): |
207 | 229 | dummyqual=args.dummy_qual, |
208 | 230 | dry_run=args.dry_run) |
209 | 231 |
|
| 232 | +def _main_identity(args): |
| 233 | + colmap = args_to_colmap(args) |
| 234 | + identity.identity( |
| 235 | + path_in=args.input, |
| 236 | + path_out=args.output, |
| 237 | + path_ref=args.reference, |
| 238 | + fmt_in=args.input_format, |
| 239 | + fmt_in_ref=args.ref_format, |
| 240 | + colmap=colmap, |
| 241 | + dry_run=args.dry_run) |
| 242 | + |
210 | 243 | def _setup_log(verbose, quiet, prefix): |
211 | 244 | # Handle warnings via logging |
212 | 245 | logging.captureWarnings(True) |
@@ -269,6 +302,10 @@ def __setup_arg_parser(): |
269 | 302 | help="Convert FASTA/FASTQ/CSV/TSV", |
270 | 303 | description=rewrap(convert.__doc__), |
271 | 304 | formatter_class=argparse.RawDescriptionHelpFormatter) |
| 305 | + p_identity = subps.add_parser("identity", |
| 306 | + help="Calculate pairwise identities", |
| 307 | + description=rewrap(identity.__doc__), |
| 308 | + formatter_class=argparse.RawDescriptionHelpFormatter) |
272 | 309 | p_show = subps.add_parser("show", |
273 | 310 | help="show file contents", |
274 | 311 | description=rewrap(show.__doc__), |
@@ -463,6 +500,25 @@ def __setup_arg_parser(): |
463 | 500 | 'as text (e.g. use "I" for 40)') |
464 | 501 | p_convert.set_defaults(func=_main_convert) |
465 | 502 |
|
| 503 | + __add_common_args(p_identity) |
| 504 | + p_identity.add_argument("input", |
| 505 | + help="input file path, or a literal '-' for standard input") |
| 506 | + p_identity.add_argument("output", |
| 507 | + help="output file path, or a literal '-' for standard output") |
| 508 | + p_identity.add_argument("-r", "--reference", |
| 509 | + help="optional reference file path (default: use first query as ref)") |
| 510 | + p_identity.add_argument("--input-format", |
| 511 | + help="format of input " |
| 512 | + "(default: detect from input filename if possible)") |
| 513 | + p_identity.add_argument("--ref-format", |
| 514 | + help="format of reference " |
| 515 | + "(default: detect from reference filename if possible)") |
| 516 | + p_identity.add_argument("--col-seq-id", |
| 517 | + help="Name of column containing sequence IDs (for tabular input/output)") |
| 518 | + p_identity.add_argument("--col-seq", |
| 519 | + help="Name of column containing sequences (for tabular input/output)") |
| 520 | + p_identity.set_defaults(func=_main_identity) |
| 521 | + |
466 | 522 | return parser |
467 | 523 |
|
468 | 524 | def __add_common_args(obj): |
|
0 commit comments