snakemake-interface-software-deployment-plugins 0.2.3__tar.gz → 0.3.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 (11) hide show
  1. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/PKG-INFO +1 -1
  2. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/pyproject.toml +1 -1
  3. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/snakemake_interface_software_deployment_plugins/__init__.py +75 -26
  4. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/snakemake_interface_software_deployment_plugins/registry/__init__.py +7 -0
  5. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/snakemake_interface_software_deployment_plugins/registry/plugin.py +2 -0
  6. snakemake_interface_software_deployment_plugins-0.3.0/snakemake_interface_software_deployment_plugins/settings.py +46 -0
  7. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/snakemake_interface_software_deployment_plugins/tests.py +2 -2
  8. snakemake_interface_software_deployment_plugins-0.2.3/snakemake_interface_software_deployment_plugins/settings.py +0 -17
  9. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/LICENSE +0 -0
  10. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/README.md +0 -0
  11. {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.3.0}/snakemake_interface_software_deployment_plugins/_common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snakemake-interface-software-deployment-plugins
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: This package provides a stable interface for interactions between Snakemake and its software deployment plugins.
5
5
  License: MIT
6
6
  Author: Johannes Köster
@@ -5,7 +5,7 @@ license = "MIT"
5
5
  name = "snakemake-interface-software-deployment-plugins"
6
6
  packages = [{include = "snakemake_interface_software_deployment_plugins"}]
7
7
  readme = "README.md"
8
- version = "0.2.3"
8
+ version = "0.3.0"
9
9
 
10
10
  [tool.poetry.dependencies]
11
11
  argparse-dataclass = "^2.0.0"
@@ -5,11 +5,10 @@ __license__ = "MIT"
5
5
 
6
6
  from abc import ABC, abstractmethod
7
7
  from copy import copy
8
- from dataclasses import dataclass, field, fields
8
+ from dataclasses import dataclass, field
9
9
  import hashlib
10
10
  from pathlib import Path
11
- import sys
12
- from typing import Any, ClassVar, Dict, Optional, Tuple, Type
11
+ from typing import Any, ClassVar, Dict, Iterable, Optional, Self, Tuple, Type
13
12
  import subprocess as sp
14
13
 
