[med-svn] [Git][med-team/q2cli][upstream] New upstream version 2024.2.0
Andreas Tille (@tille)
gitlab at salsa.debian.org
Sun Feb 18 17:15:12 GMT 2024
Andreas Tille pushed to branch upstream at Debian Med / q2cli
Commits:
2dece376 by Andreas Tille at 2024-02-18T15:57:52+01:00
New upstream version 2024.2.0
- - - - -
13 changed files:
- .github/workflows/ci-dev.yaml
- README.md
- q2cli/_version.py
- q2cli/builtin/tools.py
- q2cli/click/option.py
- q2cli/core/cache.py
- q2cli/core/state.py
- q2cli/core/usage.py
- q2cli/tests/test_cli.py
- q2cli/tests/test_core.py
- q2cli/tests/test_tools.py
- q2cli/tests/test_usage.py
- q2cli/util.py
Changes:
=====================================
.github/workflows/ci-dev.yaml
=====================================
@@ -9,4 +9,4 @@ jobs:
ci:
uses: qiime2/distributions/.github/workflows/lib-ci-dev.yaml at dev
with:
- distro: core
\ No newline at end of file
+ distro: amplicon
=====================================
README.md
=====================================
@@ -1,6 +1,6 @@
# q2cli
-
+
A [click-based](http://click.pocoo.org/) command line interface for [QIIME
2](https://github.com/qiime2/qiime2).
=====================================
q2cli/_version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
- git_refnames = " (tag: 2023.9.1, refs/pull/314/head, Release-2023.9)"
- git_full = "1d091d6908622c80cabeddb1dda697b2313d3eab"
- git_date = "2023-10-04 14:13:55 +0000"
+ git_refnames = " (tag: 2024.2.0, Release-2024.2)"
+ git_full = "722997ebe6be5973716cc5f3e9f0bbd4ce722095"
+ git_date = "2024-02-16 21:49:58 +0000"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
q2cli/builtin/tools.py
=====================================
@@ -207,26 +207,6 @@ def show_formats(queries, importable, exportable, strict, tsv):
_print_descriptions(descriptions, tsv)
-def show_importable_types(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
- click.secho('This functionality has been moved to the list-types command.',
- fg='red', bold=True)
- click.secho('Run `qiime tools list-types --help` for more information.',
- fg='red', bold=True)
- ctx.exit()
-
-
-def show_importable_formats(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
- click.secho('This functionality has been moved to the list-formats '
- 'command.', fg='red', bold=True)
- click.secho('Run `qiime tools list-formats --help` for more information.',
- fg='red', bold=True)
- ctx.exit()
-
-
@tools.command(name='import',
short_help='Import data into a new QIIME 2 Artifact.',
help="Import data to create a new QIIME 2 Artifact. See "
@@ -236,7 +216,7 @@ def show_importable_formats(ctx, param, value):
cls=ToolCommand)
@click.option('--type', required=True,
help='The semantic type of the artifact that will be created '
- 'upon importing. Use --show-importable-types to see what '
+ 'upon importing. Use `qiime tools list-types` to see what '
'importable semantic types are available in the current '
'deployment.')
@click.option('--input-path', required=True,
@@ -250,29 +230,22 @@ def show_importable_formats(ctx, param, value):
@click.option('--input-format', required=False,
help='The format of the data to be imported. If not provided, '
'data must be in the format expected by the semantic type '
- 'provided via --type.')
- at click.option('--show-importable-types', is_flag=True, is_eager=True,
- callback=show_importable_types, expose_value=False,
- help='Show the semantic types that can be supplied to --type '
- 'to import data into an artifact.')
- at click.option('--show-importable-formats', is_flag=True, is_eager=True,
- callback=show_importable_formats, expose_value=False,
- help='Show formats that can be supplied to --input-format to '
- 'import data into an artifact.')
-def import_data(type, input_path, output_path, input_format):
- import qiime2.sdk
- import qiime2.plugin
+ 'provided via --type. Use `qiime tools list-formats '
+ '--importable` to see which formats of input data are '
+ 'importable.')
+ at click.option('--validate-level', default='max',
+ type=click.Choice(['min', 'max']),
+ help='How much to validate the imported data before creating the'
+ ' artifact. A value of "max" will generally read the entire'
+ ' file or directory, whereas "min" will not usually do so.'
+ ' [default: "max"]')
+def import_data(type, input_path, output_path, input_format, validate_level):
+
from q2cli.core.config import CONFIG
- try:
- artifact = qiime2.sdk.Artifact.import_data(type, input_path,
- view_type=input_format)
- except qiime2.plugin.ValidationError as e:
- header = 'There was a problem importing %s:' % input_path
- q2cli.util.exit_with_error(e, header=header, traceback=None)
- except Exception as e:
- header = 'An unexpected error has occurred:'
- q2cli.util.exit_with_error(e, header=header)
+
+ artifact = _import(type, input_path, input_format, validate_level)
artifact.save(output_path)
+
if input_format is None:
input_format = artifact.format.__name__
@@ -820,6 +793,75 @@ def cache_store(cache, artifact_path, key):
click.echo(CONFIG.cfg_style('success', success))
+ at tools.command(name='cache-import',
+ short_help='Imports data into an Artifact in the cache under a '
+ 'key.',
+ help='Imports data into an Artifact in the cache under a key.',
+ cls=ToolCommand)
+ at click.option('--type', required=True,
+ help='The semantic type of the artifact that will be created '
+ 'upon importing. Use `qiime tools list-types` to see what '
+ 'importable semantic types are available in the current '
+ 'deployment.')
+ at click.option('--input-path', required=True,
+ type=click.Path(exists=True, file_okay=True, dir_okay=True,
+ readable=True),
+ help='Path to file or directory that should be imported.')
+ at click.option('--cache', required=True,
+ type=click.Path(exists=True, file_okay=False, dir_okay=True,
+ readable=True),
+ help='Path to an existing cache to save into.')
+ at click.option('--key', required=True,
+ help='The key to save the artifact under (must be a valid '
+ 'Python identifier).')
+ at click.option('--input-format', required=False,
+ help='The format of the data to be imported. If not provided, '
+ 'data must be in the format expected by the semantic type '
+ 'provided via --type. Use `qiime tools list-formats '
+ '--importable` to see which formats of input data are '
+ 'importable.')
+ at click.option('--validate-level', required=False, default='max',
+ type=click.Choice(['min', 'max']),
+ help='How much to validate the imported data before creating the'
+ ' artifact. A value of "max" will generally read the entire'
+ ' file or directory, whereas "min" will not usually do so.'
+ ' [default: "max"]')
+def cache_import(type, input_path, cache, key, input_format, validate_level):
+ from qiime2 import Cache
+ from q2cli.core.config import CONFIG
+
+ artifact = _import(type, input_path, input_format, validate_level)
+ _cache = Cache(cache)
+ _cache.save(artifact, key)
+
+ if input_format is None:
+ input_format = artifact.format.__name__
+
+ success = 'Imported %s as %s to %s:%s' % (input_path,
+ input_format,
+ cache,
+ key)
+ click.echo(CONFIG.cfg_style('success', success))
+
+
+def _import(type, input_path, input_format, validate_level):
+ import qiime2.sdk
+ import qiime2.plugin
+
+ try:
+ artifact = qiime2.sdk.Artifact.import_data(
+ type, input_path, view_type=input_format,
+ validate_level=validate_level)
+ except qiime2.plugin.ValidationError as e:
+ header = 'There was a problem importing %s:' % input_path
+ q2cli.util.exit_with_error(e, header=header, traceback=None)
+ except Exception as e:
+ header = 'An unexpected error has occurred:'
+ q2cli.util.exit_with_error(e, header=header)
+
+ return artifact
+
+
@tools.command(name='cache-fetch',
short_help='Fetches an artifact out of a cache into a .qza.',
help='Fetches the artifact saved to the specified cache under '
=====================================
q2cli/click/option.py
=====================================
@@ -107,13 +107,22 @@ class GeneratedOption(click.Option):
def _consume_metadata(self, ctx, opts):
# double consume
+ # this consume deals with the metadata file
md_file, source = super().consume_value(ctx, opts)
# consume uses self.name, so mutate but backup for after
backup, self.name = self.name, self.q2_extra_dest
- md_col, _ = super().consume_value(ctx, opts)
+ try:
+ # this consume deals with the metadata column
+ md_col, _ = super().consume_value(ctx, opts)
+ # If `--m-metadata-column` isn't provided, need to set md_col to None
+ # in order for the click.MissingParameter errors below to be raised
+ except click.MissingParameter:
+ md_col = None
self.name = backup
-
+ # These branches won't get hit unless there's a value associated with
+ # md_col - the try/except case above handled the situation where the
+ # metadata_column parameter itself wasn't provided (vs just a value)
if (md_col is None) != (md_file is None):
# missing one or the other
if md_file is None:
=====================================
q2cli/core/cache.py
=====================================
@@ -161,10 +161,10 @@ class DeploymentCache:
for entry_point in pkg_resources.iter_entry_points(
group='qiime2.plugins'):
if 'QIIMETEST' in os.environ:
- if entry_point.name == 'dummy-plugin':
+ if entry_point.name in ('dummy-plugin', 'other-plugin'):
reqs.add(entry_point.dist.as_requirement())
else:
- if entry_point.name != 'dummy-plugin':
+ if entry_point.name not in ('dummy-plugin', 'other-plugin'):
reqs.add(entry_point.dist.as_requirement())
return reqs
=====================================
q2cli/core/state.py
=====================================
@@ -135,6 +135,8 @@ def _get_metavar(type):
'Str': 'TEXT',
'Float': 'NUMBER',
'Bool': '',
+ 'Jobs': 'NJOBS',
+ 'Threads': 'NTHREADS',
}
style = qiime2.sdk.util.interrogate_collection_type(type)
=====================================
q2cli/core/usage.py
=====================================
@@ -173,6 +173,55 @@ class CLIUsage(usage.Usage):
return variable
+ def construct_artifact_collection(self, name, members):
+ variable = super().construct_artifact_collection(
+ name, members
+ )
+
+ rc_dir = variable.to_interface_name()
+
+ keys = members.keys()
+ names = [name.to_interface_name() for name in members.values()]
+
+ keys_arg = '( '
+ for key in keys:
+ keys_arg += f'{key} '
+ keys_arg += ')'
+ names_arg = '( '
+ for name in names:
+ names_arg += f'{name} '
+ names_arg += ')'
+
+ lines = [
+ '## constructing result collection ##',
+ f'rc_name={rc_dir}',
+ 'ext=.qza',
+ f'keys={keys_arg}',
+ f'names={names_arg}',
+ 'construct_result_collection',
+ '##',
+ ]
+ self.recorder.extend(lines)
+
+ return variable
+
+ def get_artifact_collection_member(self, name, variable, key):
+ accessed_variable = super().get_artifact_collection_member(
+ name, variable, key
+ )
+
+ rc_dir = variable.to_interface_name()
+ member_fp = os.path.join(rc_dir, f'{key}.qza')
+
+ lines = [
+ '## accessing result collection member ##',
+ f'ln -s {member_fp} {accessed_variable.to_interface_name()}',
+ '##',
+ ]
+ self.recorder.extend(lines)
+
+ return variable
+
def import_from_format(self, name, semantic_type,
variable, view_type=None):
imported_var = super().import_from_format(
@@ -684,6 +733,25 @@ class ReplayCLIUsage(CLIUsage):
self.shebang, self.header_boundary, self.copyright, self.how_to
))
+ # for creating result collections in bash
+ bash_rc_function = [
+ 'construct_result_collection () {',
+ '\tmkdir $rc_name',
+ '\ttouch $rc_name.order',
+ '\tfor key in "${keys[@]}"; do',
+ '\t\techo $key >> $rc_name.order',
+ '\tdone',
+ '\tfor i in "${!keys[@]}"; do',
+ '\t\tln -s ../"${names[i]}" $rc_name"${keys[i]}"$ext',
+ '\tdone',
+ '}'
+ ]
+ self.header.extend([
+ '## function to create result collections ##',
+ *bash_rc_function,
+ '##',
+ ])
+
def build_footer(self, dag: ProvDAG):
'''
Constructs a renderable footer using the terminal uuids of a ProvDAG.
=====================================
q2cli/tests/test_cli.py
=====================================
@@ -8,6 +8,7 @@
import os.path
import unittest
+import contextlib
import unittest.mock
import tempfile
import shutil
@@ -52,6 +53,7 @@ class CliTests(unittest.TestCase):
self.assertIn('System versions', result.output)
self.assertIn('Installed plugins', result.output)
self.assertIn('dummy-plugin', result.output)
+ self.assertIn('other-plugin', result.output)
def test_list_commands(self):
# top level commands, including a plugin, are present
@@ -60,6 +62,7 @@ class CliTests(unittest.TestCase):
self.assertIn('info', commands)
self.assertIn('tools', commands)
self.assertIn('dummy-plugin', commands)
+ self.assertIn('other-plugin', commands)
def test_plugin_list_commands(self):
# plugin commands are present including a method and visualizer
@@ -566,7 +569,8 @@ class TestMetadataSupport(MetadataTestsBase):
class TestMetadataColumnSupport(MetadataTestsBase):
- def test_required_missing(self):
+ # Neither md file or column params provided
+ def test_required_missing_file_and_column(self):
result = self._run_command(
'identity-with-metadata-column', '--i-ints', self.input_artifact,
'--o-out', self.output_artifact)
@@ -575,6 +579,27 @@ class TestMetadataColumnSupport(MetadataTestsBase):
self.assertTrue(result.output.startswith('Usage:'))
self.assertIn("Missing option '--m-metadata-file'", result.output)
+ # md file param missing, md column param & value provided
+ def test_required_missing_file(self):
+ result = self._run_command(
+ 'identity-with-metadata-column', '--i-ints', self.input_artifact,
+ '--m-metadata-column', 'a', '--o-out', self.output_artifact)
+
+ self.assertEqual(result.exit_code, 1)
+ self.assertTrue(result.output.startswith('Usage:'))
+ self.assertIn("Missing option '--m-metadata-file'", result.output)
+
+ # md file param & value provided, md column param missing
+ def test_required_missing_column(self):
+ result = self._run_command(
+ 'identity-with-metadata-column', '--i-ints', self.input_artifact,
+ '--m-metadata-file', self.metadata_file1,
+ '--o-out', self.output_artifact)
+
+ self.assertEqual(result.exit_code, 1)
+ self.assertTrue(result.output.startswith('Usage:'))
+ self.assertIn("Missing option '--m-metadata-column'", result.output)
+
def test_optional_metadata_missing(self):
result = self._run_command(
'identity-with-optional-metadata-column', '--i-ints',
@@ -894,6 +919,27 @@ class TestCollectionSupport(unittest.TestCase):
' values. All values must be keyed or unkeyed',
str(result.exception))
+ def test_keyed_path_with_tilde(self):
+ self.art1.save(self.art1_path)
+ self.art2.save(self.art2_path)
+
+ tmp = tempfile.gettempdir()
+ tempdir = os.path.basename(self.tempdir)
+
+ with modified_environ(HOME=tmp):
+ result = self._run_command(
+ 'dict-of-ints',
+ '--i-ints', f'foo:{os.path.join("~", tempdir, "art1.qza")}',
+ '--i-ints', f'bar:{os.path.join("~", tempdir, "art2.qza")}',
+ '--o-output', self.output, '--verbose')
+
+ self.assertEqual(result.exit_code, 0)
+ collection = ResultCollection.load(self.output)
+
+ self.assertEqual(collection['foo'].view(int), 0)
+ self.assertEqual(collection['bar'].view(int), 1)
+ self.assertEqual(list(collection.keys()), ['foo', 'bar'])
+
def test_directory_with_non_artifacts(self):
input_dir = os.path.join(self.tempdir, 'in')
os.mkdir(input_dir)
@@ -924,5 +970,43 @@ class TestCollectionSupport(unittest.TestCase):
result.output)
+ at contextlib.contextmanager
+def modified_environ(*remove, **update):
+ """
+ Taken from: https://stackoverflow.com/a/34333710.
+
+ Updating the os.environ dict only modifies the environment variables from
+ the perspective of the current Python process, so this isn't dangerous at
+ all.
+
+ Temporarily updates the ``os.environ`` dictionary in-place.
+
+ The ``os.environ`` dictionary is updated in-place so that the modification
+ is sure to work in all situations.
+
+ :param remove: Environment variables to remove.
+ :param update: Dictionary of environment variables and values to
+ add/update.
+ """
+ env = os.environ
+ update = update or {}
+ remove = remove or []
+
+ # List of environment variables being updated or removed.
+ stomped = (set(update.keys()) | set(remove)) & set(env.keys())
+ # Environment variables and values to restore on exit.
+ update_after = {k: env[k] for k in stomped}
+ # Environment variables and values to remove on exit.
+ remove_after = frozenset(k for k in update if k not in env)
+
+ try:
+ env.update(update)
+ [env.pop(k, None) for k in remove]
+ yield
+ finally:
+ env.update(update_after)
+ [env.pop(k) for k in remove_after]
+
+
if __name__ == "__main__":
unittest.main()
=====================================
q2cli/tests/test_core.py
=====================================
@@ -61,9 +61,9 @@ class TestOption(unittest.TestCase):
def test_repeated_eager_option_with_callback(self):
result = self.runner.invoke(
q2cli.builtin.tools.tools,
- ['import', '--show-importable-types', '--show-importable-types'])
+ ['list-types', '--tsv', '--tsv'])
- self._assertRepeatedOptionError(result, '--show-importable-types')
+ self._assertRepeatedOptionError(result, '--tsv')
def test_repeated_builtin_flag(self):
result = self.runner.invoke(
@@ -412,12 +412,13 @@ class WriteReproducibilitySupplementTests(unittest.TestCase):
self.assertTrue(zipfile.is_zipfile(out_fp))
exp = {
- 'python3_replay.py',
- 'cli_replay.sh',
- 'citations.bib',
- 'recorded_metadata/',
- 'recorded_metadata/dummy_plugin_identity_with_metadata_0/'
- 'metadata_0.tsv',
+ 'supplement/',
+ 'supplement/python3_replay.py',
+ 'supplement/cli_replay.sh',
+ 'supplement/citations.bib',
+ 'supplement/recorded_metadata/',
+ 'supplement/recorded_metadata/'
+ 'dummy_plugin_identity_with_metadata_0/metadata_0.tsv',
}
with zipfile.ZipFile(out_fp, 'r') as myzip:
namelist_set = set(myzip.namelist())
@@ -438,12 +439,13 @@ class WriteReproducibilitySupplementTests(unittest.TestCase):
self.assertTrue(zipfile.is_zipfile(out_fp))
exp = {
- 'python3_replay.py',
- 'cli_replay.sh',
- 'citations.bib',
- 'recorded_metadata/',
- 'recorded_metadata/dummy_plugin_identity_with_metadata_0/'
- 'metadata_0.tsv',
+ 'supplement/',
+ 'supplement/python3_replay.py',
+ 'supplement/cli_replay.sh',
+ 'supplement/citations.bib',
+ 'supplement/recorded_metadata/',
+ 'supplement/recorded_metadata/'
+ 'dummy_plugin_identity_with_metadata_0/metadata_0.tsv',
}
with zipfile.ZipFile(out_fp, 'r') as myzip:
namelist_set = set(myzip.namelist())
=====================================
q2cli/tests/test_tools.py
=====================================
@@ -455,6 +455,90 @@ class TestExportToFileFormat(TestInspectMetadata):
self.assertEqual(success, result.output)
+class TestImport(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.runner = CliRunner()
+
+ cls.tempdir = tempfile.mkdtemp(prefix='qiime2-q2cli-test-temp-')
+
+ cls.in_dir1 = os.path.join(cls.tempdir, 'input1')
+ os.mkdir(cls.in_dir1)
+ with open(os.path.join(cls.in_dir1, 'ints.txt'), 'w') as fh:
+ for i in range(5):
+ fh.write(f'{i}\n')
+ fh.write('a\n')
+
+ cls.in_dir2 = os.path.join(cls.tempdir, 'input2')
+ os.mkdir(cls.in_dir2)
+ with open(os.path.join(cls.in_dir2, 'ints.txt'), 'w') as fh:
+ fh.write('1\n')
+ fh.write('a\n')
+ fh.write('3\n')
+
+ cls.cache = Cache(os.path.join(cls.tempdir, 'new_cache'))
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir)
+
+ def test_import_min_validate(self):
+ out_fp = os.path.join(self.tempdir, 'out1.qza')
+
+ # import with min allows format error outside of min purview
+ # (validate level min checks only first 5 items)
+ result = self.runner.invoke(tools, [
+ 'import', '--type', 'IntSequence1', '--input-path', self.in_dir1,
+ '--output-path', out_fp, '--validate-level', 'min'
+ ])
+ self.assertEqual(result.exit_code, 0)
+
+ # import with max should catch all format errors, max is default
+ result = self.runner.invoke(tools, [
+ 'import', '--type', 'IntSequence1', '--input-path',
+ self.in_dir1, '--output-path', out_fp
+ ])
+ self.assertEqual(result.exit_code, 1)
+ self.assertIn('Line 6 is not an integer', result.output)
+
+ out_fp = os.path.join(self.tempdir, 'out2.qza')
+
+ # import with min catches format errors within its purview
+ result = self.runner.invoke(tools, [
+ 'import', '--type', 'IntSequence1', '--input-path',
+ self.in_dir2, '--output-path', out_fp, '--validate-level', 'min'
+ ])
+ self.assertEqual(result.exit_code, 1)
+ self.assertIn('Line 2 is not an integer', result.output)
+
+ def test_cache_import_min_validate(self):
+ # import with min allows format error outside of min purview
+ # (validate level min checks only first 5 items)
+ result = self.runner.invoke(tools, [
+ 'cache-import', '--type', 'IntSequence1', '--input-path',
+ self.in_dir1, '--cache', str(self.cache.path), '--key', 'foo',
+ '--validate-level', 'min'
+ ])
+ self.assertEqual(result.exit_code, 0)
+
+ # import with max should catch all format errors, max is default
+ result = self.runner.invoke(tools, [
+ 'cache-import', '--type', 'IntSequence1', '--input-path',
+ self.in_dir1, '--cache', str(self.cache.path), '--key', 'foo'
+ ])
+ self.assertEqual(result.exit_code, 1)
+ self.assertIn('Line 6 is not an integer', result.output)
+
+ # import with min catches format errors within its purview
+ result = self.runner.invoke(tools, [
+ 'cache-import', '--type', 'IntSequence1', '--input-path',
+ self.in_dir2, '--cache', str(self.cache.path), '--key', 'foo',
+ '--validate-level', 'min'
+ ])
+ self.assertEqual(result.exit_code, 1)
+ self.assertIn('Line 2 is not an integer', result.output)
+
+
class TestCacheTools(unittest.TestCase):
def setUp(self):
get_dummy_plugin()
@@ -469,6 +553,8 @@ class TestCacheTools(unittest.TestCase):
self.art2 = Artifact.import_data('IntSequence1', [3, 4, 5])
self.art3 = Artifact.import_data('IntSequence1', [6, 7, 8])
self.art4 = Artifact.import_data('IntSequence2', [9, 10, 11])
+ self.to_import = os.path.join(self.tempdir.name, 'to_import')
+ self.art1.export_data(self.to_import)
self.cache = Cache(os.path.join(self.tempdir.name, 'new_cache'))
def tearDown(self):
@@ -632,6 +718,16 @@ class TestCacheTools(unittest.TestCase):
pool_output)
self.assertEqual(success, result.output)
+ def test_cache_import(self):
+ self.max_diff = None
+ result = self.runner.invoke(
+ tools, ['cache-import', '--type', 'IntSequence1', '--input-path',
+ self.to_import, '--cache', f'{self.cache.path}', '--key',
+ 'foo'])
+ success = 'Imported %s as IntSequenceDirectoryFormat to %s:foo\n' % \
+ (self.to_import, self.cache.path)
+ self.assertEqual(success, result.output)
+
def _get_cache_contents(cache):
"""Gets contents of cache not including contents of the artifacts
@@ -1071,7 +1167,12 @@ class TestReplay(unittest.TestCase):
self.assertEqual(result.exit_code, 0)
self.assertTrue(zipfile.is_zipfile(out_fp))
- exp = {'python3_replay.py', 'cli_replay.sh', 'citations.bib'}
+ exp = {
+ 'supplement/',
+ 'supplement/python3_replay.py',
+ 'supplement/cli_replay.sh',
+ 'supplement/citations.bib'
+ }
with zipfile.ZipFile(out_fp, 'r') as zfh:
self.assertEqual(exp, set(zfh.namelist()))
@@ -1086,13 +1187,15 @@ class TestReplay(unittest.TestCase):
self.assertTrue(zipfile.is_zipfile(out_fp))
exp = {
- 'python3_replay.py',
- 'cli_replay.sh',
- 'citations.bib',
- 'recorded_metadata/',
- 'recorded_metadata/dummy_plugin_identity_with_metadata_0/',
- 'recorded_metadata/dummy_plugin_identity_with_metadata_0/'
- 'metadata_0.tsv',
+ 'supplement/',
+ 'supplement/python3_replay.py',
+ 'supplement/cli_replay.sh',
+ 'supplement/citations.bib',
+ 'supplement/recorded_metadata/',
+ 'supplement/recorded_metadata/'
+ 'dummy_plugin_identity_with_metadata_0/',
+ 'supplement/recorded_metadata/'
+ 'dummy_plugin_identity_with_metadata_0/metadata_0.tsv',
}
with zipfile.ZipFile(out_fp, 'r') as zfh:
self.assertEqual(exp, set(zfh.namelist()))
@@ -1126,6 +1229,25 @@ class TestReplay(unittest.TestCase):
'no available usage drivers', str(result.exception)
)
+ def test_replay_supplement_zipfile(self):
+ with tempfile.TemporaryDirectory() as tempdir:
+ in_fp = os.path.join(self.tempdir, 'concated_ints.qza')
+ out_fp = os.path.join(tempdir, 'supplement.zip')
+
+ result = self.runner.invoke(
+ tools,
+ ['replay-supplement', '--in-fp', in_fp, '--out-fp', out_fp]
+ )
+ self.assertEqual(result.exit_code, 0)
+ self.assertTrue(zipfile.is_zipfile(out_fp))
+
+ unzipped_path = os.path.join(tempdir, 'extracted')
+ os.makedirs(unzipped_path)
+ with zipfile.ZipFile(out_fp, 'r') as zfh:
+ zfh.extractall(unzipped_path)
+
+ self.assertEqual(os.listdir(unzipped_path), ['supplement'])
+
if __name__ == "__main__":
unittest.main()
=====================================
q2cli/tests/test_usage.py
=====================================
@@ -7,8 +7,10 @@
# ----------------------------------------------------------------------------
import os
+import sys
import subprocess
import tempfile
+import unittest
from q2cli.core.usage import CLIUsage
@@ -189,11 +191,44 @@ def test_round_trip(action, example):
use = CLIUsage(enable_assertions=True)
example_f(use)
rendered = use.render()
+ if sys.platform.startswith('linux'):
+ # TODO: remove me when arrays are not used in shell
+ extra = dict(executable='/bin/bash')
+ else:
+ extra = dict()
with tempfile.TemporaryDirectory() as tmpdir:
for ref, data in use.get_example_data():
data.save(os.path.join(tmpdir, ref))
- subprocess.run([rendered],
+ subprocess.run(rendered,
shell=True,
check=True,
cwd=tmpdir,
- env={**os.environ})
+ env={**os.environ},
+ **extra)
+
+
+class ReplayResultCollectionTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.plugin = get_dummy_plugin()
+
+ def test_construct_and_access_collection(self):
+ action = self.plugin.actions['dict_of_ints']
+ use = CLIUsage()
+ action.examples['construct_and_access_collection'](use)
+ exp = """\
+## constructing result collection ##
+rc_name=rc-in/
+ext=.qza
+keys=( a b )
+names=( ints-a.qza ints-b.qza )
+construct_result_collection
+##
+qiime dummy-plugin dict-of-ints \\
+ --i-ints rc-in/ \\
+ --o-output rc-out/
+## accessing result collection member ##
+ln -s rc-out/b.qza ints-b-from-collection.qza
+##"""
+
+ self.assertEqual(exp, use.render())
=====================================
q2cli/util.py
=====================================
@@ -471,6 +471,10 @@ def _load_input_cache(fp):
def _load_input_file(fp):
import qiime2.sdk
+ from os.path import expanduser
+
+ # If there is a leading ~ we expand it to be the path to home
+ fp = expanduser(fp)
# test if valid
peek = None
View it on GitLab: https://salsa.debian.org/med-team/q2cli/-/commit/2dece37619415f674da2794ccf52bde9543f9488
--
View it on GitLab: https://salsa.debian.org/med-team/q2cli/-/commit/2dece37619415f674da2794ccf52bde9543f9488
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20240218/89569859/attachment-0001.htm>
More information about the debian-med-commit
mailing list