snakemake-interface-software-deployment-plugins 0.7.2__tar.gz → 0.7.4__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 (10) hide show
  1. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/PKG-INFO +1 -1
  2. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/pyproject.toml +1 -1
  3. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/__init__.py +42 -16
  4. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/tests.py +16 -6
  5. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/LICENSE +0 -0
  6. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/README.md +0 -0
  7. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/_common.py +0 -0
  8. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/registry/__init__.py +0 -0
  9. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/registry/plugin.py +0 -0
  10. {snakemake_interface_software_deployment_plugins-0.7.2 → snakemake_interface_software_deployment_plugins-0.7.4}/snakemake_interface_software_deployment_plugins/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snakemake-interface-software-deployment-plugins
3
- Version: 0.7.2
3
+ Version: 0.7.4
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.7.2"
8
+ version = "0.7.4"
9
9
 
10
10
  [tool.poetry.dependencies]
11
11
  argparse-dataclass = "^2.0.0"
@@ -9,7 +9,17 @@ from dataclasses import dataclass, field
9
9
  import hashlib
10
10
  from pathlib import Path
11
11
  import shutil
12
- from typing import Any, Dict, Iterable, Optional, Self, Tuple, Type, Union
12
+ from typing import (
13
+ Any,
14
+ ClassVar,
15
+ Dict,
16
+ Iterable,
17
+ Optional,
18
+ Self,
19
+ Tuple,
20
+ Type,
21
+ Union,
22
+ )
13
23
  import subprocess as sp
14
24
 
15
25
  from snakemake_interface_software_deployment_plugins.settings import (
@@ -124,7 +134,18 @@ class EnvSpecBase(ABC):
124
134
 
125
135
 
126
136
  class EnvBase(ABC):
127
- _cache: Dict[Tuple[Type["EnvBase"], Optional["EnvBase"]], Any] = {}
137
+ _cache: ClassVar[Dict[Tuple[Type["EnvBase"], Optional["EnvBase"]], Any]] = {}
138
+ spec: EnvSpecBase
139
+ within: Optional["EnvBase"]
140
+ settings: Optional[SoftwareDeploymentSettingsBase]
141
+ shell_executable: str
142
+ tempdir: Path
143
+ _cache_prefix: Path
144
+ _deployment_prefix: Path
145
+ _pinfile_prefix: Path
146
+ _managed_hash_store: Optional[str] = None
147
+ _managed_deployment_hash_store: Optional[str] = None
148
+ _obj_hash: Optional[int] = None
128
149
 
129
150
  def __init__(
130
151
  self,
@@ -142,9 +163,6 @@ class EnvBase(ABC):
142
163
  self.settings: Optional[SoftwareDeploymentSettingsBase] = settings
143
164
  self.shell_executable: str = shell_executable
144
165
  self.tempdir = tempdir
145
- self._managed_hash_store: Optional[str] = None
146
- self._managed_deployment_hash_store: Optional[str] = None
147
- self._obj_hash: Optional[int] = None
148
166
  self._deployment_prefix: Path = deployment_prefix
149
167
  self._cache_prefix: Path = cache_prefix
150
168
  self._pinfile_prefix: Path = pinfile_prefix
@@ -177,6 +195,21 @@ class EnvBase(ABC):
177
195
  """
178
196
  ...
179
197
 
198
+ def is_deployable(self) -> bool:
199
+ """Overwrite this in case the deployability of the environment depends on
200
+ the spec or settings."""
201
+ return isinstance(self, DeployableEnvBase)
202
+
203
+ def is_pinnable(self) -> bool:
204
+ """Overwrite this in case the pinability of the environment depends on
205
+ the spec or settings."""
206
+ return isinstance(self, PinnableEnvBase)
207
+
208
+ def is_cacheable(self) -> bool:
209
+ """Overwrite this in case the cacheability of the environment depends on
210
+ the spec or settings."""
211
+ return isinstance(self, CacheableEnvBase)
212
+
180
213
  @abstractmethod
181
214
  def record_hash(self, hash_object) -> None:
182
215
  """Update given hash object (using hash_object.update()) such that it changes
@@ -241,7 +274,7 @@ class EnvBase(ABC):
241
274
  )
242
275
 
243
276
 
244
- class PinnableEnvBase(ABC):
277
+ class PinnableEnvBase(EnvBase, ABC):
245
278
  @classmethod
246
279
  @abstractmethod
247
280
  def pinfile_extension(cls) -> str: ...
@@ -256,7 +289,6 @@ class PinnableEnvBase(ABC):
256
289
 
257
290
  @property
258
291
  def pinfile(self) -> Path:
259
- assert isinstance(self, EnvBase)
260
292
  ext = self.pinfile_extension()
261
293
  if not ext.startswith("."):
262
294
  raise ValueError("pinfile_extension must start with a dot.")
@@ -265,7 +297,7 @@ class PinnableEnvBase(ABC):
265
297
  )
266
298
 
267
299
 
268
- class CacheableEnvBase(ABC):
300
+ class CacheableEnvBase(EnvBase, ABC):
269
301
  async def get_cache_assets(self) -> Iterable[str]: ...
270
302
 
271
303
  @abstractmethod
@@ -277,12 +309,10 @@ class CacheableEnvBase(ABC):
277
309
 
278
310
  @property
279
311
  def cache_path(self) -> Path:
