claudevdm commented on code in PR #41184:
URL: https://github.com/apache/beam/pull/41184#discussion_r4539487289


##########
sdks/python/apache_beam/typehints/decorators.py:
##########
@@ -175,6 +175,35 @@ def _annotation_namespaces(fn):
   return getattr(target, '__globals__', {}), localns
 
 
+def _is_valid_type_hint(hint):
+  """Whether a resolved annotation only contains valid type hint components.
+
+  A string annotation can resolve to an object that is not a type, e.g. a
+  protobuf enum (an ``EnumTypeWrapper`` instance) used as
+  ``Mapping[str, 'schema_pb2.FeatureType']``. Beam's composite type hints
+  reject such parameters, so these annotations must not be resolved.
+  """
+  if hint is None or hint is Ellipsis:
+    return True
+  if isinstance(hint, (list, tuple)):
+    # Callable parameter lists, e.g. the [int] in Callable[[int], str].
+    return all(_is_valid_type_hint(h) for h in hint)
+  origin = get_origin(hint)
+  if origin is Literal:
+    # Literal arguments are values, not types.
+    return True
+  args = get_args(hint)
+  if origin is typing.Annotated:
+    args = args[:1]  # The remaining arguments are metadata.
+  if args:
+    return all(_is_valid_type_hint(arg) for arg in args)
+  try:

Review Comment:
   `validate_composite_type_param` rejects TypeVars, NewTypes and `type` 
aliases, so any string annotation using one now falls back to `Any`:
   
   ```python
   from __future__ import annotations
   import typing
   import apache_beam as beam
   
   T = typing.TypeVar('T')
   
   def first(xs: list[T]) -> T:
     return xs[0]
   
   p = beam.Pipeline()
   print((p | beam.Create([[1]]) | beam.Map(first)).element_type)
   # master: <class 'int'>    this PR: Any
   ```
   
   Could we validate the converted hint instead? The enum case still falls 
back, since a non-type comes back from `convert_to_beam_type` unchanged. A test 
like the repro above would be good to add too.
   
   ```suggestion
     try:
       validate_composite_type_param(
           convert_to_beam_type(hint), 'Resolved type annotation')
     except (TypeError, ValueError):
       return False
   ```
   



##########
sdks/python/apache_beam/typehints/decorators.py:
##########
@@ -186,12 +215,17 @@ def one_hint():
 
   one_hint.__annotations__ = {'value': annotation}
   try:
-    return typing.get_type_hints(
+    resolved = typing.get_type_hints(
         one_hint, globalns=globalns, localns=localns,
         include_extras=True)['value']
   except (AttributeError, NameError, SyntaxError, TypeError):
     # convert_to_beam_type maps unresolved strings and ForwardRefs to Any.
     return annotation
+  if not _is_valid_type_hint(resolved):
+    # The annotation names something that is not a type. Keep it unresolved
+    # so that convert_to_beam_type maps it to Any, as for unresolved names.
+    return annotation
+  return resolved

Review Comment:
   Not caused by this PR: a resolved annotation that Beam can't convert raises 
instead of falling back to `Any`. This ran before #40625:
   
   ```python
   from __future__ import annotations
   from collections.abc import Generator
   import apache_beam as beam
   
   def gen(x: int) -> Generator[int]:
     yield x
   
   print([1] | beam.FlatMap(gen))
   # ValueError: expecting type collections.abc.Generator[int] to have arity 3, 
had arity 1 instead
   ```
   
   Would it be worth guarding the conversion here as well?
   
   ```suggestion
     try:
       convert_to_beam_type(resolved)
     except (TypeError, ValueError):
       return annotation
     return resolved
   ```
   



##########
sdks/python/apache_beam/typehints/decorators.py:
##########
@@ -175,6 +175,35 @@ def _annotation_namespaces(fn):
   return getattr(target, '__globals__', {}), localns
 
 
+def _is_valid_type_hint(hint):
+  """Whether a resolved annotation only contains valid type hint components.
+
+  A string annotation can resolve to an object that is not a type, e.g. a
+  protobuf enum (an ``EnumTypeWrapper`` instance) used as
+  ``Mapping[str, 'schema_pb2.FeatureType']``. Beam's composite type hints
+  reject such parameters, so these annotations must not be resolved.
+  """
+  if hint is None or hint is Ellipsis:
+    return True
+  if isinstance(hint, (list, tuple)):
+    # Callable parameter lists, e.g. the [int] in Callable[[int], str].
+    return all(_is_valid_type_hint(h) for h in hint)
+  origin = get_origin(hint)
+  if origin is Literal:
+    # Literal arguments are values, not types.
+    return True
+  args = get_args(hint)
+  if origin is typing.Annotated:
+    args = args[:1]  # The remaining arguments are metadata.
+  if args:
+    return all(_is_valid_type_hint(arg) for arg in args)
+  try:

Review Comment:
   `validate_composite_type_param` rejects TypeVars, NewTypes and `type` 
aliases, so any string annotation using one now falls back to `Any`:
   
   ```python
   from __future__ import annotations
   import typing
   import apache_beam as beam
   
   T = typing.TypeVar('T')
   
   def first(xs: list[T]) -> T:
     return xs[0]
   
   p = beam.Pipeline()
   print((p | beam.Create([[1]]) | beam.Map(first)).element_type)
   # master: <class 'int'>    this PR: Any
   ```
   
   Could we validate the converted hint instead? The enum case still falls 
back, since a non-type comes back from `convert_to_beam_type` unchanged. A test 
like the repro above would be good to add too.
   
   ```suggestion
     try:
       validate_composite_type_param(
           convert_to_beam_type(hint), 'Resolved type annotation')
     except (TypeError, ValueError):
       return False
   ```
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to