snk-cli 0.3.4__tar.gz → 0.5.0__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.
Files changed (69) hide show
  1. {snk_cli-0.3.4 → snk_cli-0.5.0}/PKG-INFO +4 -3
  2. {snk_cli-0.3.4 → snk_cli-0.5.0}/pyproject.toml +3 -2
  3. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/__about__.py +1 -1
  4. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/cli.py +1 -5
  5. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/dynamic_typer.py +11 -2
  6. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/options/option.py +1 -0
  7. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/options/utils.py +4 -1
  8. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/env.py +1 -2
  9. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/profile.py +21 -24
  10. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/workflow.py +0 -54
  11. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/snk.yaml +2 -0
  12. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_dynamic_options.py +34 -1
  13. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_profile.py +5 -3
  14. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_snk_config.py +8 -1
  15. {snk_cli-0.3.4 → snk_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  16. {snk_cli-0.3.4 → snk_cli-0.5.0}/.github/workflows/tests.yml +0 -0
  17. {snk_cli-0.3.4 → snk_cli-0.5.0}/.gitignore +0 -0
  18. {snk_cli-0.3.4 → snk_cli-0.5.0}/LICENSE.txt +0 -0
  19. {snk_cli-0.3.4 → snk_cli-0.5.0}/README.md +0 -0
  20. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/index.md +0 -0
  21. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/cli.md +0 -0
  22. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/config.md +0 -0
  23. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/dynamic_typer.md +0 -0
  24. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/options.md +0 -0
  25. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/subcommands.md +0 -0
  26. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/testing.md +0 -0
  27. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/utils.md +0 -0
  28. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/validate.md +0 -0
  29. {snk_cli-0.3.4 → snk_cli-0.5.0}/docs/reference/workflow.md +0 -0
  30. {snk_cli-0.3.4 → snk_cli-0.5.0}/mkdocs.yml +0 -0
  31. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/__init__.py +0 -0
  32. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/conda.py +0 -0
  33. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/config/__init__.py +0 -0
  34. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/config/config.py +0 -0
  35. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/config/utils.py +0 -0
  36. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/options/__init__.py +0 -0
  37. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/__init__.py +0 -0
  38. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/config.py +0 -0
  39. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/run.py +0 -0
  40. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/subcommands/script.py +0 -0
  41. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/testing.py +0 -0
  42. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/utils.py +0 -0
  43. {snk_cli-0.3.4 → snk_cli-0.5.0}/src/snk_cli/validate.py +0 -0
  44. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/__init__.py +0 -0
  45. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/conftest.py +0 -0
  46. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/artic_v4.1.bed +0 -0
  47. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/config.yaml +0 -0
  48. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/cov.fasta +0 -0
  49. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/print_config/Snakefile +0 -0
  50. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/print_config/cli.py +0 -0
  51. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/print_config/config.yaml +0 -0
  52. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/print_config/snk.yaml +0 -0
  53. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/cli.py +0 -0
  54. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/config.yaml +0 -0
  55. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/resources/data.txt +0 -0
  56. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/things/__about__.py +0 -0
  57. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/workflow/Snakefile +0 -0
  58. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/workflow/envs/wget.yml +0 -0
  59. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/workflow/profiles/base/config.yaml +0 -0
  60. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/workflow/profiles/slurm/config.yaml +0 -0
  61. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/data/workflow/workflow/scripts/hello.py +0 -0
  62. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/__init__.py +0 -0
  63. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_run.py +0 -0
  64. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_subcommands.py +0 -0
  65. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_validate.py +0 -0
  66. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_cli/test_workflow_cli.py +0 -0
  67. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_conda_env.py +0 -0
  68. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/test_dynamic_typer.py +0 -0
  69. {snk_cli-0.3.4 → snk_cli-0.5.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snk-cli
3
- Version: 0.3.4
3
+ Version: 0.5.0
4
4
  Project-URL: Documentation, https://github.com/wytamma/snk-cli#readme
5
5
  Project-URL: Issues, https://github.com/wytamma/snk-cli/issues
6
6
  Project-URL: Source, https://github.com/wytamma/snk-cli
@@ -19,11 +19,12 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
19
  Requires-Python: >=3.8
20
20
  Requires-Dist: art~=5.9
21
21
  Requires-Dist: datrie>=0.8.2
22
- Requires-Dist: gitpython~=3.1
23
22
  Requires-Dist: makefun~=1.15
24
23
  Requires-Dist: pulp<2.8
24
+ Requires-Dist: rich>=10.11.0
25
+ Requires-Dist: shellingham>=1.3.0
25
26
  Requires-Dist: snakemake>=7
26
- Requires-Dist: typer[all]~=0.9
27
+ Requires-Dist: typer~=0.9
27
28
  Description-Content-Type: text/markdown
28
29
 
29
30
  # snk-cli
@@ -26,8 +26,9 @@ classifiers = [
26
26
  ]
27
27
  dependencies = [
28
28
  "snakemake>=7",
29
- "typer[all]~=0.9",
30
- "GitPython~=3.1",
29
+ "typer~=0.9",
30
+ "shellingham >=1.3.0",
31
+ "rich >=10.11.0",
31
32
  "pulp<2.8", # Pin pulp <2.8 for snakemake: https://github.com/snakemake/snakemake/issues/2607
32
33
  "art~=5.9",
33
34
  "makefun~=1.15",
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Wytamma Wirth <wytamma.wirth@me.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.3.4"
4
+ __version__ = "0.5.0"
@@ -60,16 +60,12 @@ class CLI(DynamicTyper):
60
60
  )
61
61
  else:
62
62
  self.snk_config = snk_config
63
- if self.workflow.version:
64
- self.version = self.workflow.version
65
- else:
66
- self.version = self.snk_config.version
63
+ self.version = self.snk_config.version
67
64
  if self.snk_config.configfile:
68
65
  self.snakemake_config = load_configfile(self.snk_config.configfile)
69
66
  else:
70
67
  self.snakemake_config = load_workflow_snakemake_config(workflow_dir_path)
71
68
  self.options = build_dynamic_cli_options(self.snakemake_config, self.snk_config)
72
- print(self.options)
73
69
  # try to load the snakefile from the snakemake config
74
70
  snakefile = self.snk_config.snakefile
75
71
  if not snakefile:
@@ -2,6 +2,7 @@ import typer
2
2
  from typing import List, Callable
3
3
  from inspect import signature, Parameter
4
4
  from makefun import with_signature
5
+ from enum import Enum
5
6
 
6
7
  from .options import Option
7
8
  import sys
@@ -146,17 +147,25 @@ class DynamicTyper:
146
147
  >>> create_cli_parameter(option)
147
148
  Parameter('foo', kind=Parameter.POSITIONAL_OR_KEYWORD, default=typer.Option(..., help='[CONFIG] A number'), annotation=int)
148
149
  """
150
+ annotation_type = option.type
151
+ default = option.default
152
+ if option.type is Enum or option.choices:
153
+ if not option.choices:
154
+ raise ValueError(f"Enum type {option.name} requires choices to be defined.")
155
+ annotation_type = Enum('DynamicEnum', {e: e for e in option.choices})
156
+ if default:
157
+ default = annotation_type(default)
149
158
  return Parameter(
150
159
  option.name,
151
160
  kind=Parameter.POSITIONAL_OR_KEYWORD,
152
161
  default=typer.Option(
153
- ... if option.required else option.default,
162
+ ... if option.required else default,
154
163
  *[option.flag, option.short_flag] if option.short else [],
155
164
  help=f"{option.help}",
156
165
  rich_help_panel="Workflow Configuration",
157
166
  hidden=option.hidden,
158
167
  ),
159
- annotation=option.type,
168
+ annotation=annotation_type,
160
169
  )
161
170
 
162
171
  def check_if_option_passed_via_command_line(self, option: Option):
@@ -16,3 +16,4 @@ class Option:
16
16
  short_flag: Optional[str]
17
17
  hidden: bool = False
18
18
  from_annotation: bool = False
19
+ choices: Optional[list] = None
@@ -1,8 +1,9 @@
1
- from typing import List, Any
1
+ from typing import List
2
2
  from ..config.config import SnkConfig
3
3
  from ..utils import get_default_type, flatten
4
4
  from .option import Option
5
5
  from pathlib import Path
6
+ from enum import Enum
6
7
 
7
8
  types = {
8
9
  "int": int,
@@ -17,6 +18,7 @@ types = {
17
18
  "list[str]": List[str],
18
19
  "list[path]": List[Path],
19
20
  "list[int]": List[int],
21
+ "enum": Enum,
20
22
  }
21
23
 
22
24
  def get_keys_from_annotation(annotations):
@@ -78,6 +80,7 @@ def create_option_from_annotation(
78
80
  short_flag=f"-{short}" if short else None,
79
81
  hidden=hidden,
80
82
  from_annotation=from_annotation,
83
+ choices=annotation_values.get(f"{annotation_key}:choices", None),
81
84
  )
82
85
 
83
86
 
@@ -11,8 +11,7 @@ from snk_cli.conda import conda_environment_factory
11
11
  from ..workflow import Workflow
12
12
  from rich.console import Console
13
13
  from rich.syntax import Syntax
14
- from snakemake.deployment.conda import Conda, Env, CreateCondaEnvironmentException
15
- from snk_cli.config.config import get_config_from_workflow_dir
14
+ from snakemake.deployment.conda import Conda, CreateCondaEnvironmentException
16
15
 
17
16
  from concurrent.futures import ProcessPoolExecutor
18
17
 
@@ -78,31 +78,28 @@ class ProfileApp(DynamicTyper):
78
78
  import os
79
79
  import platform
80
80
 
81
- try:
82
- if platform.system() == "Windows":
83
- os.startfile(file_path)
84
- elif platform.system() == "Darwin": # macOS
85
- subprocess.call(("open", file_path))
86
- else: # Linux and other Unix-like systems
87
- editors = ["nano", "vim", "vi"]
88
- editor = None
89
- for e in editors:
90
- if (
91
- subprocess.call(
92
- ["which", e], stdout=subprocess.PIPE, stderr=subprocess.PIPE
93
- )
94
- == 0
95
- ):
96
- editor = e
97
- break
98
- if editor:
99
- subprocess.call([editor, file_path])
100
- else:
101
- self.error(
102
- "No suitable text editor found. Please install nano or vim."
81
+ if platform.system() == "Windows":
82
+ os.startfile(file_path)
83
+ elif platform.system() == "Darwin": # macOS
84
+ subprocess.call(("open", file_path))
85
+ else: # Linux and other Unix-like systems
86
+ editors = ["nano", "vim", "vi"]
87
+ editor = None
88
+ for e in editors:
89
+ if (
90
+ subprocess.call(
91
+ ["which", e], stdout=subprocess.PIPE, stderr=subprocess.PIPE
103
92
  )
104
- except Exception as e:
105
- print(f"An error occurred: {e}")
93
+ == 0
94
+ ):
95
+ editor = e
96
+ break
97
+ if editor:
98
+ subprocess.call([editor, file_path])
99
+ else:
100
+ self.error(
101
+ "No suitable text editor found. Please install nano or vim."
102
+ )
106
103
 
107
104
  def edit(self, name: str = typer.Argument(..., help="The name of the profile.")):
108
105
  profile_path = self._get_profile_path(name)
@@ -1,7 +1,6 @@
1
1
  from pathlib import Path
2
2
  import sys
3
3
  from typing import Optional
4
- from git import Repo, InvalidGitRepositoryError
5
4
  import importlib.util
6
5
  import os
7
6
 
@@ -30,61 +29,8 @@ class Workflow:
30
29
  """
31
30
  self.path = path
32
31
  self.editable = self.check_is_editable()
33
- if self.editable: # editable mode
34
- self.repo = None
35
- else:
36
- try:
37
- self.repo = Repo(path)
38
- except InvalidGitRepositoryError:
39
- self.repo = None
40
32
  self.name = self.path.name
41
33
 
42
-
43
- @property
44
- def tag(self):
45
- """
46
- Gets the tag of the workflow.
47
-
48
- Returns:
49
- str: The tag of the workflow, or None if no tag is found.
50
- """
51
- try:
52
- tag = self.repo.git.describe(["--tags", "--exact-match"])
53
- except Exception:
54
- tag = None
55
- return tag
56
-
57
- @property
58
- def commit(self):
59
- """
60
- Gets the commit SHA of the workflow.
61
-
62
- Returns:
63
- str: The commit SHA of the workflow.
64
- """
65
- try:
66
- sha = self.repo.head.object.hexsha
67
- commit = self.repo.git.rev_parse(sha, short=8)
68
- except Exception:
69
- commit = None
70
- return commit
71
-
72
- @property
73
- def version(self):
74
- """
75
- Gets the version of the workflow.
76
-
77
- Returns:
78
- str: The version of the workflow, or None if no version is found.
79
- """
80
- if self.repo is None:
81
- return None
82
- if self.tag:
83
- version = self.tag
84
- else:
85
- version = self.commit
86
- return version
87
-
88
34
  @property
89
35
  def executable(self):
90
36
  """
@@ -9,6 +9,8 @@ cli:
9
9
  type: bool
10
10
  null_annotation:
11
11
  default: null
12
+ enum:
13
+ choices: [a, b, c]
12
14
  test:
13
15
  another:
14
16
  test:
@@ -35,9 +35,42 @@ def test_create_option_from_annotation(
35
35
  assert option.default == "default_value"
36
36
  assert option.updated is False
37
37
  assert option.help == "Test help"
38
- assert option.type == str
38
+ assert option.type is str
39
39
  assert option.required is True
40
40
 
41
+ def test_create_option_from_annotation_with_short(
42
+ default_annotation_values, default_default_values
43
+ ):
44
+ default_annotation_values["test:short"] = "t"
45
+
46
+ option = create_option_from_annotation(
47
+ "test", default_annotation_values, default_default_values
48
+ )
49
+
50
+ assert option.short == "t"
51
+
52
+ def test_create_option_from_annotation_with_hidden(
53
+ default_annotation_values, default_default_values
54
+ ):
55
+ default_annotation_values["test:hidden"] = True
56
+
57
+ option = create_option_from_annotation(
58
+ "test", default_annotation_values, default_default_values
59
+ )
60
+
61
+ assert option.hidden is True
62
+
63
+ def test_create_option_from_annotation_with_enums(
64
+ default_annotation_values, default_default_values
65
+ ):
66
+ default_annotation_values["test:choices"] = ["a", "b", "c"]
67
+
68
+ option = create_option_from_annotation(
69
+ "test", default_annotation_values, default_default_values
70
+ )
71
+
72
+ assert option.choices == ["a", "b", "c"]
73
+
41
74
 
42
75
  @pytest.fixture
43
76
  def default_snakemake_config():
@@ -15,7 +15,8 @@ def mock_platform_system():
15
15
 
16
16
  @pytest.fixture
17
17
  def mock_os_startfile():
18
- if platform.system() == 'Windows':
18
+ # Only patch os.startfile if the platform is Windows
19
+ if hasattr(os, 'startfile'):
19
20
  with patch('os.startfile') as mock:
20
21
  yield mock
21
22
  else:
@@ -26,6 +27,7 @@ def mock_subprocess_call():
26
27
  with patch('subprocess.call') as mock:
27
28
  yield mock
28
29
 
30
+ @pytest.mark.skipif(platform.system() != 'Windows', reason="Requires Windows")
29
31
  def test_open_text_editor_windows(mock_platform_system, mock_os_startfile, local_runner: SnkCliRunner):
30
32
  mock_platform_system.return_value = 'Windows'
31
33
  file_path = Path('tests/data/workflow/workflow/profiles/slurm/config.yaml')
@@ -48,7 +50,7 @@ def test_open_text_editor_linux(mock_platform_system, mock_subprocess_call, loca
48
50
  file_path = Path('tests/data/workflow/workflow/profiles/slurm/config.yaml')
49
51
 
50
52
  with patch('subprocess.call') as mock_call:
51
- mock_call.side_effect = [1, 1, 0] # Mocking 'which' command results: nano not found, vim not found, vi found
53
+ mock_call.side_effect = [1, 1, 0, 0] # Mocking 'which' command results: nano not found, vim not found, vi found
52
54
 
53
55
  res = local_runner(["profile", "edit", "slurm"])
54
56
  assert res.exit_code == 0, res.stderr
@@ -65,5 +67,5 @@ def test_open_text_editor_no_editor_found(mock_platform_system, mock_subprocess_
65
67
 
66
68
  with patch('typer.secho') as mock_print:
67
69
  res = local_runner(["profile", "edit", "slurm"])
68
- assert res.exit_code == 0, res.stderr
70
+ assert res.exit_code == 1, res.stderr
69
71
  mock_print.assert_called_once_with("No suitable text editor found. Please install nano or vim.", fg='red', err=True)
@@ -41,4 +41,11 @@ def test_non_standard_configfile(tmp_path):
41
41
  runner = dynamic_runner({}, SnkConfig(configfile=tmp_path / "config2.yaml"), tmp_path=tmp_path)
42
42
  res = runner.invoke(["run"])
43
43
  assert res.exit_code == 0, res.stderr
44
- assert "config2" in res.stdout, res.stderr
44
+ assert "config2" in res.stdout, res.stderr
45
+
46
+ def test_snk_config_with_enums(tmp_path):
47
+ runner = dynamic_runner({}, SnkConfig(cli={"test": {"choices": ["enum1", "enum2"], "type": "enum"}}), tmp_path=tmp_path)
48
+ res = runner.invoke(["run", "--help"])
49
+ assert res.exit_code == 0, res.stderr
50
+ assert "enum1" in res.stdout, res.stderr
51
+ assert "enum2" in res.stdout, res.stderr
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