15
14
  from snakemake_interface_software_deployment_plugins.settings import (
@@ -17,28 +16,84 @@ from snakemake_interface_software_deployment_plugins.settings import (
17
16
  )
18
17
 
19
18
 
20
- _MANAGED_FIELDS = {
21
- "settings",
22
- "_managed_hash_store",
23
- "_managed_deployment_hash_store",
24
- "_obj_hash",
25
- }
26
-
27
-
28
- @dataclass
29
19
  class EnvSpecBase(ABC):
30
- within: Optional["EnvSpecBase"]
31
- fallback: Optional["EnvSpecBase"]
20
+ def __init__(self):
21
+ self.within: Optional["EnvSpecBase"] = None
22
+ self.fallback: Optional["EnvSpecBase"] = None
23
+ self.kind: str = self.__class__.__module__.common_settings.provides
24
+ self._obj_hash: Optional[int] = None
32
25
 
33
26
  @classmethod
34
27
  def env_cls(cls):
35
- return sys.modules[__name__].EnvBase
28
+ return cls.__module__.EnvBase
29
+
30
+ @abstractmethod
31
+ def identity_attributes(self) -> Iterable[str]:
32
+ """Yield the attributes of the subclass that uniquely identify the
33
+ environment spec. These are used for hashing and equality comparison.
34
+ """
35
+ ...
36
+
37
+ @abstractmethod
38
+ def source_path_attributes(self) -> Iterable[str]:
39
+ """Return iterable of attributes of the subclass that represent paths that are
40
+ supposed to be interpreted as being relative to the defining rule.
41
+
42
+ For example, this would be attributes pointing to conda environment files.
43
+ """
44
+ ...
45
+
46
+ def has_source_paths(self) -> bool:
47
+ if any(self.source_path_attributes()):
48
+ return True
49
+ if self.within is not None and self.within.has_source_paths():
50
+ return True
51
+ if self.fallback is not None and self.fallback.has_source_paths():
52
+ return True
53
+ return False
54
+
55
+ def modify_source_paths(self, modify_func) -> Self:
56
+ if self.has_source_paths():
57
+ self_or_copied = copy(self)
58
+ else:
59
+ return self
60
+ for attr in self_or_copied.source_path_attributes():
61
+ setattr(self_or_copied, attr, modify_func(getattr(self_or_copied, attr)))
62
+
63
+ if self_or_copied.within is not None:
64
+ self_or_copied.within = self_or_copied.within.modify_source_paths(
65
+ modify_func
66
+ )
67
+
68
+ if self_or_copied.fallback is not None:
69
+ self_or_copied.fallback = self_or_copied.fallback.modify_source_paths(
70
+ modify_func
71
+ )
72
+ return self_or_copied
36
73
 
37
74
  def __or__(self, other: "EnvSpecBase") -> "EnvSpecBase":
38
75
  copied = copy(self)
39
76
  copied.fallback = other
77
+ copied._obj_hash = None
40
78
  return copied
41
79
 
80
+ def managed_identity_attributes(self) -> Iterable[str]:
81
+ yield from self.identity_attributes()
82
+ yield "kind"
83
+ yield "within"
84
+ yield "fallback"
85
+
86
+ def __hash__(self) -> int:
87
+ return hash(
88
+ tuple(getattr(self, attr) for attr in self.managed_identity_attributes())
89
+ )
90
+
91
+ def __eq__(self, other) -> bool:
92
+ return self.__class__ == other.__class__ and all(
93
+ getattr(self, attr) == getattr(other, attr)
94
+ for attr in self.managed_identity_attributes()
95
+ )
96
+
42
97
 
43
98
  @dataclass
44
99
  class EnvBase:
@@ -119,20 +174,14 @@ class EnvBase:
119
174
  def __hash__(self) -> int:
120
175
  # take the hash of all fields by settings, _managed_hash_store and _managed_deployment_hash_store
121
176
  if self._obj_hash is None:
122
- self._obj_hash = hash(
123
- tuple(
124
- getattr(self, field.name)
125
- for field in fields(self)
126
- if field.name not in _MANAGED_FIELDS
127
- )
128
- )
177
+ self._obj_hash = hash(self.hash())
129
178
  return self._obj_hash
130
179
 
131
180
  def __eq__(self, other) -> bool:
132
- return self.__class__ == other.__class__ and all(
133
- getattr(self, field.name) == getattr(other, field.name)
134
- for field in fields(self)
135
- if field.name not in _MANAGED_FIELDS
181
+ return (
182
+ self.__class__ == other.__class__
183
+ and self.spec == other.spec
184
+ and self.hash() == other.hash()
136
185
  )
137
186
 
138
187
 
@@ -6,6 +6,7 @@ __license__ = "MIT"
6
6
  import types
7
7
  from typing import Mapping
8
8
  from snakemake_interface_software_deployment_plugins.settings import (
9
+ CommonSettings,
9
10
  SoftwareDeploymentSettingsBase,
10
11
  )
11
12
 
@@ -30,6 +31,7 @@ class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
30
31
  """Load a plugin by name."""
31
32
  return Plugin(
32
33
  _name=name,
34
+ common_settings=module.common_settings,
33
35
  _software_deployment_settings_cls=getattr(
34
36
  module, "SoftwareDeploymentSettings", None
35
37
  ),
@@ -38,6 +40,11 @@ class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
38
40
 
39
41
  def expected_attributes(self) -> Mapping[str, AttributeType]:
40
42
  return {
43
+ "common_settings": AttributeType(
44
+ cls=CommonSettings,
45
+ mode=AttributeMode.REQUIRED,
46
+ kind=AttributeKind.OBJECT,
47
+ ),
41
48
  "SoftwareDeploymentSettings": AttributeType(
42
49
  cls=SoftwareDeploymentSettingsBase,
43
50
  mode=AttributeMode.OPTIONAL,
@@ -7,6 +7,7 @@ from dataclasses import dataclass
7
7
  from typing import Optional, Type
8
8
  from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase
9
9
  from snakemake_interface_software_deployment_plugins.settings import (
10
+ CommonSettings,
10
11
  SoftwareDeploymentSettingsBase,
11
12
  )
12
13
  import snakemake_interface_software_deployment_plugins._common as common
@@ -16,6 +17,7 @@ from snakemake_interface_common.plugin_registry.plugin import PluginBase
16
17
 
17
18
  @dataclass
18
19
  class Plugin(PluginBase):
20
+ common_settings: CommonSettings
19
21
  _software_deployment_settings_cls: Optional[Type[SoftwareDeploymentSettingsBase]]
20
22
  _env_cls: Type[EnvBase]
21
23
  _env_spec_cls: Type[EnvSpecBase]
@@ -0,0 +1,46 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ import snakemake_interface_common.plugin_registry.plugin
5
+
6
+
7
+ @dataclass
8
+ class SoftwareDeploymentSettingsBase(
9
+ snakemake_interface_common.plugin_registry.plugin.SettingsBase
10
+ ):
11
+ """Base class for software deployment settings.
12
+
13
+ Software deployment plugins can define a subclass of this class,
14
+ named 'SoftwareDeploymentProviderSettings'.
15
+ """
16
+
17
+ pass
18
+
19
+
20
+ @dataclass
21
+ class CommonSettings:
22
+ """Common settings for software deployment plugins.
23
+
24
+ This class is used to define common settings for software deployment plugins.
25
+
26
+ Attributes
27
+ ----------
28
+ provides : str
29
+ The kind of the software environment provided (e.g. conda, container).
30
+ This should not return something describing the tool to provide the software
31
+ environment but the resulting environment itself. For example,
32
+ it should return "conda" instead of mamba, rattler, pixi etc., or
33
+ "container" instead of docker, singularity, podman, or
34
+ "envmodules" instead of lmod, environment-modules, etc.
35
+ Snakemake will ensure that the user only activates one plugin per provided
36
+ kind.
37
+ """
38
+
39
+ provides: str
40
+
41
+ def __post_init__(self):
42
+ if not self.provides.isidentifier():
43
+ raise ValueError(
44
+ "CommonSettings.provides must be a valid Python identifier, but "
45
+ f"is {self.provides}."
46
+ )
@@ -46,10 +46,10 @@ class TestSoftwareDeploymentBase(ABC):
46
46
  """
47
47
  ...
48
48
 
49
+ @abstractmethod
49
50
  def get_software_deployment_provider_settings(
50
51
  self,
51
- ) -> Optional[SoftwareDeploymentSettingsBase]:
52
- return None
52
+ ) -> Optional[SoftwareDeploymentSettingsBase]: ...
53
53
 
54
54
  def test_shellcmd(self, tmp_path):
55
55
  env = self._get_env(tmp_path)
@@ -1,17 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- import snakemake_interface_common.plugin_registry.plugin
5
-
6
-
7
- @dataclass
8
- class SoftwareDeploymentSettingsBase(
9
- snakemake_interface_common.plugin_registry.plugin.SettingsBase
10
- ):
11
- """Base class for software deployment settings.
12
-
13
- Software deployment plugins can define a subclass of this class,
14
- named 'SoftwareDeploymentProviderSettings'.
15
- """
16
-
17
- pass