Skip to content

Commit 21590f9

Browse files
committed
chore: clean-up
doc: add docs doc: fix TypeError messages fix: remove unguarded UnionType import test: fix imports
1 parent 939d3e1 commit 21590f9

File tree

3 files changed

+46
-38
lines changed

3 files changed

+46
-38
lines changed

latch/resources/workflow.py

+40-31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import typing
44
from dataclasses import is_dataclass
55
from textwrap import dedent
6-
from types import UnionType
76
from typing import Any, Callable, Dict, Union, get_args, get_origin
87

98
import click
@@ -50,7 +49,6 @@ class UnionType:
5049
TYPE_ANNOTATION_TYPES = (type, typing._GenericAlias, UnionType) # type: ignore[attr-defined]
5150

5251

53-
5452
def _generate_metadata(f: Callable) -> LatchMetadata:
5553
signature = inspect.signature(f)
5654
metadata = LatchMetadata(f.__name__, LatchAuthor())
@@ -72,7 +70,7 @@ def _inject_metadata(f: Callable, metadata: LatchMetadata) -> None:
7270
# so that when users call @workflow without any arguments or
7371
# parentheses, the workflow still serializes as expected
7472
def workflow(
75-
metadata: Union[LatchMetadata, Callable]
73+
metadata: Union[LatchMetadata, Callable],
7674
) -> Union[PythonFunctionWorkflow, Callable]:
7775
if isinstance(metadata, Callable):
7876
f = metadata
@@ -141,25 +139,33 @@ def decorator(f: Callable):
141139

142140

143141
def _is_valid_samplesheet_parameter_type(parameter: inspect.Parameter) -> bool:
144-
"""
145-
Check if a parameter in the workflow function's signature is annotated with a valid type for a
146-
samplesheet LatchParameter.
142+
"""Check if a workflow parameter is hinted with a valid type for a samplesheet LatchParameter.
143+
144+
Currently, a samplesheet LatchParameter must be defined as a list of dataclasses, or as an
145+
`Optional` list of dataclasses when the parameter is part of a `ForkBranch`.
146+
147+
Args:
148+
parameter: A parameter from the workflow function's signature.
149+
150+
Returns:
151+
True if the parameter is annotated as a list of dataclasses, or as an `Optional` list of
152+
dataclasses.
153+
False otherwise.
147154
"""
148155
annotation = parameter.annotation
149156

150157
# If the parameter did not have a type annotation, short-circuit and return False
151158
if not _is_type_annotation(annotation):
152159
return False
153160

154-
return (
155-
_is_list_of_dataclasses_type(annotation)
156-
or (_is_optional_type(annotation) and _is_list_of_dataclasses_type(_unpack_optional_type(annotation)))
161+
return _is_list_of_dataclasses_type(annotation) or (
162+
_is_optional_type(annotation)
163+
and _is_list_of_dataclasses_type(_unpack_optional_type(annotation))
157164
)
158165

159166

160167
def _is_list_of_dataclasses_type(dtype: TypeAnnotation) -> bool:
161-
"""
162-
Check if the type is a list of dataclasses.
168+
"""Check if the type is a list of dataclasses.
163169
164170
Args:
165171
dtype: A type.
@@ -169,10 +175,10 @@ def _is_list_of_dataclasses_type(dtype: TypeAnnotation) -> bool:
169175
False otherwise.
170176
171177
Raises:
172-
TypeError: If the input is not a `type`.
178+
TypeError: If the input is not a valid `TypeAnnotation` type (see above).
173179
"""
174180
if not isinstance(dtype, TYPE_ANNOTATION_TYPES):
175-
raise TypeError(f"Expected `type`, got {type(dtype)}: {dtype}")
181+
raise TypeError(f"Expected type annotation, got {type(dtype)}: {dtype}")
176182

177183
origin = get_origin(dtype)
178184
args = get_args(dtype)
@@ -187,8 +193,7 @@ def _is_list_of_dataclasses_type(dtype: TypeAnnotation) -> bool:
187193

188194

