11import typing as ty
2+ import abc
23from typing_extensions import Self
34import attrs
45from pydra .utils .typing import StateArray
6+ from pydra .utils .hash import hash_single
57from . import node
68
79if ty .TYPE_CHECKING :
1315TypeOrAny = ty .Union [type , ty .Any ]
1416
1517
16- @attrs .define (auto_attribs = True , kw_only = True )
17- class LazyField (ty .Generic [T ]):
18+ @attrs .define (kw_only = True )
19+ class LazyField (ty .Generic [T ], metaclass = abc . ABCMeta ):
1820 """Lazy fields implement promises."""
1921
20- node : node .Node
2122 field : str
2223 type : TypeOrAny
23- # Set of splitters that have been applied to the lazy field. Note that the splitter
24- # specifications are transformed to a tuple[tuple[str, ...], ...] form where the
25- # outer tuple is the outer product, the inner tuple are inner products (where either
26- # product can be of length==1)
27- splits : ty .FrozenSet [ty .Tuple [ty .Tuple [str , ...], ...]] = attrs .field (
28- factory = frozenset , converter = frozenset
29- )
3024 cast_from : ty .Optional [ty .Type [ty .Any ]] = None
31- # type_checked will be set to False after it is created but defaults to True here for
32- # ease of testing
33- type_checked : bool = True
25+ type_checked : bool = False
3426
3527 def __bytes_repr__ (self , cache ):
36- yield type (self ).__name__ .encode ()
37- yield self .name .encode ()
38- yield self .field .encode ()
39-
40- def cast (self , new_type : TypeOrAny ) -> Self :
41- """ "casts" the lazy field to a new type
42-
43- Parameters
44- ----------
45- new_type : type
46- the type to cast the lazy-field to
47-
48- Returns
49- -------
50- cast_field : LazyField
51- a copy of the lazy field with the new type
52- """
53- return type (self )[new_type ](
54- name = self .name ,
55- field = self .field ,
56- type = new_type ,
57- splits = self .splits ,
58- cast_from = self .cast_from if self .cast_from else self .type ,
59- )
60-
61- # def split(self, splitter: Splitter) -> Self:
62- # """ "Splits" the lazy field over an array of nodes by replacing the sequence type
63- # of the lazy field with StateArray to signify that it will be "split" across
64-
65- # Parameters
66- # ----------
67- # splitter : str or ty.Tuple[str, ...] or ty.List[str]
68- # the splitter to append to the list of splitters
69- # """
70- # from pydra.utils.typing import (
71- # TypeParser,
72- # ) # pylint: disable=import-outside-toplevel
73-
74- # splits = self.splits | set([LazyField.normalize_splitter(splitter)])
75- # # Check to see whether the field has already been split over the given splitter
76- # if splits == self.splits:
77- # return self
78-
79- # # Modify the type of the lazy field to include the split across a state-array
80- # inner_type, prev_split_depth = TypeParser.strip_splits(self.type)
81- # assert prev_split_depth <= 1
82- # if inner_type is ty.Any:
83- # type_ = StateArray[ty.Any]
84- # elif TypeParser.matches_type(inner_type, list):
85- # item_type = TypeParser.get_item_type(inner_type)
86- # type_ = StateArray[item_type]
87- # else:
88- # raise TypeError(
89- # f"Cannot split non-sequence field {self} of type {inner_type}"
90- # )
91- # if prev_split_depth:
92- # type_ = StateArray[type_]
93- # return type(self)[type_](
94- # name=self.name,
95- # field=self.field,
96- # type=type_,
97- # splits=splits,
98- # )
99-
100- # # def combine(self, combiner: str | list[str]) -> Self:
28+ yield type (self ).__name__ .encode () + b"("
29+ yield from bytes (hash_single (self .source , cache ))
30+ yield b"field=" + self .field .encode ()
31+ yield b"type=" + bytes (hash_single (self .type , cache ))
32+ yield b"cast_from=" + bytes (hash_single (self .cast_from , cache ))
33+ yield b")"
10134
10235 def _apply_cast (self , value ):
10336 """\" Casts\" the value from the retrieved type if a cast has been applied to
@@ -110,19 +43,24 @@ def _apply_cast(self, value):
11043 return value
11144
11245
113- @attrs .define (auto_attribs = True , kw_only = True )
46+ @attrs .define (kw_only = True )
11447class LazyInField (LazyField [T ]):
11548
49+ workflow : "Workflow" = attrs .field ()
50+
11651 attr_type = "input"
11752
11853 def __eq__ (self , other ):
11954 return (
12055 isinstance (other , LazyInField )
12156 and self .field == other .field
12257 and self .type == other .type
123- and self .splits == other .splits
12458 )
12559
60+ @property
61+ def source (self ):
62+ return self .workflow
63+
12664 def get_value (self , wf : "Workflow" , state_index : ty .Optional [int ] = None ) -> ty .Any :
12765 """Return the value of a lazy field.
12866
@@ -155,8 +93,31 @@ def apply_splits(obj, depth):
15593 value = self ._apply_cast (value )
15694 return value
15795
96+ def cast (self , new_type : TypeOrAny ) -> Self :
97+ """ "casts" the lazy field to a new type
15898
99+ Parameters
100+ ----------
101+ new_type : type
102+ the type to cast the lazy-field to
103+
104+ Returns
105+ -------
106+ cast_field : LazyInField
107+ a copy of the lazy field with the new type
108+ """
109+ return type (self )[new_type ](
110+ workflow = self .workflow ,
111+ field = self .field ,
112+ type = new_type ,
113+ cast_from = self .cast_from if self .cast_from else self .type ,
114+ )
115+
116+
117+ @attrs .define (kw_only = True )
159118class LazyOutField (LazyField [T ]):
119+
120+ node : node .Node
160121 attr_type = "output"
161122
162123 def get_value (self , wf : "Workflow" , state_index : ty .Optional [int ] = None ) -> ty .Any :
@@ -178,16 +139,15 @@ def get_value(self, wf: "Workflow", state_index: ty.Optional[int] = None) -> ty.
178139 TypeParser ,
179140 ) # pylint: disable=import-outside-toplevel
180141
181- node = getattr (wf , self .name )
182- result = node .result (state_index = state_index )
142+ result = self .node .result (state_index = state_index )
183143 if result is None :
184144 raise RuntimeError (
185- f"Could not find results of '{ node .name } ' node in a sub-directory "
186- f"named '{ node .checksum } ' in any of the cache locations.\n "
187- + "\n " .join (str (p ) for p in set (node .cache_locations ))
145+ f"Could not find results of '{ self . node .name } ' node in a sub-directory "
146+ f"named '{ self . node .checksum } ' in any of the cache locations.\n "
147+ + "\n " .join (str (p ) for p in set (self . node .cache_locations ))
188148 + f"\n \n This is likely due to hash changes in '{ self .name } ' node inputs. "
189- f"Current values and hashes: { node .inputs } , "
190- f"{ node .inputs ._hashes } \n \n "
149+ f"Current values and hashes: { self . node .inputs } , "
150+ f"{ self . node .inputs ._hashes } \n \n "
191151 "Set loglevel to 'debug' in order to track hash changes "
192152 "throughout the execution of the workflow.\n \n "
193153 "These issues may have been caused by `bytes_repr()` methods "
@@ -224,3 +184,27 @@ def get_nested_results(res, depth: int):
224184 value = get_nested_results (result , depth = split_depth )
225185 value = self ._apply_cast (value )
226186 return value
187+
188+ @property
189+ def source (self ):
190+ return self .node
191+
192+ def cast (self , new_type : TypeOrAny ) -> Self :
193+ """ "casts" the lazy field to a new type
194+
195+ Parameters
196+ ----------
197+ new_type : type
198+ the type to cast the lazy-field to
199+
200+ Returns
201+ -------
202+ cast_field : LazyOutField
203+ a copy of the lazy field with the new type
204+ """
205+ return type (self )[new_type ](
206+ node = self .node ,
207+ field = self .field ,
208+ type = new_type ,
209+ cast_from = self .cast_from if self .cast_from else self .type ,
210+ )
0 commit comments