Skip to content

Commit b31ede8

Browse files
committed
Add support for raw data type bindings
The generic binding infrastructure now allows annotating the function argument as `str` or `bytes`. This also adds support for `str` and `bytes` binding for the blob functions as an example. The binding data adapter is responsible for checking the declared `dataType` in function.json against the declared parameter type annotation. All existing bindings, except blob, currently refuse to bind if `dataType` is specified. Issue: #66.
1 parent 31272c0 commit b31ede8

File tree

17 files changed

+179
-32
lines changed

17 files changed

+179
-32
lines changed

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ trigger:
55
- master
66

77
variables:
8-
DOTNET_VERSION: '2.1.402'
8+
DOTNET_VERSION: '2.2.103'
99

1010
jobs:
1111
- job: Tests

azure/functions_worker/bindings/blob.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ class BlobConverter(meta.InConverter,
4747
binding='blob'):
4848

4949
@classmethod
50-
def check_input_type_annotation(cls, pytype: type) -> bool:
51-
return issubclass(pytype, azf_abc.InputStream)
50+
def check_input_type_annotation(
51+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
52+
if (datatype is protos.BindingInfo.undefined
53+
or datatype is protos.BindingInfo.stream):
54+
return issubclass(pytype, azf_abc.InputStream)
55+
elif (datatype is protos.BindingInfo.binary):
56+
return issubclass(pytype, bytes)
57+
elif (datatype is protos.BindingInfo.string):
58+
return issubclass(pytype, str)
59+
else: # Unknown datatype
60+
return False
5261

5362
@classmethod
5463
def check_output_type_annotation(cls, pytype: type) -> bool:
@@ -77,12 +86,41 @@ def from_proto(cls, data: protos.TypedData, *,
7786
pytype: typing.Optional[type],
7887
trigger_metadata) -> typing.Any:
7988
data_type = data.WhichOneof('data')
89+
90+
if pytype is str:
91+
# Bound as dataType: string
92+
if data_type == 'string':
93+
return data.string
94+
else:
95+
raise ValueError(
96+
f'unexpected type of data received for the "blob" binding '
97+
f'declared to receive a string: {data_type!r}'
98+
)
99+
100+
return data.string
101+
102+
elif pytype is bytes:
103+
if data_type == 'bytes':
104+
return data.bytes
105+
elif data_type == 'string':
106+
# This should not happen with the correct dataType spec,
107+
# but we can be forgiving in this case.
108+
return data.string.encode('utf-8')
109+
else:
110+
raise ValueError(
111+
f'unexpected type of data received for the "blob" binding '
112+
f'declared to receive bytes: {data_type!r}'
113+
)
114+
80115
if data_type == 'string':
81116
data = data.string.encode('utf-8')
82117
elif data_type == 'bytes':
83118
data = data.bytes
84119
else:
85-
raise NotImplementedError
120+
raise ValueError(
121+
f'unexpected type of data received for the "blob" binding '
122+
f': {data_type!r}'
123+
)
86124

87125
if trigger_metadata is None:
88126
return InputStream(data=data)

azure/functions_worker/bindings/cosmosdb.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ class CosmosDBConverter(meta.InConverter, meta.OutConverter,
1212
binding='cosmosDB'):
1313

1414
@classmethod
15-
def check_input_type_annotation(cls, pytype: type) -> bool:
16-
return issubclass(pytype, cdb.DocumentList)
15+
def check_input_type_annotation(
16+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
17+
if datatype is protos.BindingInfo.undefined:
18+
return issubclass(pytype, cdb.DocumentList)
19+
else:
20+
return False
1721

1822
@classmethod
1923
def check_output_type_annotation(cls, pytype: type) -> bool:

azure/functions_worker/bindings/eventgrid.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ class EventGridEventInConverter(meta.InConverter,
1111
binding='eventGridTrigger', trigger=True):
1212

1313
@classmethod
14-
def check_input_type_annotation(cls, pytype: type) -> bool:
15-
return issubclass(pytype, _eventgrid.EventGridEvent)
14+
def check_input_type_annotation(
15+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
16+
if datatype is protos.BindingInfo.undefined:
17+
return issubclass(pytype, _eventgrid.EventGridEvent)
18+
else:
19+
return False
1620

1721
@classmethod
1822
def from_proto(cls, data: protos.TypedData, *,

azure/functions_worker/bindings/eventhub.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ class EventHubConverter(meta.InConverter, meta.OutConverter,
1111
binding='eventHub'):
1212

1313
@classmethod
14-
def check_input_type_annotation(cls, pytype: type) -> bool:
15-
return issubclass(pytype, _eventhub.EventHubEvent)
14+
def check_input_type_annotation(
15+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
16+
if datatype is protos.BindingInfo.undefined:
17+
return issubclass(pytype, _eventhub.EventHubEvent)
18+
else:
19+
return False
1620

1721
@classmethod
1822
def check_output_type_annotation(cls, pytype) -> bool:

azure/functions_worker/bindings/http.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,12 @@ class HttpRequestConverter(meta.InConverter,
101101
binding='httpTrigger', trigger=True):
102102

103103
@classmethod
104-
def check_input_type_annotation(cls, pytype: type) -> bool:
105-
return issubclass(pytype, azf_abc.HttpRequest)
104+
def check_input_type_annotation(
105+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
106+
if datatype is protos.BindingInfo.undefined:
107+
return issubclass(pytype, azf_abc.HttpRequest)
108+
else:
109+
return False
106110

107111
@classmethod
108112
def from_proto(cls, data: protos.TypedData, *,

azure/functions_worker/bindings/meta.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ def _parse_timedelta(
202202
class InConverter(_BaseConverter, binding=None):
203203

204204
@abc.abstractclassmethod
205-
def check_input_type_annotation(cls, pytype: type) -> bool:
205+
def check_input_type_annotation(
206+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
206207
pass
207208

208209
@abc.abstractclassmethod
@@ -257,15 +258,16 @@ def is_trigger_binding(bind_name: str) -> bool:
257258
raise ValueError(f'unsupported binding type {bind_name!r}')
258259

259260

260-
def check_input_type_annotation(binding: str, pytype: type) -> bool:
261+
def check_input_type_annotation(binding: str, pytype: type,
262+
datatype: protos.BindingInfo.DataType) -> bool:
261263
try:
262264
checker = _ConverterMeta._check_in_typeann[binding]
263265
except KeyError:
264266
raise TypeError(
265267
f'{binding!r} input binding does not have '
266268
f'a corresponding Python type') from None
267269

268-
return checker(pytype)
270+
return checker(pytype, datatype)
269271

270272

271273
def check_output_type_annotation(binding: str, pytype: type) -> bool:

azure/functions_worker/bindings/queue.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ class QueueMessageInConverter(meta.InConverter,
5656
binding='queueTrigger', trigger=True):
5757

5858
@classmethod
59-
def check_input_type_annotation(cls, pytype: type) -> bool:
60-
return issubclass(pytype, azf_abc.QueueMessage)
59+
def check_input_type_annotation(
60+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
61+
if datatype is protos.BindingInfo.undefined:
62+
return issubclass(pytype, azf_abc.QueueMessage)
63+
else:
64+
return False
6165

6266
@classmethod
6367
def from_proto(cls, data: protos.TypedData, *,

azure/functions_worker/bindings/servicebus.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ class ServiceBusMessageInConverter(meta.InConverter,
109109
binding='serviceBusTrigger', trigger=True):
110110

111111
@classmethod
112-
def check_input_type_annotation(cls, pytype: type) -> bool:
113-
return issubclass(pytype, azf_sbus.ServiceBusMessage)
112+
def check_input_type_annotation(
113+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
114+
if datatype is protos.BindingInfo.undefined:
115+
return issubclass(pytype, azf_sbus.ServiceBusMessage)
116+
else:
117+
return False
114118

115119
@classmethod
116120
def from_proto(cls, data: protos.TypedData, *,

azure/functions_worker/bindings/timer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ class TimerRequestConverter(meta.InConverter,
2121
binding='timerTrigger', trigger=True):
2222

2323
@classmethod
24-
def check_input_type_annotation(cls, pytype: type) -> bool:
25-
return issubclass(pytype, azf_abc.TimerRequest)
24+
def check_input_type_annotation(
25+
cls, pytype: type, datatype: protos.BindingInfo.DataType) -> bool:
26+
if datatype is protos.BindingInfo.undefined:
27+
return issubclass(pytype, azf_abc.TimerRequest)
28+
else:
29+
return False
2630

2731
@classmethod
2832
def from_proto(cls, data: protos.TypedData, *,

0 commit comments

Comments
 (0)