4
4
import os
5
5
import sys
6
6
import typing as ty
7
+ import logging
7
8
import attr
8
9
from ..engine .specs import (
9
10
LazyField ,
19
20
# Python < 3.8
20
21
from typing_extensions import get_origin , get_args # type: ignore
21
22
23
+ logger = logging .getLogger ("pydra" )
22
24
23
25
NO_GENERIC_ISSUBCLASS = sys .version_info .major == 3 and sys .version_info .minor < 10
24
26
@@ -56,6 +58,9 @@ class TypeParser(ty.Generic[T]):
56
58
the tree of more complex nested container types. Overrides 'coercible' to enable
57
59
you to carve out exceptions, such as TypeParser(list, coercible=[(ty.Iterable, list)],
58
60
not_coercible=[(str, list)])
61
+ allow_lazy_super : bool
62
+ Allow lazy fields to pass the type check if their types are superclasses of the
63
+ specified pattern (instead of matching or being subclasses of the pattern)
59
64
label : str
60
65
the label to be used to identify the type parser in error messages. Especially
61
66
useful when TypeParser is used as a converter in attrs.fields
@@ -64,6 +69,7 @@ class TypeParser(ty.Generic[T]):
64
69
tp : ty .Type [T ]
65
70
coercible : ty .List [ty .Tuple [TypeOrAny , TypeOrAny ]]
66
71
not_coercible : ty .List [ty .Tuple [TypeOrAny , TypeOrAny ]]
72
+ allow_lazy_super : bool
67
73
label : str
68
74
69
75
COERCIBLE_DEFAULT : ty .Tuple [ty .Tuple [type , type ], ...] = (
@@ -107,6 +113,7 @@ def __init__(
107
113
not_coercible : ty .Optional [
108
114
ty .Iterable [ty .Tuple [TypeOrAny , TypeOrAny ]]
109
115
] = NOT_COERCIBLE_DEFAULT ,
116
+ allow_lazy_super : bool = False ,
110
117
label : str = "" ,
111
118
):
112
119
def expand_pattern (t ):
@@ -135,6 +142,7 @@ def expand_pattern(t):
135
142
)
136
143
self .not_coercible = list (not_coercible ) if not_coercible is not None else []
137
144
self .pattern = expand_pattern (tp )
145
+ self .allow_lazy_super = allow_lazy_super
138
146
139
147
def __call__ (self , obj : ty .Any ) -> ty .Union [T , LazyField [T ]]:
140
148
"""Attempts to coerce the object to the specified type, unless the value is
@@ -161,7 +169,27 @@ def __call__(self, obj: ty.Any) -> ty.Union[T, LazyField[T]]:
161
169
if obj is attr .NOTHING :
162
170
coerced = attr .NOTHING # type: ignore[assignment]
163
171
elif isinstance (obj , LazyField ):
164
- self .check_type (obj .type )
172
+ try :
173
+ self .check_type (obj .type )
174
+ except TypeError as e :
175
+ if self .allow_lazy_super :
176
+ try :
177
+ # Check whether the type of the lazy field isn't a superclass of
178
+ # the type to check against, and if so, allow it due to permissive
179
+ # typing rules.
180
+ TypeParser (obj .type ).check_type (self .tp )
181
+ except TypeError :
182
+ raise e
183
+ else :
184
+ logger .info (
185
+ "Connecting lazy field %s to %s%s via permissive typing that "
186
+ "allows super-to-sub type connections" ,
187
+ obj ,
188
+ self .tp ,
189
+ self .label_str ,
190
+ )
191
+ else :
192
+ raise e
165
193
coerced = obj # type: ignore
166
194
elif isinstance (obj , StateArray ):
167
195
coerced = StateArray (self (o ) for o in obj ) # type: ignore[assignment]
0 commit comments