Source code for doot.workflow.structs.action_spec

  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 dataclasses import InitVar, dataclass, field
 21from uuid import UUID, uuid1
 22
 23# ##-- end stdlib imports
 24
 25# ##-- 3rd party imports
 26from pydantic import BaseModel, Field, field_validator, model_validator
 27from jgdv import Maybe, Proto
 28from jgdv.structs.chainguard import ChainGuard
 29from jgdv.structs.strang import CodeReference
 30from jgdv._abstract.protocols.general import SpecStruct_p, Buildable_p
 31from jgdv._abstract.protocols.pydantic import ProtocolModelMeta
 32# ##-- end 3rd party imports
 33
 34# ##-- 1st party imports
 35import doot
 36import doot.errors
 37
 38# ##-- end 1st party imports
 39
 40# ##-- types
 41# isort: off
 42import abc
 43import collections.abc
 44from typing import TYPE_CHECKING, cast, assert_type, assert_never
 45from typing import Generic, NewType, Never, Any
 46# Protocols:
 47from typing import Protocol, runtime_checkable
 48# Typing Decorators:
 49from typing import no_type_check, final, overload, override
 50# outside of type_checking for pydantic
 51from jgdv import Func  # noqa: TC002
 52if TYPE_CHECKING:
 53    from .. import _interface as API  # noqa: N812
 54    from jgdv import Maybe
 55    from typing import Final
 56    from typing import ClassVar, Any, LiteralString
 57    from typing import Self, Literal
 58    from typing import TypeGuard
 59    from collections.abc import Iterable, Iterator, Callable, Generator
 60    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 61
 62##--|
 63
 64# isort: on
 65# ##-- end types
 66
 67##-- logging
 68logging = logmod.getLogger(__name__)
 69##-- end logging
 70
 71ALIASES = doot.aliases.on_fail([]).action
 72
 73##--| body
[docs] 74class ActionSpec(BaseModel, Buildable_p, metaclass=ProtocolModelMeta, arbitrary_types_allowed=True): 75 """ 76 When an action isn't a full blown class, it gets wrapped in this, 77 which passes the action spec to the callable. 78 79 TODO: recogise arg prefixs and convert to correct type. 80 eg: path:a/relative/path -> Path(./a/relative/path) 81 path:/usr/bin/python -> Path(/usr/bin/python) 82 83 """ 84 do : Maybe[CodeReference] = Field(default=None) 85 args : list[Any] = Field(default_factory=list) 86 kwargs : ChainGuard = Field(default_factory=ChainGuard) 87 fun : Maybe[Func] = Field(default=None) 88
[docs] 89 @override 90 @classmethod 91 def build(cls, data:dict|list|ChainGuard|ActionSpec, *, fun:Maybe[Callable]=None) -> ActionSpec: # type: ignore[override] 92 match data: 93 case ActionSpec(): 94 return data 95 case list(): 96 action_spec = cls( 97 args=data, 98 fun=fun if callable(fun) else None, 99 ) 100 return action_spec 101 102 case dict() | ChainGuard(): 103 kwargs = ChainGuard({x:y for x,y in data.items() if x not in ActionSpec.model_fields}) 104 fun = data.get('fun', fun) 105 action_spec = cls( 106 do=data.get('do', None), 107 args=data.get('args',[]), 108 kwargs=kwargs, 109 fun=fun, 110 ) 111 return action_spec 112 case _: 113 raise doot.errors.StructLoadError("Unrecognized specification data", data)
114
[docs] 115 @field_validator("do", mode="before") 116 def _validate_do(cls, val:Maybe[str|CodeReference|Callable]) -> Maybe[CodeReference]: 117 aliases : dict[str, str] 118 match val: 119 case None: 120 return None 121 case CodeReference(): 122 return val 123 case str() if (aliases:=ALIASES()) and val in aliases: 124 alias = aliases[val] 125 return CodeReference(alias) 126 case str(): 127 return CodeReference(val) 128 case x if callable(x): 129 return CodeReference(x) 130 case _: 131 raise TypeError("Unrecognized action spec do type", val)
132 133 @override 134 def __str__(self) -> str: 135 result = [] 136 if isinstance(self.do, str): 137 result.append(f"do={self.do}") 138 elif self.do and hasattr(self.do, '__qualname__'): 139 result.append(f"do={self.do.__qualname__}") 140 elif self.do: 141 result.append(f"do={self.do.__class__.__qualname__}") 142 143 if self.args: 144 result.append(f"args={[str(x) for x in self.args]}") 145 if self.kwargs: 146 result.append(f"kwargs={self.kwargs}") 147 if self.fun and hasattr(self.fun, '__qualname__'): 148 result.append(f"calling={self.fun.__qualname__}") 149 elif self.fun: 150 result.append(f"calling={self.fun.__class__.__qualname__}") 151 152 return f"<ActionSpec: {' '.join(result)} >" 153 154 def __call__(self, task_state:dict) -> Any: 155 if self.fun is None: 156 raise doot.errors.StructError("Action Spec has not been finalised with a function", self) 157 158 return self.fun(self, task_state) 159
[docs] 160 @property 161 def params(self) -> ChainGuard: 162 return self.kwargs
163
[docs] 164 def set_function(self, *, fun:Maybe[API.Action_p|Func|type|ImportError]=None) -> None: 165 """ 166 Sets the function of the action spec. 167 if given a class, the class is built, 168 if given a callable, that is used directly. 169 170 """ 171 if fun is None: 172 assert(self.do is not None) 173 fun = self.do() 174 175 match fun: 176 case ImportError() as err: 177 raise err from None 178 case type() as x: 179 self.fun = x() 180 case x if callable(x): 181 self.fun = fun 182 case x: 183 raise doot.errors.StructError("Action Spec Given a non-callable fun: %s", fun)
184
[docs] 185 def verify(self, state:dict, *, fields:Maybe[list[str]]=None) -> None: 186 raise NotImplementedError()
187
[docs] 188 def verify_out(self, state:dict) -> None: 189 self.verify(state, fields=self.outState)