snk-cli 0.5.1__tar.gz → 0.5.3__tar.gz
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.
- {snk_cli-0.5.1 → snk_cli-0.5.3}/PKG-INFO +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/__about__.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/cli.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/config/config.py +11 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/dynamic_typer.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/config.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/env.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/profile.py +1 -1
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/run.py +16 -23
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/script.py +1 -1
- snk_cli-0.5.3/tests/test_SnkConfig.py +125 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_validate.py +6 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/.github/workflows/publish.yml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/.github/workflows/tests.yml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/.gitignore +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/LICENSE.txt +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/README.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/index.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/cli.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/config.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/dynamic_typer.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/options.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/subcommands.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/testing.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/utils.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/validate.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/docs/reference/workflow.md +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/mkdocs.yml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/pyproject.toml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/conda.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/config/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/config/utils.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/options/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/options/option.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/options/utils.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/subcommands/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/testing.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/utils.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/validate.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/src/snk_cli/workflow.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/conftest.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/artic_v4.1.bed +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/config.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/cov.fasta +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/print_config/Snakefile +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/print_config/cli.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/print_config/config.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/print_config/snk.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/cli.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/config.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/resources/data.txt +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/snk.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/things/__about__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/workflow/Snakefile +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/workflow/envs/wget.yml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/workflow/profiles/base/config.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/workflow/profiles/slurm/config.yaml +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/data/workflow/workflow/scripts/hello.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/__init__.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_dynamic_options.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_profile.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_run.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_snk_config.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_subcommands.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_cli/test_workflow_cli.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_conda_env.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/test_dynamic_typer.py +0 -0
- {snk_cli-0.5.1 → snk_cli-0.5.3}/tests/utils.py +0 -0
|
@@ -259,5 +259,5 @@ class CLI(DynamicTyper):
|
|
|
259
259
|
info_dict["conda_prefix_dir"] = str(self.conda_prefix_dir)
|
|
260
260
|
info_dict["singularity_prefix_dir"] = str(self.singularity_prefix_dir)
|
|
261
261
|
info_dict["workflow_dir_path"] = str(self.workflow.path)
|
|
262
|
-
|
|
262
|
+
self.echo(json.dumps(info_dict, indent=2))
|
|
263
263
|
|
|
@@ -205,6 +205,17 @@ class SnkConfig:
|
|
|
205
205
|
>>> snk_config.to_yaml(Path("snk.yaml"))
|
|
206
206
|
"""
|
|
207
207
|
config_dict = {k: v for k, v in vars(self).items() if not k.startswith("_")}
|
|
208
|
+
def convert_paths(data):
|
|
209
|
+
if isinstance(data, dict):
|
|
210
|
+
return {key: convert_paths(value) for key, value in data.items()}
|
|
211
|
+
elif isinstance(data, list):
|
|
212
|
+
return [convert_paths(item) for item in data]
|
|
213
|
+
elif isinstance(data, Path):
|
|
214
|
+
return str(data)
|
|
215
|
+
return data
|
|
216
|
+
|
|
217
|
+
config_dict = convert_paths(config_dict)
|
|
218
|
+
|
|
208
219
|
with open(path, "w") as f:
|
|
209
220
|
yaml.dump(config_dict, f)
|
|
210
221
|
|
|
@@ -152,7 +152,7 @@ class DynamicTyper:
|
|
|
152
152
|
if option.type is Enum or option.choices:
|
|
153
153
|
if not option.choices:
|
|
154
154
|
raise ValueError(f"Enum type {option.name} requires choices to be defined.")
|
|
155
|
-
annotation_type = Enum(f'{option.name}
|
|
155
|
+
annotation_type = Enum(f'{option.name}', {str(e): str(e) for e in option.choices})
|
|
156
156
|
if default:
|
|
157
157
|
try:
|
|
158
158
|
default = annotation_type(default)
|
|
@@ -213,10 +213,9 @@ class RunApp(DynamicTyper):
|
|
|
213
213
|
# Set up conda frontend
|
|
214
214
|
conda_found = check_command_available("conda")
|
|
215
215
|
if not conda_found and verbose:
|
|
216
|
-
|
|
216
|
+
self.log(
|
|
217
217
|
"Conda not found! Install conda to use environments.\n",
|
|
218
|
-
|
|
219
|
-
err=True,
|
|
218
|
+
color=typer.colors.MAGENTA,
|
|
220
219
|
)
|
|
221
220
|
|
|
222
221
|
if conda_found and self.snk_config.conda and not no_conda:
|
|
@@ -228,10 +227,9 @@ class RunApp(DynamicTyper):
|
|
|
228
227
|
)
|
|
229
228
|
if not check_command_available("mamba"):
|
|
230
229
|
if verbose:
|
|
231
|
-
|
|
230
|
+
self.log(
|
|
232
231
|
"Could not find mamba, using conda instead...",
|
|
233
|
-
|
|
234
|
-
err=True,
|
|
232
|
+
color=typer.colors.MAGENTA,
|
|
235
233
|
)
|
|
236
234
|
args.append("--conda-frontend=conda")
|
|
237
235
|
else:
|
|
@@ -260,7 +258,7 @@ class RunApp(DynamicTyper):
|
|
|
260
258
|
if configs:
|
|
261
259
|
args.extend(["--config", *configs])
|
|
262
260
|
if verbose:
|
|
263
|
-
|
|
261
|
+
self.log(f"snakemake {' '.join(args)}\n", color=typer.colors.MAGENTA)
|
|
264
262
|
if not keep_snakemake and Path(".snakemake").exists():
|
|
265
263
|
keep_snakemake = True
|
|
266
264
|
try:
|
|
@@ -281,7 +279,7 @@ class RunApp(DynamicTyper):
|
|
|
281
279
|
if status:
|
|
282
280
|
sys.exit(status)
|
|
283
281
|
if not keep_snakemake and Path(".snakemake").exists():
|
|
284
|
-
|
|
282
|
+
self.log("Cleaning up '.snakemake' folder... use --keep-snakemake to keep.")
|
|
285
283
|
shutil.rmtree(".snakemake")
|
|
286
284
|
|
|
287
285
|
def _save_dag(self, snakemake_args: List[str], filename: Path):
|
|
@@ -320,11 +318,10 @@ class RunApp(DynamicTyper):
|
|
|
320
318
|
)
|
|
321
319
|
with open(filename, "w") as output_file:
|
|
322
320
|
if self.verbose:
|
|
323
|
-
|
|
321
|
+
self.log(f"Saving dag to {filename}", color=typer.colors.MAGENTA)
|
|
324
322
|
subprocess.run(["cat"], stdin=dot_process.stdout, stdout=output_file)
|
|
325
323
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
326
|
-
|
|
327
|
-
raise typer.Exit(1)
|
|
324
|
+
self.error("Graphviz `dot` command not found! Please install.", exit=True)
|
|
328
325
|
|
|
329
326
|
@contextmanager
|
|
330
327
|
def _copy_resources(
|
|
@@ -355,10 +352,9 @@ class RunApp(DynamicTyper):
|
|
|
355
352
|
|
|
356
353
|
def copy_resource(src: Path, dst: Path, symlink: bool = False):
|
|
357
354
|
if self.verbose:
|
|
358
|
-
|
|
355
|
+
self.log(
|
|
359
356
|
f" - Copying resource '{src}' to '{dst}'",
|
|
360
|
-
|
|
361
|
-
err=True,
|
|
357
|
+
color=typer.colors.MAGENTA,
|
|
362
358
|
)
|
|
363
359
|
target_is_directory = src.is_dir()
|
|
364
360
|
if symlink:
|
|
@@ -380,10 +376,9 @@ class RunApp(DynamicTyper):
|
|
|
380
376
|
if resources_folder.exists():
|
|
381
377
|
resources.insert(0, Path("resources"))
|
|
382
378
|
if self.verbose and resources:
|
|
383
|
-
|
|
379
|
+
self.log(
|
|
384
380
|
f"Copying {len(resources)} resources to working directory...",
|
|
385
|
-
|
|
386
|
-
err=True,
|
|
381
|
+
color=typer.colors.MAGENTA
|
|
387
382
|
)
|
|
388
383
|
try:
|
|
389
384
|
for resource in resources:
|
|
@@ -394,10 +389,9 @@ class RunApp(DynamicTyper):
|
|
|
394
389
|
copy_resource(abs_path, destination, symlink=symlink_resources)
|
|
395
390
|
copied_resources.append(destination)
|
|
396
391
|
elif self.verbose:
|
|
397
|
-
|
|
392
|
+
self.log(
|
|
398
393
|
f" - Resource '{resource.name}' already exists! Skipping...",
|
|
399
|
-
|
|
400
|
-
err=True,
|
|
394
|
+
color=typer.colors.MAGENTA,
|
|
401
395
|
)
|
|
402
396
|
yield
|
|
403
397
|
finally:
|
|
@@ -406,10 +400,9 @@ class RunApp(DynamicTyper):
|
|
|
406
400
|
for copied_resource in copied_resources:
|
|
407
401
|
if copied_resource.exists():
|
|
408
402
|
if self.verbose:
|
|
409
|
-
|
|
403
|
+
self.log(
|
|
410
404
|
f"Deleting '{copied_resource.name}' resource...",
|
|
411
|
-
|
|
412
|
-
err=True,
|
|
405
|
+
color=typer.colors.MAGENTA,
|
|
413
406
|
)
|
|
414
407
|
remove_resource(copied_resource)
|
|
415
408
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from snk_cli.config.config import SnkConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_snk_config_creation():
|
|
6
|
+
snk_config = SnkConfig()
|
|
7
|
+
assert snk_config.tagline == "A Snakemake workflow CLI generated with Snk"
|
|
8
|
+
assert snk_config.font == "small"
|
|
9
|
+
assert snk_config.resources == []
|
|
10
|
+
assert snk_config.cli == {}
|
|
11
|
+
assert snk_config.symlink_resources is False
|
|
12
|
+
assert snk_config.version is None
|
|
13
|
+
assert snk_config.skip_missing is False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_validate_resources_with_existing_files(tmp_path):
|
|
17
|
+
resources = [tmp_path / "file1", tmp_path / "file2"]
|
|
18
|
+
for resource in resources:
|
|
19
|
+
resource.touch()
|
|
20
|
+
snk_config = SnkConfig()
|
|
21
|
+
# Should not raise an exception
|
|
22
|
+
snk_config.validate_resources(resources)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_validate_resources_with_missing_files(tmp_path):
|
|
26
|
+
resources = [tmp_path / "file1", tmp_path / "missing_file"]
|
|
27
|
+
resources[0].touch()
|
|
28
|
+
snk_config = SnkConfig()
|
|
29
|
+
with pytest.raises(FileNotFoundError):
|
|
30
|
+
snk_config.validate_resources(resources)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_add_resources(tmp_path):
|
|
34
|
+
resources = [tmp_path / "file1", tmp_path / "file2"]
|
|
35
|
+
for resource in resources:
|
|
36
|
+
resource.touch()
|
|
37
|
+
snk_config = SnkConfig()
|
|
38
|
+
snk_config.add_resources(resources)
|
|
39
|
+
assert len(snk_config.resources) == 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_from_path_with_existing_file(tmp_path):
|
|
43
|
+
config_file = tmp_path / "snk.yaml"
|
|
44
|
+
config_file.touch()
|
|
45
|
+
config_file.write_text("logo: test_logo")
|
|
46
|
+
snk_config = SnkConfig.from_path(config_file)
|
|
47
|
+
assert snk_config._snk_config_path == config_file
|
|
48
|
+
|
|
49
|
+
def test_missing_file(tmp_path):
|
|
50
|
+
config_file = tmp_path / "snk.yaml"
|
|
51
|
+
with pytest.raises(FileNotFoundError):
|
|
52
|
+
SnkConfig.from_path(config_file)
|
|
53
|
+
|
|
54
|
+
def test_empty_file(tmp_path):
|
|
55
|
+
config_file = tmp_path / "snk.yaml"
|
|
56
|
+
config_file.touch()
|
|
57
|
+
with pytest.raises(ValueError):
|
|
58
|
+
SnkConfig.from_path(config_file)
|
|
59
|
+
|
|
60
|
+
def test_invalid_yaml(tmp_path):
|
|
61
|
+
config_file = tmp_path / "snk.yaml"
|
|
62
|
+
config_file.touch()
|
|
63
|
+
config_file.write_text("logo: test_logo\ninvalid_yaml")
|
|
64
|
+
with pytest.raises(Exception):
|
|
65
|
+
SnkConfig.from_path(config_file)
|
|
66
|
+
|
|
67
|
+
def test_from_dir_with_existing_file(tmp_path):
|
|
68
|
+
config_file = tmp_path / ".snk"
|
|
69
|
+
config_file.touch()
|
|
70
|
+
config_file.write_text("logo: test_logo")
|
|
71
|
+
# catch warning
|
|
72
|
+
with pytest.warns(DeprecationWarning):
|
|
73
|
+
snk_config = SnkConfig.from_workflow_dir(tmp_path)
|
|
74
|
+
assert snk_config._snk_config_path == config_file
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_from_path_with_missing_file(tmp_path):
|
|
78
|
+
config_file = tmp_path / "missing_file.yaml"
|
|
79
|
+
# assert it raises FileNotFoundError
|
|
80
|
+
with pytest.raises(FileNotFoundError):
|
|
81
|
+
SnkConfig.from_path(config_file)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_to_yaml(tmp_path):
|
|
85
|
+
snk_config = SnkConfig(art="test_art", logo="test_logo")
|
|
86
|
+
yaml_file = tmp_path / "snk.yaml"
|
|
87
|
+
snk_config.to_yaml(yaml_file)
|
|
88
|
+
assert yaml_file.exists()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_save(tmp_path):
|
|
92
|
+
config_file = tmp_path / "snk.yaml"
|
|
93
|
+
snk_config = SnkConfig(_snk_config_path=config_file)
|
|
94
|
+
snk_config.art = "new_art"
|
|
95
|
+
snk_config.save()
|
|
96
|
+
saved_snk_config = SnkConfig.from_path(config_file)
|
|
97
|
+
assert saved_snk_config.art == "new_art"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_version_from_about_file(tmp_path):
|
|
101
|
+
config_file = tmp_path / "snk.yaml"
|
|
102
|
+
about_file = tmp_path / "__about__.py"
|
|
103
|
+
about_file.touch()
|
|
104
|
+
about_file.write_text("__version__ = '0.0.1'")
|
|
105
|
+
config_file.write_text("version: __about__.py")
|
|
106
|
+
snk_config = SnkConfig.from_path(config_file)
|
|
107
|
+
assert snk_config.version == "0.0.1"
|
|
108
|
+
|
|
109
|
+
def test_version_from_about_file_with_missing_file(tmp_path):
|
|
110
|
+
config_file = tmp_path / "snk.yaml"
|
|
111
|
+
about_file = tmp_path / "__about__.py"
|
|
112
|
+
config_file.write_text("version: __about__.py")
|
|
113
|
+
with pytest.raises(FileNotFoundError):
|
|
114
|
+
SnkConfig.from_path(config_file)
|
|
115
|
+
|
|
116
|
+
def test_paths_are_written_as_strings(tmp_path):
|
|
117
|
+
config_file = tmp_path / "snk.yaml"
|
|
118
|
+
snk_config = SnkConfig(snakefile=tmp_path / "snakefile", resources=[tmp_path / "file1", tmp_path / "file2"])
|
|
119
|
+
snk_config.to_yaml(config_file)
|
|
120
|
+
with open(config_file) as f:
|
|
121
|
+
config = f.read()
|
|
122
|
+
print(config)
|
|
123
|
+
assert f"snakefile: {tmp_path / 'snakefile'}" in config
|
|
124
|
+
assert f"- {tmp_path / 'file1'}" in config
|
|
125
|
+
assert f"- {tmp_path / 'file2'}" in config
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|