Skip to content

Commit e4a2a09

Browse files
committed
added label to type parser
1 parent a53ec8d commit e4a2a09

File tree

3 files changed

+34
-17
lines changed

3 files changed

+34
-17
lines changed

pydra/engine/helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ def make_klass(spec):
261261
type=tp,
262262
**kwargs,
263263
)
264-
type_checker = TypeParser[newfield.type](newfield.type)
264+
checker_label = f"'{name}' field of {spec.name}"
265+
type_checker = TypeParser[newfield.type](newfield.type, label=checker_label)
265266
if newfield.type in (MultiInputObj, MultiInputFile):
266267
converter = attr.converters.pipe(ensure_list, type_checker)
267268
elif newfield.type in (MultiOutputObj, MultiOutputFile):

pydra/engine/specs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,8 @@ def collect_additional_outputs(self, inputs, output_dir, outputs):
450450
input_value = getattr(inputs, fld.name, attr.NOTHING)
451451
if input_value is not attr.NOTHING:
452452
if TypeParser.contains_type(FileSet, fld.type):
453-
input_value = TypeParser(fld.type).coerce(input_value)
453+
label = f"output field '{fld.name}' of {self}"
454+
input_value = TypeParser(fld.type, label=label).coerce(input_value)
454455
additional_out[fld.name] = input_value
455456
elif (
456457
fld.default is None or fld.default == attr.NOTHING

pydra/utils/typing.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,15 @@ class TypeParser(ty.Generic[T]):
5656
the tree of more complex nested container types. Overrides 'coercible' to enable
5757
you to carve out exceptions, such as TypeParser(list, coercible=[(ty.Iterable, list)],
5858
not_coercible=[(str, list)])
59+
label : str
60+
the label to be used to identify the type parser in error messages. Especially
61+
useful when TypeParser is used as a converter in attrs.fields
5962
"""
6063

6164
tp: ty.Type[T]
6265
coercible: ty.List[ty.Tuple[TypeOrAny, TypeOrAny]]
6366
not_coercible: ty.List[ty.Tuple[TypeOrAny, TypeOrAny]]
67+
label: str
6468

6569
COERCIBLE_DEFAULT: ty.Tuple[ty.Tuple[type, type], ...] = (
6670
(
@@ -103,6 +107,7 @@ def __init__(
103107
not_coercible: ty.Optional[
104108
ty.Iterable[ty.Tuple[TypeOrAny, TypeOrAny]]
105109
] = NOT_COERCIBLE_DEFAULT,
110+
label: str = "",
106111
):
107112
def expand_pattern(t):
108113
"""Recursively expand the type arguments of the target type in nested tuples"""
@@ -118,10 +123,12 @@ def expand_pattern(t):
118123
return origin
119124
if origin not in (ty.Union, type) and not issubclass(origin, ty.Iterable):
120125
raise TypeError(
121-
f"TypeParser doesn't know how to handle args ({args}) for {origin} types"
126+
f"TypeParser doesn't know how to handle args ({args}) for {origin} "
127+
f"types{self.label_str}"
122128
)
123129
return (origin, [expand_pattern(a) for a in args])
124130

131+
self.label = label
125132
self.tp = tp
126133
self.coercible = (
127134
list(coercible) if coercible is not None else [(ty.Any, ty.Any)]
@@ -194,7 +201,7 @@ def expand_and_coerce(obj, pattern: ty.Union[type, tuple]):
194201
else ""
195202
)
196203
raise TypeError(
197-
f"Could not coerce to {type_} as {obj!r} is not iterable{msg}"
204+
f"Could not coerce to {type_} as {obj!r} is not iterable{msg}{self.label_str}"
198205
) from e
199206
if issubclass(origin, tuple):
200207
return coerce_tuple(type_, obj_args, pattern_args)
@@ -221,7 +228,8 @@ def coerce_union(obj, pattern_args):
221228
except TypeError as e:
222229
reasons.append(e)
223230
raise TypeError(
224-
f"Could not coerce object, {obj!r}, to any of the union types {pattern_args}:\n\n"
231+
f"Could not coerce object, {obj!r}, to any of the union types "
232+
f"{pattern_args}{self.label_str}:\n\n"
225233
+ "\n\n".join(f"{a} -> {e}" for a, e in zip(pattern_args, reasons))
226234
)
227235

@@ -240,7 +248,7 @@ def coerce_mapping(
240248
else ""
241249
)
242250
raise TypeError(
243-
f"Could not coerce to {type_} as {obj} is not a mapping type{msg}"
251+
f"Could not coerce to {type_} as {obj} is not a mapping type{msg}{self.label_str}"
244252
) from e
245253
return coerce_obj(
246254
{
@@ -263,7 +271,7 @@ def coerce_tuple(
263271
elif len(pattern_args) != len(obj_args):
264272
raise TypeError(
265273
f"Incorrect number of items in tuple, expected "
266-
f"{len(pattern_args)}, got {len(obj_args)}"
274+
f"{len(pattern_args)}, got {len(obj_args)}{self.label_str}"
267275
)
268276
return coerce_obj(
269277
[expand_and_coerce(o, p) for o, p in zip(obj_args, pattern_args)], type_
@@ -281,7 +289,7 @@ def coerce_sequence(
281289
def coerce_type(type_: ty.Type[ty.Any], pattern_args: ty.List[ty.Type[ty.Any]]):
282290
if not any(issubclass(type_, t) for t in pattern_args):
283291
raise TypeError(
284-
f"{type_} is not one of the specified types {pattern_args}"
292+
f"{type_} is not one of the specified types {pattern_args}{self.label_str}"
285293
)
286294
return type_
287295

@@ -297,7 +305,9 @@ def coerce_obj(obj, type_):
297305
if obj is not object_
298306
else ""
299307
)
300-
raise TypeError(f"Cannot coerce {obj!r} into {type_}{msg}") from e
308+
raise TypeError(
309+
f"Cannot coerce {obj!r} into {type_}{msg}{self.label_str}"
310+
) from e
301311

302312
return expand_and_coerce(object_, self.pattern)
303313

@@ -323,7 +333,7 @@ def check_type(self, type_: ty.Type[ty.Any]):
323333
raise TypeError("Splits without any type arguments are invalid")
324334
if len(args) > 1:
325335
raise TypeError(
326-
f"Splits with more than one type argument ({args}) are invalid"
336+
f"Splits with more than one type argument ({args}) are invalid{self.label_str}"
327337
)
328338
return self.check_type(args[0])
329339

@@ -343,7 +353,7 @@ def expand_and_check(tp, pattern: ty.Union[type, tuple]):
343353
)
344354
raise TypeError(
345355
f"{tp} doesn't match pattern {pattern}, when matching {type_} to "
346-
f"{self.pattern}"
356+
f"{self.pattern}{self.label_str}"
347357
)
348358
tp_args = get_args(tp)
349359
self.check_coercible(tp_origin, pattern_origin)
@@ -378,7 +388,7 @@ def check_union(tp, pattern_args):
378388
if reasons:
379389
raise TypeError(
380390
f"Cannot coerce {tp} to "
381-
f"ty.Union[{', '.join(str(a) for a in pattern_args)}], "
391+
f"ty.Union[{', '.join(str(a) for a in pattern_args)}]{self.label_str}, "
382392
f"because {tp_arg} cannot be coerced to any of its args:\n\n"
383393
+ "\n\n".join(
384394
f"{a} -> {e}" for a, e in zip(pattern_args, reasons)
@@ -414,7 +424,7 @@ def check_tuple(tp_args, pattern_args):
414424
if len(tp_args) != len(pattern_args):
415425
raise TypeError(
416426
f"Wrong number of type arguments in tuple {tp_args} compared to pattern "
417-
f"{pattern_args} in attempting to match {type_} to {self.pattern}"
427+
f"{pattern_args} in attempting to match {type_} to {self.pattern}{self.label_str}"
418428
)
419429
for t, p in zip(tp_args, pattern_args):
420430
expand_and_check(t, p)
@@ -426,7 +436,8 @@ def check_sequence(tp_args, pattern_args):
426436
if not tp_args:
427437
raise TypeError(
428438
"Generic ellipsis type arguments not specific enough to match "
429-
f"{pattern_args} in attempting to match {type_} to {self.pattern}"
439+
f"{pattern_args} in attempting to match {type_} to "
440+
f"{self.pattern}{self.label_str}"
430441
)
431442
for arg in tp_args:
432443
expand_and_check(arg, pattern_args[0])
@@ -476,16 +487,16 @@ def type_name(t):
476487

477488
if not matches_criteria(self.coercible):
478489
raise TypeError(
479-
f"Cannot coerce {repr(source)} into {target} as the coercion doesn't match "
480-
f"any of the explicit inclusion criteria: "
490+
f"Cannot coerce {repr(source)} into {target}{self.label_str} as the "
491+
"coercion doesn't match any of the explicit inclusion criteria: "
481492
+ ", ".join(
482493
f"{type_name(s)} -> {type_name(t)}" for s, t in self.coercible
483494
)
484495
)
485496
matches_not_coercible = matches_criteria(self.not_coercible)
486497
if matches_not_coercible:
487498
raise TypeError(
488-
f"Cannot coerce {repr(source)} into {target} as it is explicitly "
499+
f"Cannot coerce {repr(source)} into {target}{self.label_str} as it is explicitly "
489500
"excluded by the following coercion criteria: "
490501
+ ", ".join(
491502
f"{type_name(s)} -> {type_name(t)}"
@@ -799,5 +810,9 @@ def strip_splits(cls, type_: ty.Type[ty.Any]) -> ty.Tuple[ty.Type, int]:
799810
depth += 1
800811
return type_, depth
801812

813+
@property
814+
def label_str(self):
815+
return f" in {self.label} " if self.label else ""
816+
802817
get_origin = staticmethod(get_origin)
803818
get_args = staticmethod(get_args)

0 commit comments

Comments
 (0)