280
- assert isinstance(self, EnvBase)
281
312
  return self._cache_prefix
282
313
 
283
314
  async def remove_cache(self) -> None:
284
315
  """Remove the cached environment assets."""
285
- assert isinstance(self, EnvBase)
286
316
  for asset in await self.get_cache_assets():
287
317
  asset_path = self.cache_path / asset
288
318
  if asset_path.exists():
@@ -297,7 +327,7 @@ class CacheableEnvBase(ABC):
297
327
  )
298
328
 
299
329
 
300
- class DeployableEnvBase(ABC):
330
+ class DeployableEnvBase(EnvBase, ABC):
301
331
  @abstractmethod
302
332
  def is_deployment_path_portable(self) -> bool:
303
333
  """Return whether the deployment path matters for the environment, i.e.
@@ -325,7 +355,6 @@ class DeployableEnvBase(ABC):
325
355
  deployment is senstivive to the path (e.g. in case of conda, which patches
326
356
  the RPATH in binaries).
327
357
  """
328
- assert isinstance(self, EnvBase)
329
358
  self.record_hash(hash_object)
330
359
  if not self.is_deployment_path_portable():
331
360
  hash_object.update(str(self._deployment_prefix).encode())
@@ -337,24 +366,21 @@ class DeployableEnvBase(ABC):
337
366
 
338
367
  def managed_remove(self) -> None:
339
368
  """Remove the deployed environment, handling exceptions."""
340
- assert isinstance(self, EnvBase)
341
369
  try:
342
370
  self.remove()
343
371
  except Exception as e:
344
372
  raise WorkflowError(f"Removal of {self.spec} failed: {e}")
345
373
 
346
374
  async def managed_deploy(self) -> None:
347
- assert isinstance(self, EnvBase)
348
375
  try:
349
376
  await self.deploy()
350
377
  except Exception as e:
351
378
  raise WorkflowError(f"Deployment of {self.spec} failed: {e}")
352
379
 
353
380
  def deployment_hash(self) -> str:
354
- assert isinstance(self, EnvBase)
355
381
  return self._managed_generic_hash("deployment_hash")
356
382
 
357
383
  @property
358
384
  def deployment_path(self) -> Path:
359
- assert isinstance(self, EnvBase) and self._deployment_prefix is not None
385
+ assert self._deployment_prefix is not None
360
386
  return self._deployment_prefix / self.deployment_hash()
@@ -90,11 +90,13 @@ class TestSoftwareDeploymentBase(ABC):
90
90
  cmd = env.managed_decorate_shellcmd(self.get_test_cmd())
91
91
  assert sp.run(cmd, shell=True, executable=self.shell_executable).returncode == 0
92
92
 
93
- def test_archive(self, tmp_path):
93
+ def test_cache(self, tmp_path):
94
94
  env = self._get_env(tmp_path)
95
- if not isinstance(env, CacheableEnvBase):
95
+ if not env.is_cacheable():
96
96
  pytest.skip("Environment either not deployable or not cacheable.")
97
97
 
98
+ assert isinstance(env, CacheableEnvBase)
99
+
98
100
  asyncio.run(env.cache_assets())
99
101
 
100
102
  self._deploy(env, tmp_path)
@@ -103,9 +105,11 @@ class TestSoftwareDeploymentBase(ABC):
103
105
 
104
106
  def test_pin(self, tmp_path):
105
107
  env = self._get_env(tmp_path)
106
- if not isinstance(env, PinnableEnvBase):
108
+ if not env.is_pinnable():
107
109
  pytest.skip("Environment is not pinnable.")
108
110
 
111
+ assert isinstance(env, PinnableEnvBase)
112
+
109
113
  asyncio.run(env.pin())
110
114
  assert env.pinfile.exists()
111
115
  print("Pinfile content:", env.pinfile.read_text(), sep="\n")
@@ -125,10 +129,15 @@ class TestSoftwareDeploymentBase(ABC):
125
129
 
126
130
  def test_source_path_attributes(self):
127
131
  spec = self.get_env_spec()
132
+
133
+ def is_source_file_or_none(attr: str) -> bool:
134
+ val = getattr(spec, attr)
135
+ return val is None or isinstance(val, EnvSpecSourceFile)
136
+
128
137
  assert all(
129
138
  isinstance(attr, str)
130
139
  and hasattr(spec, attr)
131
- and isinstance(getattr(spec, attr), (EnvSpecSourceFile, None))
140
+ and is_source_file_or_none(attr)
132
141
  for attr in spec.source_path_attributes()
133
142
  ), "bug in plugin: all source path attributes must be of type EnvSpecSourceFile"
134
143
 
@@ -164,9 +173,10 @@ class TestSoftwareDeploymentBase(ABC):
164
173
  pinfile_prefix=pinfile_prefix,
165
174
  )
166
175
 
167
- def _deploy(self, env: DeployableEnvBase, tmp_path):
168
- if not isinstance(env, DeployableEnvBase):
176
+ def _deploy(self, env: EnvBase, tmp_path):
177
+ if not env.is_deployable():
169
178
  pytest.skip("Environment is not deployable.")
170
179
 
180
+ assert isinstance(env, DeployableEnvBase)
171
181
  asyncio.run(env.deploy())
172
182
  assert any((tmp_path / "deployments").iterdir())