Skip to content

fix: allow union typing for supported bindings #239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions azure/functions/eventgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ class EventGridEventOutConverter(meta.OutConverter, binding="eventGrid"):
def check_output_type_annotation(cls, pytype: type) -> bool:
valid_types = (str, bytes, azf_eventgrid.EventGridOutputEvent,
List[azf_eventgrid.EventGridOutputEvent])
return (meta.is_iterable_type_annotation(pytype, str) or meta.
is_iterable_type_annotation(pytype,
azf_eventgrid.EventGridOutputEvent)
return (meta.is_iterable_type_annotation(pytype, str)
or meta.is_iterable_type_annotation(
pytype,
azf_eventgrid.EventGridOutputEvent)
or (isinstance(pytype, type)
and issubclass(pytype, valid_types)))
and issubclass(pytype, valid_types)))

@classmethod
def encode(cls, obj: Any, *, expected_type:
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/eventhub.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class EventHubConverter(meta.InConverter, meta.OutConverter,
def check_input_type_annotation(cls, pytype: type) -> bool:
valid_types = (_eventhub.EventHubEvent)
return (
meta.is_iterable_type_annotation(pytype, valid_types)
meta.is_supported_union_annotation(pytype, valid_types)
or meta.is_iterable_type_annotation(pytype, valid_types)
or (isinstance(pytype, type) and issubclass(pytype, valid_types))
)

Expand Down
3 changes: 2 additions & 1 deletion azure/functions/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def check_input_type_annotation(cls, pytype) -> bool:
valid_types = (KafkaEvent)

return (
meta.is_iterable_type_annotation(pytype, valid_types)
meta.is_supported_union_annotation(pytype, valid_types)
or meta.is_iterable_type_annotation(pytype, valid_types)
or (isinstance(pytype, type) and issubclass(pytype, valid_types))
)

Expand Down
24 changes: 24 additions & 0 deletions azure/functions/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import datetime
import json
import re
import sys
from typing import Dict, Optional, Union, Tuple, Mapping, Any
if sys.version_info >= (3, 9):
from typing import get_origin, get_args
else:
from ._thirdparty.typing_inspect import get_origin, get_args

from ._thirdparty import typing_inspect
from ._utils import (
Expand Down Expand Up @@ -37,6 +42,25 @@ def is_iterable_type_annotation(annotation: object, pytype: object) -> bool:
for arg in args)


def is_supported_union_annotation(annotation: object, pytype) -> bool:
"""Allows for Union annotation in function apps to be used as a type
hint, as long as the types in the Union are supported. This is
supported for bindings that allow for multiple types.
"""
origin = get_origin(annotation)
if origin is not Union:
return False

args = get_args(annotation)
for arg in args:
supported = (is_iterable_type_annotation(arg, pytype)
or (isinstance(arg, type) and issubclass(arg,
pytype)))
if not supported:
return False
return True


class Datum:
def __init__(self, value: Any, type: Optional[str]):
self.value: Any = value
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/servicebus.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ class ServiceBusMessageInConverter(meta.InConverter,
def check_input_type_annotation(cls, pytype: type) -> bool:
valid_types = (azf_sbus.ServiceBusMessage)
return (
meta.is_iterable_type_annotation(pytype, valid_types)
meta.is_supported_union_annotation(pytype, valid_types)
or meta.is_iterable_type_annotation(pytype, valid_types)
or (isinstance(pytype, type) and issubclass(pytype, valid_types))
)

Expand Down
14 changes: 13 additions & 1 deletion tests/test_eventhub.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import sys
from typing import List, Mapping
import unittest
import json
Expand All @@ -27,6 +27,18 @@ def test_eventhub_input_type(self):
self.assertFalse(check_input_type(bytes))
self.assertFalse(check_input_type(List[str]))

@unittest.skipIf(sys.version_info < (3, 10),
reason="requires Python 3.10 or above")
def test_eventhub_input_type_above_310(self):
check_input_type = (
azf_eh.EventHubConverter.check_input_type_annotation
)
self.assertTrue(check_input_type(
func.EventHubEvent | List[func.EventHubEvent]))
self.assertFalse(check_input_type(func.EventHubEvent | List[str]))
self.assertFalse(check_input_type(str | List[func.EventHubEvent]))
self.assertFalse(check_input_type(str | List[str]))

def test_eventhub_output_type(self):
check_output_type = (
azf_eh.EventHubTriggerConverter.check_output_type_annotation
Expand Down
14 changes: 14 additions & 0 deletions tests/test_kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import List
import unittest
import json
import sys

from unittest.mock import patch
import azure.functions as func
Expand Down Expand Up @@ -40,6 +41,19 @@ def test_kafka_input_type(self):
self.assertFalse(check_input_type(bytes))
self.assertFalse(check_input_type(List[str]))

@unittest.skipIf(sys.version_info < (3, 10),
reason="requires Python 3.10 or above")
def test_kafka_input_type_above_310(self):
check_input_type = (
azf_ka.KafkaConverter.check_input_type_annotation
)

self.assertTrue(check_input_type(
func.KafkaEvent | List[func.KafkaEvent]))
self.assertFalse(check_input_type(func.KafkaEvent | List[str]))
self.assertFalse(check_input_type(str | List[func.KafkaEvent]))
self.assertFalse(check_input_type(str | List[str]))

def test_kafka_output_type(self):
check_output_type = (
azf_ka.KafkaTriggerConverter.check_output_type_annotation
Expand Down
14 changes: 14 additions & 0 deletions tests/test_servicebus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Dict, List
import json
import sys
import unittest
from datetime import datetime, timedelta, date

Expand Down Expand Up @@ -231,6 +232,19 @@ class ServiceBusMessageChild(func.ServiceBusMessage):
self.assertFalse(check_input_type(str))
self.assertFalse(check_input_type(type(None)))

@unittest.skipIf(sys.version_info < (3, 10),
reason="requires Python 3.10 or above")
def test_servicebus_input_type_above_310(self):
check_input_type = (
azf_sb.ServiceBusMessageInConverter.check_input_type_annotation
)

self.assertTrue(check_input_type(
func.ServiceBusMessage | List[func.ServiceBusMessage]))
self.assertFalse(check_input_type(func.ServiceBusMessage | List[str]))
self.assertFalse(check_input_type(str | List[func.ServiceBusMessage]))
self.assertFalse(check_input_type(str | List[str]))

def test_servicebus_output_type(self):
check_output_type = (
azf_sb.ServiceBusMessageOutConverter.check_output_type_annotation
Expand Down