Source code for doot.workflow.structs.artifact

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5
  6# Imports:
  7from __future__ import annotations
  8
  9# ##-- stdlib imports
 10import datetime
 11import enum
 12import functools as ftz
 13import importlib
 14import itertools as itz
 15import logging as logmod
 16import pathlib as pl
 17import re
 18import time
 19import weakref
 20from uuid import UUID, uuid1
 21
 22# ##-- end stdlib imports
 23
 24# ##-- 3rd party imports
 25from jgdv.structs.chainguard import ChainGuard
 26from jgdv.structs.dkey import DKey
 27from jgdv.structs.locator import Location
 28
 29# ##-- end 3rd party imports
 30
 31# ##-- 1st party imports
 32import doot
 33import doot.errors
 34from .. import _interface as API  # noqa: N812
 35from .._interface import ArtifactStatus_e
 36
 37# ##-- end 1st party imports
 38
 39# ##-- types
 40# isort: off
 41# General
 42import abc
 43import collections.abc
 44import typing
 45import types
 46from typing import cast, assert_type, assert_never
 47from typing import Generic, NewType, Never
 48from typing import no_type_check, final, override, overload
 49# Protocols and Interfaces:
 50from typing import Protocol, runtime_checkable
 51if typing.TYPE_CHECKING:
 52    from typing import Final, ClassVar, Any, Self
 53    from typing import Literal, LiteralString
 54    from typing import TypeGuard
 55    from collections.abc import Iterable, Iterator, Callable, Generator
 56    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 57
 58    from jgdv import Maybe, TimeDelta
 59
 60# isort: on
 61# ##-- end types
 62
 63##-- logging
 64logging = logmod.getLogger(__name__)
 65##-- end logging
 66
[docs] 67class TaskArtifact(Location): 68 """ 69 A Location, but specialized to represent artifacts/files 70 An concrete or abstract artifact a task can produce or consume. 71 72 Tasks can depend on both concrete and abstract: 73 depends_on=['file:>/a/file.txt', 'file:>*.txt', 'file:>?.txt'] 74 and can be a requirement for concrete or *solo* abstract artifacts: 75 required_for=['file:>a/file.txt', 'file:>?.txt'] 76 77 """ 78 __slots__ = ("priority",) 79 priority : int 80 81 def __init__(self, *args:Any, **kwargs:Any) -> None: 82 super().__init__(*args, **kwargs) 83 self.priority = API.DEFAULT_PRIORITY 84 85 def __bool__(self): 86 return self.exists() 87
[docs] 88 @property 89 def parent(self) -> pl.Path: 90 return self.path.parent
91
[docs] 92 def is_stale(self, *, delta:Maybe[TimeDelta]=None) -> bool: 93 """ whether the artifact itself is stale 94 delta defaults to 1 day 95 """ 96 match delta: 97 case None: 98 return self < datetime.timedelta(days=1) 99 case datetime.timedelta(): 100 return self < delta 101 case _: 102 raise NotImplementedError()
103
[docs] 104 def reify(self, other:pl.Path|Location) -> Maybe[TaskArtifact]: # noqa: PLR0912, PLR0915 105 """ 106 Apply a more concrete path onto this location 107 """ 108 if self.is_concrete(): 109 raise NotImplementedError("Can't reify an already concrete location", self, other) 110 111 match other: 112 case pl.Path() | str(): 113 other = Location(other) 114 case _: 115 pass 116 117 result = [] 118 add_rest = False 119 # Compare path 120 for x,y in itz.zip_longest(self.body_parent, other.body_parent): 121 if add_rest: 122 result.append(y or x) 123 continue 124 match x, y: 125 case _, None: 126 result.append(x) 127 case None, _: 128 result.append(y) 129 case _, _ if x == y: 130 result.append(x) 131 case str() as x, _ if x == self.Wild.rec_glob: 132 add_rest = True 133 result.append(y) 134 case str() as x, str() if x in self.Wild: 135 result.append(y) 136 case str(), str() as y if y in self.Wild: 137 result.append(x) 138 case str(), str(): 139 return None 140 141 logging.debug("%s and %s match on path", self, other) 142 # Compare the stem/ext 143 stem, ext = "", "" 144 match self.stem, other.stem: 145 case None, None: 146 pass 147 case None, str() as y: 148 stem = y 149 case str() as x, None: 150 stem = x 151 case str() as x, str() as y if x == y: 152 stem = x 153 case (xa, ya), (xb, yb) if xa == xb and ya == yb: 154 stem = ya 155 case (xa, ya), str() as xb: 156 stem = xb 157 case _, _: 158 return None 159 160 logging.debug("%s and %s match on stem", self, other) 161 match self.ext(), other.ext(): 162 case None, None: 163 pass 164 case (xa, ya), (xb, yb) if xa == xb and ya == yb: 165 ext = ya 166 case (x, y), str() as yb: 167 ext = yb 168 case (_, x), None: 169 ext = x 170 case None, (_, y): 171 ext = y 172 case str() as x, None: 173 ext = x 174 case None, str() as y: 175 ext = y 176 case str() as x, str() as y if x == y: 177 ext = x 178 case _, _: 179 return None 180 181 logging.debug("%s and %s match", self, other) 182 result.append(f"{stem}{ext}") 183 184 return self.__class__("/".join(result))
185
[docs] 186 def exists(self) -> bool: 187 as_path = self.path 188 expanded = doot.locs[as_path] # type: ignore[attr-defined] 189 return expanded.exists()
190
[docs] 191 @override 192 def is_concrete(self) -> bool: 193 if self.Marks.abstract in self: 194 return False 195 try: 196 _ = doot.locs.expand(self) 197 except KeyError: 198 return False 199 else: 200 return True
201 202
[docs] 203 def get_status(self) -> ArtifactStatus_e: 204 """ Get the status of the artifact, 205 To start, either declared, or exists. 206 TODO: add a stale check 207 TODO: add a to-clean check 208 """ 209 if self.exists(): 210 return ArtifactStatus_e.EXISTS 211 212 213 return ArtifactStatus_e.DECLARED