189195
def _is_optional_type(dtype: TypeAnnotation) -> bool:
190-
"""
191-
Check if a type is `Optional`.
196+
"""Check if a type is `Optional`.
192197
193198
An optional type may be declared using three syntaxes: `Optional[T]`, `Union[T, None]`, or `T |
194199
None`. All of these syntaxes is supported by this function.
@@ -201,22 +206,25 @@ def _is_optional_type(dtype: TypeAnnotation) -> bool:
201206
False otherwise.
202207
203208
Raises:
204-
TypeError: If the input is not a `type`.
209+
TypeError: If the input is not a valid `TypeAnnotation` type (see above).
205210
"""
206211
if not isinstance(dtype, TYPE_ANNOTATION_TYPES):
207-
raise TypeError(f"Expected `type`, got {type(dtype)}: {dtype}")
212+
raise TypeError(f"Expected type annotation, got {type(dtype)}: {dtype}")
208213

209214
origin = get_origin(dtype)
210215
args = get_args(dtype)
211216

212217
# Optional[T] has `typing.Union` as its origin, but PEP604 syntax (e.g. `int | None`) has
213218
# `types.UnionType` as its origin.
214-
return (origin is Union or origin is UnionType) and len(args) == 2 and type(None) in args
219+
return (
220+
(origin is Union or origin is UnionType)
221+
and len(args) == 2
222+
and type(None) in args
223+
)
215224

216225

217226
def _unpack_optional_type(dtype: TypeAnnotation) -> type:
218-
"""
219-
Given a type of `Optional[T]`, return `T`.
227+
"""Given a type of `Optional[T]`, return `T`.
220228
221229
Args:
222230
dtype: A type of `Optional[T]`, `T | None`, or `Union[T, None]`.
@@ -225,14 +233,14 @@ def _unpack_optional_type(dtype: TypeAnnotation) -> type:
225233
The type `T`.
226234
227235
Raises:
228-
TypeError: If the input is not a `type`.
236+
TypeError: If the input is not a valid `TypeAnnotation` type (see above).
229237
ValueError: If the input type is not `Optional[T]`.
230238
"""
231239
if not isinstance(dtype, TYPE_ANNOTATION_TYPES):
232-
raise TypeError(f"Expected `type`, got {type(dtype)}: {dtype}")
240+
raise TypeError(f"Expected type annotation, got {type(dtype)}: {dtype}")
233241

234242
if not _is_optional_type(dtype):
235-
raise ValueError(f"Expected Optional[T], got {type(dtype)}: {dtype}")
243+
raise ValueError(f"Expected `Optional[T]`, got {type(dtype)}: {dtype}")
236244

237245
args = get_args(dtype)
238246

@@ -245,26 +253,27 @@ def _unpack_optional_type(dtype: TypeAnnotation) -> type:
245253
return base_type
246254

247255

256+
# NB: `inspect.Parameter.annotation` is typed as `Any`, so here we narrow the type.
248257
def _is_type_annotation(annotation: Any) -> TypeGuard[TypeAnnotation]:
249-
"""
250-
Check if the annotation on an `inspect.Parameter` instance is a type annotation.
258+
"""Check if the annotation on an `inspect.Parameter` instance is a type annotation.
251259
252260
If the corresponding parameter **did not** have a type annotation, `annotation` is set to the
253-
special class variable `Parameter.empty`.
254-
255-
NB: `Parameter.empty` itself is a subclass of `type`
256-
Otherwise, the annotation is assumed to be a type.
261+
special class variable `inspect.Parameter.empty`. Otherwise, the annotation should be a valid
262+
type annotation.
257263
258264
Args:
259265
annotation: The annotation on an `inspect.Parameter` instance.
260266
261267
Returns:
262-
True if the annotation is not `Parameter.empty`.
268+
True if the type annotation is not `inspect.Parameter.empty`.
263269
False otherwise.
264270
265271
Raises:
266-
TypeError: If the annotation is neither a type nor `Parameter.empty`.
272+
TypeError: If the annotation is neither a valid `TypeAnnotation` type (see above) nor
273+
`inspect.Parameter.empty`.
267274
"""
275+
# NB: `inspect.Parameter.empty` is a subclass of `type`, so this check passes for unannotated
276+
# parameters.
268277
if not isinstance(annotation, TYPE_ANNOTATION_TYPES):
269278
raise TypeError(f"Annotation must be a type, not {type(annotation).__name__}")
270279

tests/resources/__init__.py

Whitespace-only changes.

tests/resources/test_workflow.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import inspect
22
import sys
3-
import typing
43
from dataclasses import dataclass
54
from typing import List
65
from typing import Any
76
from typing import Collection, Iterable, Optional, Union, Mapping, Dict, Set, Tuple
87

98
import pytest
109

11-
from anno import _is_list_of_dataclasses_type
12-
from anno import _is_valid_samplesheet_parameter_type
13-
from anno import _is_optional_type
14-
from anno import _is_type_annotation
15-
from anno import _unpack_optional_type
16-
from anno import TypeAnnotation
10+
from latch.resources.workflow import _is_list_of_dataclasses_type
11+
from latch.resources.workflow import _is_valid_samplesheet_parameter_type
12+
from latch.resources.workflow import _is_optional_type
13+
from latch.resources.workflow import _is_type_annotation
14+
from latch.resources.workflow import _unpack_optional_type
15+
from latch.resources.workflow import TypeAnnotation
1716

1817
PRIMITIVE_TYPES = [int, float, bool, str]
1918
COLLECTION_TYPES = [List[int], Dict[str, int], Set[int], Tuple[int], Mapping[str, int], Iterable[int], Collection[int]]

0 commit comments

Comments
 (0)