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 @@ def __str__(self) -> str:
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 @@ def __init__(
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 @@ def decorate_shellcmd(self, cmd: str) -> str:
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 @@ def __eq__(self, other) -> bool:
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 @@ async def pin(self) -> None:
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 @@ def pinfile(self) -> Path:
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 @@ async def cache_assets(self) -> None:
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 @@ async def remove_cache(self) -> None:
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 @@ def record_deployment_hash(self, hash_object) -> None:
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 @@ def remove(self) -> None:
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 ()
0 commit comments