From 0541ad9c0ea6c85a204bc0e3043c035d62c61c33 Mon Sep 17 00:00:00 2001 From: Victor Lin <13424970+victorlin@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:37:00 -0700 Subject: [PATCH 1/4] Use `is` to compare against None From PEP 8 recommendations: Comparisons to singletons like None should always be done with is or is not, never the equality operators. --- scripts/swap_colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/swap_colors.py b/scripts/swap_colors.py index fc84743c4..058a28e58 100644 --- a/scripts/swap_colors.py +++ b/scripts/swap_colors.py @@ -8,7 +8,7 @@ parser.add_argument('--custom_colors', default=None, type=str, help="Path to .tsv or .csv with custom color ramps; will fall back to nextstrain default colors if not provided.") args = parser.parse_args().__dict__ -if args['jsons'] == None: +if args['jsons'] is None: try: jsons = glob('./*meta.json') assert len(jsons) > 0 From ffe1c804fc9117fde00896c3eb05615fba74ec79 Mon Sep 17 00:00:00 2001 From: Victor Lin <13424970+victorlin@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:17:51 -0700 Subject: [PATCH 2/4] Add test showing current behavior --- .../cram/filter-exclude-where-multiple.t | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/functional/filter/cram/filter-exclude-where-multiple.t diff --git a/tests/functional/filter/cram/filter-exclude-where-multiple.t b/tests/functional/filter/cram/filter-exclude-where-multiple.t new file mode 100644 index 000000000..f0b810926 --- /dev/null +++ b/tests/functional/filter/cram/filter-exclude-where-multiple.t @@ -0,0 +1,39 @@ +Setup + + $ source "$TESTDIR"/_setup.sh + +Create metadata file + + $ cat >metadata.tsv <<~~ + > strain region + > SEQ_1 A + > SEQ_2 B + > SEQ_3 C + > ~~ + +Scenario 1: Run command with one --exclude-where flag and multiple values + + $ ${AUGUR} filter \ + > --metadata metadata.tsv \ + > --exclude-where "region=A" "region=B" \ + > --output-strains filtered_strains.txt > /dev/null + +Both exclusions are applied. + + $ cat filtered_strains.txt + SEQ_3 + + +Scenario 2: Run command with two --exclude-where flags + + $ ${AUGUR} filter \ + > --metadata metadata.tsv \ + > --exclude-where "region=A" \ + > --exclude-where "region=B" \ + > --output-strains filtered_strains.txt > /dev/null + +Only the last exclusion is applied. + + $ cat filtered_strains.txt + SEQ_1 + SEQ_3 From adf1e7806866119a237a850dab71a5b76fa22dee Mon Sep 17 00:00:00 2001 From: Victor Lin <13424970+victorlin@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:57:05 -0700 Subject: [PATCH 3/4] Allow repeating an option that takes multiple values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many command line arguments take multiple values. Previously, to utilize this feature, all values must be specified after a single option flag (e.g. --exclude-where 'region=A' 'region=B'). If options were set using separate option flags (e.g. --exclude-where 'region=A' --exclude-where 'region=B'), only the last flag would be used, which is unintuitive. This commit enables all option flags to be used. Done across the codebase by adding action='extend' to all options that use nargs='+' or '*' and did not already specify an action. A side effect of action='extend' is that it extends instead of replacing a default value defined in add_argument. For arguments that use a custom default value, use a custom argparse action ExtendOverwriteDefault, based on the similarly named AppendOverwriteDefault from Nextstrain CLI¹. ¹ --- augur/align.py | 2 +- augur/ancestral.py | 2 +- augur/argparse_.py | 17 +++++++++++++++++ augur/clades.py | 4 ++-- augur/curate/__init__.py | 4 ++-- augur/curate/format_dates.py | 4 ++-- augur/curate/titlecase.py | 6 +++--- augur/distance.py | 10 +++++----- augur/export_v1.py | 3 ++- augur/export_v2.py | 13 +++++++------ augur/filter/__init__.py | 17 +++++++++-------- augur/frequencies.py | 11 ++++++----- augur/lbi.py | 6 +++--- augur/mask.py | 2 +- augur/measurements/concat.py | 2 +- augur/measurements/export.py | 8 ++++---- augur/parse.py | 4 ++-- augur/refine.py | 9 +++++---- augur/titers.py | 8 ++++---- augur/traits.py | 7 ++++--- augur/translate.py | 2 +- scripts/diff_jsons.py | 6 ++++-- scripts/diff_trees.py | 4 +++- scripts/s3.py | 4 ++-- scripts/swap_colors.py | 5 ++++- scripts/traits_from_json.py | 7 +++++-- scripts/tree_to_JSON.py | 10 +++++----- .../filter/cram/filter-exclude-where-multiple.t | 3 +-- 28 files changed, 106 insertions(+), 74 deletions(-) diff --git a/augur/align.py b/augur/align.py index 7c2a7af2d..6dc1a002d 100644 --- a/augur/align.py +++ b/augur/align.py @@ -24,7 +24,7 @@ def register_arguments(parser): Kept as a separate function than `register_parser` to continue to support unit tests that use this function to create argparser. """ - parser.add_argument('--sequences', '-s', required=True, nargs="+", metavar="FASTA", help="sequences to align") + parser.add_argument('--sequences', '-s', required=True, nargs="+", action="extend", metavar="FASTA", help="sequences to align") parser.add_argument('--output', '-o', default="alignment.fasta", help="output file (default: %(default)s)") parser.add_argument('--nthreads', type=nthreads_value, default=1, help="number of threads to use; specifying the value 'auto' will cause the number of available CPU cores on your system, if determinable, to be used") diff --git a/augur/ancestral.py b/augur/ancestral.py index bf0fba0f2..932d86072 100644 --- a/augur/ancestral.py +++ b/augur/ancestral.py @@ -315,7 +315,7 @@ def register_parser(parent_subparsers): ) amino_acid_options_group.add_argument('--annotation', help='GenBank or GFF file containing the annotation') - amino_acid_options_group.add_argument('--genes', nargs='+', help="genes to translate (list or file containing list)") + amino_acid_options_group.add_argument('--genes', nargs='+', action='extend', help="genes to translate (list or file containing list)") amino_acid_options_group.add_argument('--translations', type=str, help="translated alignments for each CDS/Gene. " "Currently only supported for FASTA-input. Specify the file name via a " "template like 'aa_sequences_%%GENE.fasta' where %%GENE will be replaced " diff --git a/augur/argparse_.py b/augur/argparse_.py index e5b0c8c00..6a535b430 100644 --- a/augur/argparse_.py +++ b/augur/argparse_.py @@ -76,3 +76,20 @@ class HideAsFalseAction(Action): """ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, option_string[2:6] != 'hide') + + +class ExtendOverwriteDefault(Action): + """ + Similar to the core argparse ``extend`` action, but overwrites the argument + ``default``, if any, instead of appending to it. + + Thus, the ``default`` value is not included when the option is given and + may be a non-list value if desired. + """ + def __call__(self, parser, namespace, value, option_string = None): + current = getattr(namespace, self.dest, None) + + if current is parser.get_default(self.dest) or current is None: + current = [] + + setattr(namespace, self.dest, [*current, *value]) diff --git a/augur/clades.py b/augur/clades.py index bb39ec511..71b868713 100644 --- a/augur/clades.py +++ b/augur/clades.py @@ -341,8 +341,8 @@ def parse_nodes(tree_file, node_data_files): def register_parser(parent_subparsers): parser = parent_subparsers.add_parser("clades", help=__doc__) parser.add_argument('--tree', required=True, help="prebuilt Newick -- no tree will be built if provided") - parser.add_argument('--mutations', required=True, metavar="NODE_DATA_JSON", nargs='+', help='JSON(s) containing ancestral and tip nucleotide and/or amino-acid mutations ') - parser.add_argument('--reference', nargs='+', help=SUPPRESS) + parser.add_argument('--mutations', required=True, metavar="NODE_DATA_JSON", nargs='+', action='extend', help='JSON(s) containing ancestral and tip nucleotide and/or amino-acid mutations ') + parser.add_argument('--reference', nargs='+', action='extend', help=SUPPRESS) parser.add_argument('--clades', required=True, metavar="TSV", type=str, help='TSV file containing clade definitions by amino-acid') parser.add_argument('--output-node-data', type=str, metavar="NODE_DATA_JSON", help='name of JSON file to save clade assignments to') parser.add_argument('--membership-name', type=str, default="clade_membership", help='Key to store clade membership under; use "None" to not export this') diff --git a/augur/curate/__init__.py b/augur/curate/__init__.py index 7e012c1b8..fd1888311 100644 --- a/augur/curate/__init__.py +++ b/augur/curate/__init__.py @@ -6,7 +6,7 @@ from collections import deque from textwrap import dedent -from augur.argparse_ import add_command_subparsers +from augur.argparse_ import ExtendOverwriteDefault, add_command_subparsers from augur.errors import AugurError from augur.io.json import dump_ndjson, load_ndjson from augur.io.metadata import DEFAULT_DELIMITERS, InvalidDelimiter, read_table_to_dict, read_metadata_with_sequences, write_records_to_tsv @@ -53,7 +53,7 @@ def create_shared_parser(): help="Name of the metadata column that contains the record identifier for reporting duplicate records. " "Uses the first column of the metadata file if not provided. " "Ignored if also providing a FASTA file input.") - shared_inputs.add_argument("--metadata-delimiters", default=DEFAULT_DELIMITERS, nargs="+", + shared_inputs.add_argument("--metadata-delimiters", default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="Delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") shared_inputs.add_argument("--fasta", diff --git a/augur/curate/format_dates.py b/augur/curate/format_dates.py index c7ccef72b..a58da7c7f 100644 --- a/augur/curate/format_dates.py +++ b/augur/curate/format_dates.py @@ -18,9 +18,9 @@ def register_parser(parent_subparsers): help=__doc__) required = parser.add_argument_group(title="REQUIRED") - required.add_argument("--date-fields", nargs="+", + required.add_argument("--date-fields", nargs="+", action="extend", help="List of date field names in the record that need to be standardized.") - required.add_argument("--expected-date-formats", nargs="+", + required.add_argument("--expected-date-formats", nargs="+", action="extend", help="Expected date formats that are currently in the provided date fields, " + "defined by standard format codes as listed at " + "https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes. " + diff --git a/augur/curate/titlecase.py b/augur/curate/titlecase.py index 94cbbc7d8..e0d6017b8 100755 --- a/augur/curate/titlecase.py +++ b/augur/curate/titlecase.py @@ -14,13 +14,13 @@ def register_parser(parent_subparsers): help = __doc__) required = parser.add_argument_group(title="REQUIRED") - required.add_argument("--titlecase-fields", nargs="*", + required.add_argument("--titlecase-fields", nargs="*", action="extend", help="List of fields to convert to titlecase.", required=True) optional = parser.add_argument_group(title="OPTIONAL") - optional.add_argument("--articles", nargs="*", + optional.add_argument("--articles", nargs="*", action="extend", help="List of articles that should not be converted to titlecase.") - optional.add_argument("--abbreviations", nargs="*", + optional.add_argument("--abbreviations", nargs="*", action="extend", help="List of abbreviations that should not be converted to titlecase, keeps uppercase.") optional.add_argument("--failure-reporting", diff --git a/augur/distance.py b/augur/distance.py index bfcaa1bf2..d1528ffb5 100644 --- a/augur/distance.py +++ b/augur/distance.py @@ -660,11 +660,11 @@ def get_distances_to_all_pairs(tree, sequences_by_node_and_gene, distance_map, e def register_parser(parent_subparsers): parser = parent_subparsers.add_parser("distance", help=first_line(__doc__)) parser.add_argument("--tree", help="Newick tree", required=True) - parser.add_argument("--alignment", nargs="+", help="sequence(s) to be used, supplied as FASTA files", required=True) - parser.add_argument('--gene-names', nargs="+", type=str, help="names of the sequences in the alignment, same order assumed", required=True) - parser.add_argument("--attribute-name", nargs="+", help="name to store distances associated with the given distance map; multiple attribute names are linked to corresponding positional comparison method and distance map arguments", required=True) - parser.add_argument("--compare-to", nargs="+", choices=["root", "ancestor", "pairwise"], help="type of comparison between samples in the given tree including comparison of all nodes to the root (root), all tips to their last ancestor from a previous season (ancestor), or all tips from the current season to all tips in previous seasons (pairwise)", required=True) - parser.add_argument("--map", nargs="+", help="JSON providing the distance map between sites and, optionally, sequences present at those sites; the distance map JSON minimally requires a 'default' field defining a default numeric distance and a 'map' field defining a dictionary of genes and one-based coordinates", required=True) + parser.add_argument("--alignment", nargs="+", action="extend", help="sequence(s) to be used, supplied as FASTA files", required=True) + parser.add_argument('--gene-names', nargs="+", action="extend", type=str, help="names of the sequences in the alignment, same order assumed", required=True) + parser.add_argument("--attribute-name", nargs="+", action="extend", help="name to store distances associated with the given distance map; multiple attribute names are linked to corresponding positional comparison method and distance map arguments", required=True) + parser.add_argument("--compare-to", nargs="+", action="extend", choices=["root", "ancestor", "pairwise"], help="type of comparison between samples in the given tree including comparison of all nodes to the root (root), all tips to their last ancestor from a previous season (ancestor), or all tips from the current season to all tips in previous seasons (pairwise)", required=True) + parser.add_argument("--map", nargs="+", action="extend", help="JSON providing the distance map between sites and, optionally, sequences present at those sites; the distance map JSON minimally requires a 'default' field defining a default numeric distance and a 'map' field defining a dictionary of genes and one-based coordinates", required=True) parser.add_argument("--date-annotations", help="JSON of branch lengths and date annotations from augur refine for samples in the given tree; required for comparisons to earliest or latest date") parser.add_argument("--earliest-date", help="earliest date at which samples are considered to be from previous seasons (e.g., 2019-01-01). This date is only used in pairwise comparisons. If omitted, all samples prior to the latest date will be considered.") parser.add_argument("--latest-date", help="latest date at which samples are considered to be from previous seasons (e.g., 2019-01-01); samples from any date after this are considered part of the current season") diff --git a/augur/export_v1.py b/augur/export_v1.py index 17b2ce136..901775dcc 100644 --- a/augur/export_v1.py +++ b/augur/export_v1.py @@ -9,6 +9,7 @@ from Bio import Phylo from argparse import SUPPRESS from collections import defaultdict +from .argparse_ import ExtendOverwriteDefault from .errors import AugurError from .io.metadata import DEFAULT_DELIMITERS, InvalidDelimiter, read_metadata from .utils import read_node_data, write_json, read_config, read_lat_longs, read_colors @@ -312,7 +313,7 @@ def add_core_args(parser): core = parser.add_argument_group("REQUIRED") core.add_argument('--tree','-t', required=True, help="tree to perform trait reconstruction on") core.add_argument('--metadata', required=True, metavar="FILE", help="sequence metadata") - core.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", + core.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") core.add_argument('--node-data', required=True, nargs='+', action="extend", help="JSON files with meta data for each node") core.add_argument('--output-tree', help="JSON file name that is passed on to auspice (e.g., zika_tree.json).") diff --git a/augur/export_v2.py b/augur/export_v2.py index af563bf50..4a23bb060 100644 --- a/augur/export_v2.py +++ b/augur/export_v2.py @@ -11,6 +11,7 @@ import re from Bio import Phylo +from .argparse_ import ExtendOverwriteDefault from .errors import AugurError from .io.file import open_file from .io.metadata import DEFAULT_DELIMITERS, DEFAULT_ID_COLUMNS, InvalidDelimiter, read_metadata @@ -889,21 +890,21 @@ def register_parser(parent_subparsers): config.add_argument('--maintainers', metavar="name", action="append", nargs='+', help="Analysis maintained by, in format 'Name ' 'Name2 ', ...") config.add_argument('--build-url', type=str, metavar="url", help="Build URL/repository to be displayed by Auspice") config.add_argument('--description', metavar="description.md", help="Markdown file with description of build and/or acknowledgements to be displayed by Auspice") - config.add_argument('--geo-resolutions', metavar="trait", nargs='+', help="Geographic traits to be displayed on map") - config.add_argument('--color-by-metadata', metavar="trait", nargs='+', help="Metadata columns to include as coloring options") - config.add_argument('--metadata-columns', nargs="+", + config.add_argument('--geo-resolutions', metavar="trait", nargs='+', action='extend', help="Geographic traits to be displayed on map") + config.add_argument('--color-by-metadata', metavar="trait", nargs='+', action='extend', help="Metadata columns to include as coloring options") + config.add_argument('--metadata-columns', nargs="+", action="extend", help="Metadata columns to export in addition to columns provided by --color-by-metadata or colorings in the Auspice configuration file. " + "These columns will not be used as coloring options in Auspice but will be visible in the tree.") - config.add_argument('--panels', metavar="panels", nargs='+', choices=['tree', 'map', 'entropy', 'frequencies', 'measurements'], help="Restrict panel display in auspice. Options are %(choices)s. Ignore this option to display all available panels.") + config.add_argument('--panels', metavar="panels", nargs='+', action='extend', choices=['tree', 'map', 'entropy', 'frequencies', 'measurements'], help="Restrict panel display in auspice. Options are %(choices)s. Ignore this option to display all available panels.") optional_inputs = parser.add_argument_group( title="OPTIONAL INPUT FILES" ) optional_inputs.add_argument('--node-data', metavar="JSON", nargs='+', action="extend", help="JSON files containing metadata for nodes in the tree") optional_inputs.add_argument('--metadata', metavar="FILE", help="Additional metadata for strains in the tree") - optional_inputs.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", + optional_inputs.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") - optional_inputs.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", + optional_inputs.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", action=ExtendOverwriteDefault, help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") optional_inputs.add_argument('--colors', metavar="FILE", help="Custom color definitions, one per line in the format `TRAIT_TYPE\\tTRAIT_VALUE\\tHEX_CODE`") optional_inputs.add_argument('--lat-longs', metavar="TSV", help="Latitudes and longitudes for geography traits (overrides built in mappings)") diff --git a/augur/filter/__init__.py b/augur/filter/__init__.py index 6fb09cae6..2cf6931e5 100644 --- a/augur/filter/__init__.py +++ b/augur/filter/__init__.py @@ -1,6 +1,7 @@ """ Filter and subsample a sequence set. """ +from augur.argparse_ import ExtendOverwriteDefault from augur.dates import numeric_date_type, SUPPORTED_DATE_HELP_TEXT from augur.filter.io import ACCEPTED_TYPES, column_type_pair from augur.io.metadata import DEFAULT_DELIMITERS, DEFAULT_ID_COLUMNS, METADATA_DATE_COLUMN @@ -19,8 +20,8 @@ def register_arguments(parser): input_group.add_argument('--sequences', '-s', help="sequences in FASTA or VCF format") input_group.add_argument('--sequence-index', help="sequence composition report generated by augur index. If not provided, an index will be created on the fly.") input_group.add_argument('--metadata-chunk-size', type=int, default=100000, help="maximum number of metadata records to read into memory at a time. Increasing this number can speed up filtering at the cost of more memory used.") - input_group.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") - input_group.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") + input_group.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", action=ExtendOverwriteDefault, help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") + input_group.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") metadata_filter_group = parser.add_argument_group("metadata filters", "filters to apply to metadata") metadata_filter_group.add_argument( @@ -29,7 +30,7 @@ def register_arguments(parser): Uses Pandas Dataframe querying, see https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#indexing-query for syntax. (e.g., --query "country == 'Colombia'" or --query "(country == 'USA' & (division == 'Washington'))")""" ) - metadata_filter_group.add_argument('--query-columns', type=column_type_pair, nargs="+", help=f""" + metadata_filter_group.add_argument('--query-columns', type=column_type_pair, nargs="+", action="extend", help=f""" Use alongside --query to specify columns and data types in the format 'column:type', where type is one of ({','.join(ACCEPTED_TYPES)}). Automatic type inference will be attempted on all unspecified columns used in the query. Example: region:str coverage:float. @@ -38,12 +39,12 @@ def register_arguments(parser): metadata_filter_group.add_argument('--max-date', type=numeric_date_type, help=f"maximal cutoff for date, the cutoff date is inclusive; may be specified as: {SUPPORTED_DATE_HELP_TEXT}") metadata_filter_group.add_argument('--exclude-ambiguous-dates-by', choices=['any', 'day', 'month', 'year'], help='Exclude ambiguous dates by day (e.g., 2020-09-XX), month (e.g., 2020-XX-XX), year (e.g., 200X-10-01), or any date fields. An ambiguous year makes the corresponding month and day ambiguous, too, even if those fields have unambiguous values (e.g., "201X-10-01"). Similarly, an ambiguous month makes the corresponding day ambiguous (e.g., "2010-XX-01").') - metadata_filter_group.add_argument('--exclude', type=str, nargs="+", help="file(s) with list of strains to exclude") - metadata_filter_group.add_argument('--exclude-where', nargs='+', + metadata_filter_group.add_argument('--exclude', type=str, nargs="+", action="extend", help="file(s) with list of strains to exclude") + metadata_filter_group.add_argument('--exclude-where', nargs='+', action='extend', help="Exclude samples matching these conditions. Ex: \"host=rat\" or \"host!=rat\". Multiple values are processed as OR (matching any of those specified will be excluded), not AND") metadata_filter_group.add_argument('--exclude-all', action="store_true", help="exclude all strains by default. Use this with the include arguments to select a specific subset of strains.") - metadata_filter_group.add_argument('--include', type=str, nargs="+", help="file(s) with list of strains to include regardless of priorities, subsampling, or absence of an entry in --sequences.") - metadata_filter_group.add_argument('--include-where', nargs='+', help=""" + metadata_filter_group.add_argument('--include', type=str, nargs="+", action="extend", help="file(s) with list of strains to include regardless of priorities, subsampling, or absence of an entry in --sequences.") + metadata_filter_group.add_argument('--include-where', nargs='+', action='extend', help=""" Include samples with these values. ex: host=rat. Multiple values are processed as OR (having any of those specified will be included), not AND. This rule is applied last and ensures any strains matching these @@ -56,7 +57,7 @@ def register_arguments(parser): sequence_filter_group.add_argument('--non-nucleotide', action='store_true', help="exclude sequences that contain illegal characters") subsample_group = parser.add_argument_group("subsampling", "options to subsample filtered data") - subsample_group.add_argument('--group-by', nargs='+', help=f""" + subsample_group.add_argument('--group-by', nargs='+', action='extend', help=f""" categories with respect to subsample. Notes: (1) Grouping by {sorted(constants.GROUP_BY_GENERATED_COLUMNS)} is only supported when there is a {METADATA_DATE_COLUMN!r} column in the metadata. diff --git a/augur/frequencies.py b/augur/frequencies.py index 75a11ee3c..b0abfb397 100644 --- a/augur/frequencies.py +++ b/augur/frequencies.py @@ -6,6 +6,7 @@ from Bio import Phylo, AlignIO from Bio.Align import MultipleSeqAlignment +from .argparse_ import ExtendOverwriteDefault from .errors import AugurError from .frequency_estimators import get_pivots, alignment_frequencies, tree_frequencies from .frequency_estimators import AlignmentKdeFrequencies, TreeKdeFrequencies, TreeKdeFrequenciesError @@ -24,11 +25,11 @@ def register_parser(parent_subparsers): help="method by which frequencies should be estimated") parser.add_argument('--metadata', type=str, required=True, metavar="FILE", help="metadata including dates for given samples") - parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", + parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") - parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", + parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", action=ExtendOverwriteDefault, help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") - parser.add_argument('--regions', type=str, nargs='+', default=[DEFAULT_REGION], + parser.add_argument('--regions', type=str, nargs='+', action=ExtendOverwriteDefault, default=[DEFAULT_REGION], help="region to filter to. " \ f"Regions should match values in the {REGION_COLUMN!r} column of the metadata file " \ f"if specifying values other than the default {DEFAULT_REGION!r} region.") @@ -48,9 +49,9 @@ def register_parser(parent_subparsers): help="calculate frequencies for internal nodes as well as tips") # Alignment-specific arguments - parser.add_argument('--alignments', type=str, nargs='+', + parser.add_argument('--alignments', type=str, nargs='+', action='extend', help="alignments to estimate mutations frequencies for") - parser.add_argument('--gene-names', nargs='+', type=str, + parser.add_argument('--gene-names', nargs='+', action='extend', type=str, help="names of the sequences in the alignment, same order assumed") parser.add_argument('--ignore-char', type=str, default='', help="character to be ignored in frequency calculations") diff --git a/augur/lbi.py b/augur/lbi.py index 9624f7f6e..b7063ec3a 100644 --- a/augur/lbi.py +++ b/augur/lbi.py @@ -85,9 +85,9 @@ def register_parser(parent_subparsers): parser.add_argument("--tree", help="Newick tree", required=True) parser.add_argument("--branch-lengths", help="JSON with branch lengths and internal node dates estimated by TreeTime", required=True) parser.add_argument("--output", help="JSON file with calculated distances stored by node name and attribute name", required=True) - parser.add_argument("--attribute-names", nargs="+", help="names to store distances associated with the corresponding masks", required=True) - parser.add_argument("--tau", nargs="+", type=float, help="tau value(s) defining the neighborhood of each clade", required=True) - parser.add_argument("--window", nargs="+", type=float, help="time window(s) to calculate LBI across", required=True) + parser.add_argument("--attribute-names", nargs="+", action="extend", help="names to store distances associated with the corresponding masks", required=True) + parser.add_argument("--tau", nargs="+", action="extend", type=float, help="tau value(s) defining the neighborhood of each clade", required=True) + parser.add_argument("--window", nargs="+", action="extend", type=float, help="time window(s) to calculate LBI across", required=True) parser.add_argument("--no-normalization", action="store_true", help="disable normalization of LBI by the maximum value") return parser diff --git a/augur/mask.py b/augur/mask.py index e796393b4..d7f880ffe 100644 --- a/augur/mask.py +++ b/augur/mask.py @@ -176,7 +176,7 @@ def register_arguments(parser): parser.add_argument('--mask-from-beginning', type=int, default=0, help="FASTA Only: Number of sites to mask from beginning") parser.add_argument('--mask-from-end', type=int, default=0, help="FASTA Only: Number of sites to mask from end") parser.add_argument('--mask-invalid', action='store_true', help="FASTA Only: Mask invalid nucleotides") - parser.add_argument("--mask-sites", nargs='+', type = int, help="1-indexed list of sites to mask") + parser.add_argument("--mask-sites", nargs='+', action='extend', type = int, help="1-indexed list of sites to mask") parser.add_argument('--output', '-o', help="output file") parser.add_argument('--no-cleanup', dest="cleanup", action="store_false", help="Leave intermediate files around. May be useful for debugging") diff --git a/augur/measurements/concat.py b/augur/measurements/concat.py index 4b686094a..28a9dd865 100644 --- a/augur/measurements/concat.py +++ b/augur/measurements/concat.py @@ -16,7 +16,7 @@ def register_parser(parent_subparsers): required = parser.add_argument_group( title="REQUIRED" ) - required.add_argument("--jsons", required=True, type=str, nargs="+", metavar="JSONs", + required.add_argument("--jsons", required=True, type=str, nargs="+", action="extend", metavar="JSONs", help="Measurement JSON files to concatenate.") required.add_argument("--output-json", required=True, metavar="JSON", type=str, help="Output JSON file") diff --git a/augur/measurements/export.py b/augur/measurements/export.py index ec495590a..2dfd83bcb 100644 --- a/augur/measurements/export.py +++ b/augur/measurements/export.py @@ -55,7 +55,7 @@ def register_parser(parent_subparsers): ) config.add_argument("--collection-config", metavar="JSON", help="Collection configuration file for advanced configurations. ") - config.add_argument("--grouping-column", nargs="+", + config.add_argument("--grouping-column", nargs="+", action="extend", help="Name of the column(s) that should be used as grouping(s) for measurements. " + "Note that if groupings are provided via command line args, the default group-by " + "field in the config JSON will be dropped.") @@ -69,9 +69,9 @@ def register_parser(parent_subparsers): help="The short label to display for the x-axis that describles the value of the measurements. " + "If not provided via config or command line option, the panel's default " + f"x-axis label is {DEFAULT_ARGS['x_axis_label']!r}.") - config.add_argument("--thresholds", type=float, nargs="+", + config.add_argument("--thresholds", type=float, nargs="+", action="extend", help="Measurements value threshold(s) to be displayed in the measurements panel.") - config.add_argument("--filters", nargs="+", + config.add_argument("--filters", nargs="+", action="extend", help="The columns that are to be used a filters for measurements. " + "If not provided, all columns will be available as filters.") config.add_argument("--group-by", type=str, @@ -89,7 +89,7 @@ def register_parser(parent_subparsers): optional = parser.add_argument_group( title="OPTIONAL SETTINGS" ) - optional.add_argument("--include-columns", nargs="+", + optional.add_argument("--include-columns", nargs="+", action="extend", help="The columns to include from the collection TSV in the measurements JSON. " + "Be sure to list columns that are used as groupings and/or filters. " + "If no columns are provided, then all columns will be included by default.") diff --git a/augur/parse.py b/augur/parse.py index 6b0cbb203..5d3cd34a9 100644 --- a/augur/parse.py +++ b/augur/parse.py @@ -150,8 +150,8 @@ def register_parser(parent_subparsers): parser.add_argument('--output-metadata', required=True, help="output metadata file") parser.add_argument('--output-id-field', required=False, help=f"The record field to use as the sequence identifier in the FASTA output. If not provided, this will use the first available of {PARSE_DEFAULT_ID_COLUMNS}. If none of those are available, this will use the first field in the fasta header.") - parser.add_argument('--fields', required=True, nargs='+', help="fields in fasta header") - parser.add_argument('--prettify-fields', nargs='+', help="apply string prettifying operations (underscores to spaces, capitalization, etc) to specified metadata fields") + parser.add_argument('--fields', required=True, nargs='+', action='extend', help="fields in fasta header") + parser.add_argument('--prettify-fields', nargs='+', action='extend', help="apply string prettifying operations (underscores to spaces, capitalization, etc) to specified metadata fields") parser.add_argument('--separator', default='|', help="separator of fasta header") parser.add_argument('--fix-dates', choices=['dayfirst', 'monthfirst'], help="attempt to parse non-standard dates and output them in standard YYYY-MM-DD format") diff --git a/augur/refine.py b/augur/refine.py index 95717861f..158c86afd 100644 --- a/augur/refine.py +++ b/augur/refine.py @@ -4,6 +4,7 @@ import numpy as np import sys from Bio import Phylo +from .argparse_ import ExtendOverwriteDefault from .dates import get_numerical_dates from .dates.errors import InvalidYearBounds from .io.metadata import DEFAULT_DELIMITERS, DEFAULT_ID_COLUMNS, METADATA_DATE_COLUMN, InvalidDelimiter, Metadata, read_metadata @@ -100,9 +101,9 @@ def register_parser(parent_subparsers): parser.add_argument('--alignment', '-a', help="alignment in fasta or VCF format") parser.add_argument('--tree', '-t', required=True, help="prebuilt Newick") parser.add_argument('--metadata', type=str, metavar="FILE", help="sequence metadata") - parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", + parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") - parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", + parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", action=ExtendOverwriteDefault, help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") parser.add_argument('--output-tree', type=str, help='file name to write tree to') parser.add_argument('--output-node-data', type=str, help='file name to write branch lengths as node data') @@ -113,7 +114,7 @@ def register_parser(parent_subparsers): parser.add_argument('--gen-per-year', default=50, type=float, help="number of generations per year, relevant for skyline output('skyline')") parser.add_argument('--clock-rate', type=float, help="fixed clock rate") parser.add_argument('--clock-std-dev', type=float, help="standard deviation of the fixed clock_rate estimate") - parser.add_argument('--root', nargs="+", default='best', help="rooting mechanism ('best', least-squares', 'min_dev', 'oldest', 'mid_point') " + parser.add_argument('--root', nargs="+", action=ExtendOverwriteDefault, default='best', help="rooting mechanism ('best', least-squares', 'min_dev', 'oldest', 'mid_point') " "OR node to root by OR two nodes indicating a monophyletic group to root by. " "Run treetime -h for definitions of rooting methods.") parser.add_argument('--keep-root', action="store_true", help="do not reroot the tree; use it as-is. " @@ -138,7 +139,7 @@ def register_parser(parent_subparsers): parser.add_argument('--clock-filter-iqd', type=float, help='clock-filter: remove tips that deviate more than n_iqd ' 'interquartile ranges from the root-to-tip vs time regression') parser.add_argument('--vcf-reference', type=str, help='fasta file of the sequence the VCF was mapped to') - parser.add_argument('--year-bounds', type=int, nargs='+', help='specify min or max & min prediction bounds for samples with XX in year') + parser.add_argument('--year-bounds', type=int, nargs='+', action='extend', help='specify min or max & min prediction bounds for samples with XX in year') parser.add_argument('--divergence-units', type=str, choices=['mutations', 'mutations-per-site'], default='mutations-per-site', help='Units in which sequence divergences is exported.') parser.add_argument('--seed', type=int, help='seed for random number generation') diff --git a/augur/titers.py b/augur/titers.py index b9f3b3791..2b06fb468 100644 --- a/augur/titers.py +++ b/augur/titers.py @@ -17,7 +17,7 @@ def register_parser(parent_subparsers): add_default_command(parser) tree_model = subparsers.add_parser('tree', help='tree model') - tree_model.add_argument('--titers', nargs='+', type=str, required=True, help="file with titer measurements") + tree_model.add_argument('--titers', nargs='+', action='extend', type=str, required=True, help="file with titer measurements") tree_model.add_argument('--tree', '-t', type=str, required=True, help="tree to perform fit titer model to") tree_model.add_argument('--allow-empty-model', action="store_true", help="allow model to be empty") tree_model.add_argument('--attribute-prefix', default="", help="prefix for node attributes in the JSON output including cumulative titer drop ('cTiter') and per-branch titer drop ('dTiter'). Set a prefix to disambiguate annotations from multiple tree model JSONs in the final Auspice JSON.") @@ -27,9 +27,9 @@ def register_parser(parent_subparsers): ) sub_model = subparsers.add_parser('sub', help='substitution model') - sub_model.add_argument('--titers', nargs='+', type=str, required=True, help="file with titer measurements") - sub_model.add_argument('--alignment', nargs='+', type=str, required=True, help="sequence to be used in the substitution model, supplied as fasta files") - sub_model.add_argument('--gene-names', nargs='+', type=str, required=True, help="names of the sequences in the alignment, same order assumed") + sub_model.add_argument('--titers', nargs='+', action='extend', type=str, required=True, help="file with titer measurements") + sub_model.add_argument('--alignment', nargs='+', action='extend', type=str, required=True, help="sequence to be used in the substitution model, supplied as fasta files") + sub_model.add_argument('--gene-names', nargs='+', action='extend', type=str, required=True, help="names of the sequences in the alignment, same order assumed") sub_model.add_argument('--tree', '-t', type=str, help="optional tree to annotate fit titer model to") sub_model.add_argument('--allow-empty-model', action="store_true", help="allow model to be empty") sub_model.add_argument('--attribute-prefix', default="", help="prefix for node attributes in the JSON output including cumulative titer drop ('cTiterSub') and per-substitution titer drop ('dTiterSub'). Set a prefix to disambiguate annotations from multiple substitution model JSONs in the final Auspice JSON.") diff --git a/augur/traits.py b/augur/traits.py index 0b520c600..26e36c325 100644 --- a/augur/traits.py +++ b/augur/traits.py @@ -5,6 +5,7 @@ import numpy as np from collections import defaultdict import sys +from .argparse_ import ExtendOverwriteDefault from .errors import AugurError from .io.file import open_file from .io.metadata import DEFAULT_DELIMITERS, DEFAULT_ID_COLUMNS, InvalidDelimiter, read_metadata @@ -99,12 +100,12 @@ def register_parser(parent_subparsers): parser = parent_subparsers.add_parser("traits", help=__doc__) parser.add_argument('--tree', '-t', required=True, help="tree to perform trait reconstruction on") parser.add_argument('--metadata', required=True, metavar="FILE", help="table with metadata") - parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", + parser.add_argument('--metadata-delimiters', default=DEFAULT_DELIMITERS, nargs="+", action=ExtendOverwriteDefault, help="delimiters to accept when reading a metadata file. Only one delimiter will be inferred.") - parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", + parser.add_argument('--metadata-id-columns', default=DEFAULT_ID_COLUMNS, nargs="+", action=ExtendOverwriteDefault, help="names of possible metadata columns containing identifier information, ordered by priority. Only one ID column will be inferred.") parser.add_argument('--weights', required=False, help="tsv/csv table with equilibrium probabilities of discrete states") - parser.add_argument('--columns', required=True, nargs='+', + parser.add_argument('--columns', required=True, nargs='+', action='extend', help='metadata fields to perform discrete reconstruction on') parser.add_argument('--confidence',action="store_true", help='record the distribution of subleading mugration states') diff --git a/augur/translate.py b/augur/translate.py index a7fc8c091..6ec4d4b3d 100644 --- a/augur/translate.py +++ b/augur/translate.py @@ -368,7 +368,7 @@ def register_parser(parent_subparsers): parser.add_argument('--ancestral-sequences', required=True, type=str, help='JSON (fasta input) or VCF (VCF input) containing ancestral and tip sequences') parser.add_argument('--reference-sequence', required=True, help='GenBank or GFF file containing the annotation') - parser.add_argument('--genes', nargs='+', help="genes to translate (list or file containing list)") + parser.add_argument('--genes', nargs='+', action='extend', help="genes to translate (list or file containing list)") parser.add_argument('--output-node-data', type=str, help='name of JSON file to save aa-mutations to') parser.add_argument('--alignment-output', type=str, help="write out translated gene alignments. " "If a VCF-input, a .vcf or .vcf.gz will be output here (depending on file ending). If fasta-input, specify the file name " diff --git a/scripts/diff_jsons.py b/scripts/diff_jsons.py index ce215e855..4d0ffafb4 100644 --- a/scripts/diff_jsons.py +++ b/scripts/diff_jsons.py @@ -4,6 +4,8 @@ import deepdiff import json +from augur.argparse_ import ExtendOverwriteDefault + if __name__ == "__main__": parser = argparse.ArgumentParser( @@ -13,8 +15,8 @@ parser.add_argument("first_json", help="first JSON to compare") parser.add_argument("second_json", help="second JSON to compare") parser.add_argument("--significant-digits", type=int, default=5, help="number of significant digits to use when comparing numeric values") - parser.add_argument("--exclude-paths", nargs="+", help="list of paths to exclude from consideration when performing a diff", default=["root['generated_by']['version']"]) - parser.add_argument("--exclude-regex-paths", nargs="+", help="list of path regular expressions to exclude from consideration when performing a diff") + parser.add_argument("--exclude-paths", nargs="+", action=ExtendOverwriteDefault, help="list of paths to exclude from consideration when performing a diff", default=["root['generated_by']['version']"]) + parser.add_argument("--exclude-regex-paths", nargs="+", action="extend", help="list of path regular expressions to exclude from consideration when performing a diff") parser.add_argument("--ignore-numeric-type-changes", action="store_true", help="ignore numeric type changes in the diff (e.g., int of 1 to float of 1.0)") args = parser.parse_args() diff --git a/scripts/diff_trees.py b/scripts/diff_trees.py index ed652f441..f61cece15 100644 --- a/scripts/diff_trees.py +++ b/scripts/diff_trees.py @@ -2,6 +2,8 @@ import Bio.Phylo import deepdiff +from augur.argparse_ import ExtendOverwriteDefault + def clade_to_items(clade, attrs=("name", "branch_length")): """Recursively convert a clade of a tree to a list of nested lists according to @@ -31,7 +33,7 @@ def clade_to_items(clade, attrs=("name", "branch_length")): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("first_tree", help="first Newick tree to compare") parser.add_argument("second_tree", help="second Newick tree to compare") - parser.add_argument("--attributes", nargs="+", default=["name", "branch_length"], help="node attributes to include in comparison") + parser.add_argument("--attributes", nargs="+", action=ExtendOverwriteDefault, default=["name", "branch_length"], help="node attributes to include in comparison") parser.add_argument("--significant-digits", type=int, default=5, help="number of significant digits to use when comparing branch lengths") args = parser.parse_args() diff --git a/scripts/s3.py b/scripts/s3.py index d58895164..3fbd9c760 100644 --- a/scripts/s3.py +++ b/scripts/s3.py @@ -271,14 +271,14 @@ def sync(source_bucket_name, destination_bucket_name, prefixes=None, dryrun=Fals parser_pull = subparsers.add_parser("pull") parser_pull.add_argument("--bucket", "-b", type=str, help="S3 bucket to pull files from") - parser_pull.add_argument("--prefixes", "-p", nargs="+", help="One or more file prefixes to match in the given bucket") + parser_pull.add_argument("--prefixes", "-p", nargs="+", action="extend", help="One or more file prefixes to match in the given bucket") parser_pull.add_argument("--local_dir", "--to", "-t", help="Local directory to download files into") parser_pull.set_defaults(func=pull) parser_sync = subparsers.add_parser("sync") parser_sync.add_argument("--source_bucket", "--from", type=str, help="Source S3 bucket") parser_sync.add_argument("--destination_bucket", "--to", type=str, help="Destination S3 bucket") - parser_sync.add_argument("--prefixes", "-p", nargs="+", help="One or more prefixes for files to sync between buckets") + parser_sync.add_argument("--prefixes", "-p", nargs="+", action="extend", help="One or more prefixes for files to sync between buckets") parser_sync.set_defaults(func=sync) args = parser.parse_args() diff --git a/scripts/swap_colors.py b/scripts/swap_colors.py index 058a28e58..dd3feab05 100644 --- a/scripts/swap_colors.py +++ b/scripts/swap_colors.py @@ -3,8 +3,11 @@ import argparse from glob import glob +from augur.argparse_ import ExtendOverwriteDefault + + parser = argparse.ArgumentParser(description ="Update color ramps in processed meta.JSON files") -parser.add_argument('--jsons', '--json', default=None, nargs='+', type=str, help="Path to prepared JSON(s) to edit. If none, will update all files like ./*meta.json") +parser.add_argument('--jsons', '--json', default=None, nargs='+', action=ExtendOverwriteDefault, type=str, help="Path to prepared JSON(s) to edit. If none, will update all files like ./*meta.json") parser.add_argument('--custom_colors', default=None, type=str, help="Path to .tsv or .csv with custom color ramps; will fall back to nextstrain default colors if not provided.") args = parser.parse_args().__dict__ diff --git a/scripts/traits_from_json.py b/scripts/traits_from_json.py index 0c53afc7f..25b3d681d 100644 --- a/scripts/traits_from_json.py +++ b/scripts/traits_from_json.py @@ -6,6 +6,9 @@ import json from numpy import ndarray +from augur.argparse_ import ExtendOverwriteDefault + + def get_trait(attributes, trait, dateFormat): if trait in ["num_date", "date"]: try: @@ -27,8 +30,8 @@ def get_trait(attributes, trait, dateFormat): parser = argparse.ArgumentParser(description = "Process a given JSONs") parser.add_argument('--json', required=True, type=str, help="prepared JSON") parser.add_argument('--trait', required=True, type=str, help="prepared JSON") - parser.add_argument('--header', nargs='*', type=str, help="header fields") - parser.add_argument('--date_format', nargs='*', default=["%Y-%m-%d"], type=str, help="if needed. default: [%%Y-%%m-%%d]") + parser.add_argument('--header', nargs='*', action='extend', type=str, help="header fields") + parser.add_argument('--date_format', nargs='*', action=ExtendOverwriteDefault, default=["%Y-%m-%d"], type=str, help="if needed. default: [%%Y-%%m-%%d]") params = parser.parse_args() with open(params.json, 'r') as fh: diff --git a/scripts/tree_to_JSON.py b/scripts/tree_to_JSON.py index 39bc1d0a6..9657b5c51 100644 --- a/scripts/tree_to_JSON.py +++ b/scripts/tree_to_JSON.py @@ -19,9 +19,9 @@ def get_command_line_args(): beast = parser.add_argument_group('beast') beast.add_argument('--nexus', type=str, help="Path to nexus file") beast.add_argument('--most_recent_tip', type=float, help="Date of the most recent tip (in decimal format)") - beast.add_argument('--discrete_traits', type=str, nargs='+', default=[], help="Discrete traits to extract from the BEAST annotations") - beast.add_argument('--continuous_traits', type=str, nargs='+', default=[], help="Continuous traits to extract from the BEAST annotations") - beast.add_argument('--make_traits_log', type=str, nargs='+', default=[], help="Convert these (continous) traits to log space: y=-ln(x)") + beast.add_argument('--discrete_traits', type=str, nargs='+', action='extend', default=[], help="Discrete traits to extract from the BEAST annotations") + beast.add_argument('--continuous_traits', type=str, nargs='+', action='extend', default=[], help="Continuous traits to extract from the BEAST annotations") + beast.add_argument('--make_traits_log', type=str, nargs='+', action='extend', default=[], help="Convert these (continous) traits to log space: y=-ln(x)") beast.add_argument("--fake_divergence", action="store_true", help="Set the divergence as time (prevents auspice crashing)") @@ -34,8 +34,8 @@ def get_command_line_args(): general.add_argument("--debug", action="store_const", dest="loglevel", const=logging.DEBUG, help="Enable debugging logging") general.add_argument('--output_prefix', '-o', required=True, type=str, help="Output prefix (i.e. \"_meta.json\" will be appended to this)") general.add_argument('--title', default=None, type=str, help="Title (to be displayed by auspice)") - general.add_argument("--defaults", type=str, nargs='+', default=[], help="Auspice defaults. Format: \"key:value\"") - general.add_argument("--filters", type=str, nargs='+', default=[], help="Auspice filters.") + general.add_argument("--defaults", type=str, nargs='+', action='extend', default=[], help="Auspice defaults. Format: \"key:value\"") + general.add_argument("--filters", type=str, nargs='+', action='extend', default=[], help="Auspice filters.") general.add_argument('--geo', type=str, help="CSV File w. header \"trait,value,latitude,longitude\". Turns on the map panel.") diff --git a/tests/functional/filter/cram/filter-exclude-where-multiple.t b/tests/functional/filter/cram/filter-exclude-where-multiple.t index f0b810926..a9676d914 100644 --- a/tests/functional/filter/cram/filter-exclude-where-multiple.t +++ b/tests/functional/filter/cram/filter-exclude-where-multiple.t @@ -32,8 +32,7 @@ Scenario 2: Run command with two --exclude-where flags > --exclude-where "region=B" \ > --output-strains filtered_strains.txt > /dev/null -Only the last exclusion is applied. +Both exclusions are applied. $ cat filtered_strains.txt - SEQ_1 SEQ_3 From 4ddef23466b74a054eb365a813c1e3a157063583 Mon Sep 17 00:00:00 2001 From: Victor Lin <13424970+victorlin@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:46:57 -0700 Subject: [PATCH 4/4] Update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e735c5c8c..98a373fa6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Features +* All commands: Allow repeating an option that takes multiple values. Previously, if multiple option flags were specified (e.g. `--exclude-where 'region=A' --exclude-where 'region=B'`), only the last one was used. Now, all values are used. [#1445][] (@victorlin) * ancestral, translate: output node data files are now validated. The argument `--validation-mode` is added which controls this behaviour (default: error). This argument also controls validation of the input node-data file (ancestral only). [#1440][] (@jameshadfield) ### Bug Fixes @@ -12,6 +13,7 @@ * validation: we no longer perform any validation when the requested validation mode is "skip" [#1440][] (@jameshadfield) [#1440]: https://github.com/nextstrain/augur/pull/1440 +[#1445]: https://github.com/nextstrain/augur/pull/1445 ## 24.3.0 (18 March 2024)