Skip to content

Commit a32c0f8

Browse files
authored
Adding event grid's output binding and updating gitignore (#56)
* Adding event grid's output binding and updating gitignore * Added EventGridOutputEvent class for output binding The need for a new class was to remove the topic being sent to the custom EventGrid event.
1 parent 81b8d3e commit a32c0f8

File tree

7 files changed

+337
-13
lines changed

7 files changed

+337
-13
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
ignore = W503,E402,E731
33
exclude =
44
.git, __pycache__, build, dist, .eggs, .github, .local,
5-
Samples, azure/functions/_thirdparty, docs/, .venv*/, .env*/, .vscode/
5+
Samples, azure/functions/_thirdparty, docs/, .venv*/, .env*/, .vscode/, venv

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ celerybeat-schedule
8686
.venv*
8787
venv/
8888
ENV/
89+
py3env/
8990

9091
# Spyder project settings
9192
.spyderproject
@@ -103,3 +104,10 @@ ENV/
103104

104105
.testconfig
105106
.pytest_cache
107+
108+
# mac osx specific files
109+
.DS_Store
110+
111+
# PyCharm related files
112+
.idea/
113+
.idea_modules/

azure/functions/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from ._abc import TimerRequest, InputStream, Context, Out # NoQA
22
from ._eventhub import EventHubEvent # NoQA
3-
from ._eventgrid import EventGridEvent # NoQA
3+
from ._eventgrid import EventGridEvent, EventGridOutputEvent # NoQA
44
from ._cosmosdb import Document, DocumentList # NoQA
55
from ._http import HttpRequest # NoQA
66
from ._http import HttpResponse # NoQA

azure/functions/_abc.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,37 @@ def data_version(self) -> str:
241241
pass
242242

243243

244+
class EventGridOutputEvent(abc.ABC):
245+
@property
246+
@abc.abstractmethod
247+
def id(self) -> str:
248+
pass
249+
250+
@abc.abstractmethod
251+
def get_json(self) -> typing.Any:
252+
pass
253+
254+
@property
255+
@abc.abstractmethod
256+
def subject(self) -> str:
257+
pass
258+
259+
@property
260+
@abc.abstractmethod
261+
def event_type(self) -> str:
262+
pass
263+
264+
@property
265+
@abc.abstractmethod
266+
def event_time(self) -> typing.Optional[datetime.datetime]:
267+
pass
268+
269+
@property
270+
@abc.abstractmethod
271+
def data_version(self) -> str:
272+
pass
273+
274+
244275
class Document(abc.ABC):
245276

246277
@classmethod

azure/functions/_eventgrid.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,52 @@ def __repr__(self) -> str:
5757
f'subject={self.subject} '
5858
f'at 0x{id(self):0x}>'
5959
)
60+
61+
62+
class EventGridOutputEvent(azf_abc.EventGridOutputEvent):
63+
"""An EventGrid event message."""
64+
65+
def __init__(self, *,
66+
id: str,
67+
data: typing.Dict[str, object],
68+
subject: str,
69+
event_type: str,
70+
event_time: typing.Optional[datetime.datetime],
71+
data_version: str) -> None:
72+
self.__id = id
73+
self.__data = data
74+
self.__subject = subject
75+
self.__event_type = event_type
76+
self.__event_time = event_time
77+
self.__data_version = data_version
78+
79+
@property
80+
def id(self) -> str:
81+
return self.__id
82+
83+
def get_json(self) -> typing.Any:
84+
return self.__data
85+
86+
@property
87+
def subject(self) -> str:
88+
return self.__subject
89+
90+
@property
91+
def event_type(self) -> str:
92+
return self.__event_type
93+
94+
@property
95+
def event_time(self) -> typing.Optional[datetime.datetime]:
96+
return self.__event_time
97+
98+
@property
99+
def data_version(self) -> str:
100+
return self.__data_version
101+
102+
def __repr__(self) -> str:
103+
return (
104+
f'<azure.EventGridEvent id={self.id} '
105+
f'event_type={self.event_type} '
106+
f'subject={self.subject} '
107+
f'at 0x{id(self):0x}>'
108+
)

azure/functions/eventgrid.py

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
1+
import collections
2+
import datetime
13
import json
2-
import typing
4+
from typing import Optional, List, Any, Dict, Union
35

4-
from azure.functions import _eventgrid
6+
from azure.functions import _eventgrid as azf_eventgrid
57

68
from . import meta
9+
from .meta import Datum
710

811

9-
class EventGridEventInConverter(meta.InConverter,
10-
binding='eventGridTrigger', trigger=True):
12+
class EventGridEventInConverter(meta.InConverter, binding='eventGridTrigger',
13+
trigger=True):
1114

1215
@classmethod
1316
def check_input_type_annotation(cls, pytype: type) -> bool:
14-
return issubclass(pytype, _eventgrid.EventGridEvent)
17+
"""
18+
Event Grid always sends an array and may send more than one event in
19+
the array. The runtime invokes function once for each array element,
20+
thus no need to parse List[EventGridEvent]
21+
"""
22+
valid_types = azf_eventgrid.EventGridEvent
23+
return isinstance(pytype, type) and issubclass(pytype, valid_types)
1524

1625
@classmethod
17-
def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any:
26+
def decode(cls, data: meta.Datum, *,
27+
trigger_metadata) -> azf_eventgrid.EventGridEvent:
1828
data_type = data.type
1929

2030
if data_type == 'json':
@@ -23,11 +33,7 @@ def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any:
2333
raise NotImplementedError(
2434
f'unsupported event grid payload type: {data_type}')
2535

26-
if trigger_metadata is None:
27-
raise NotImplementedError(
28-
f'missing trigger metadata for event grid input')
29-
30-
return _eventgrid.EventGridEvent(
36+
return azf_eventgrid.EventGridEvent(
3137
id=body.get('id'),
3238
topic=body.get('topic'),
3339
subject=body.get('subject'),
@@ -36,3 +42,70 @@ def decode(cls, data: meta.Datum, *, trigger_metadata) -> typing.Any:
3642
data=body.get('data'),
3743
data_version=body.get('dataVersion'),
3844
)
45+
46+
47+
class EventGridEventOutConverter(meta.OutConverter, binding="eventGrid"):
48+
@classmethod
49+
def check_output_type_annotation(cls, pytype: type) -> bool:
50+
valid_types = (str, bytes, azf_eventgrid.EventGridOutputEvent,
51+
List[azf_eventgrid.EventGridOutputEvent])
52+
return (meta.is_iterable_type_annotation(pytype, str) or meta.
53+
is_iterable_type_annotation(pytype,
54+
azf_eventgrid.EventGridOutputEvent)
55+
or (isinstance(pytype, type)
56+
and issubclass(pytype, valid_types)))
57+
58+
@classmethod
59+
def encode(cls, obj: Any, *, expected_type:
60+
Optional[type]) -> Optional[Datum]:
61+
if isinstance(obj, str):
62+
return meta.Datum(type='string', value=obj)
63+
64+
elif isinstance(obj, bytes):
65+
return meta.Datum(type='bytes', value=obj)
66+
67+
elif isinstance(obj, azf_eventgrid.EventGridOutputEvent):
68+
return meta.Datum(
69+
type='json',
70+
value=json.dumps({
71+
'id': obj.id,
72+
'subject': obj.subject,
73+
'dataVersion': obj.data_version,
74+
'eventType': obj.event_type,
75+
'data': obj.get_json(),
76+
'eventTime': cls._format_datetime(obj.event_time)
77+
})
78+
)
79+
80+
elif isinstance(obj, collections.abc.Iterable):
81+
msgs: List[Union[str, Dict[str, Any]]] = []
82+
for item in obj:
83+
if isinstance(item, str):
84+
msgs.append(item)
85+
elif isinstance(item, azf_eventgrid.EventGridOutputEvent):
86+
msgs.append({'id': item.id,
87+
'subject': item.subject,
88+
'dataVersion': item.data_version,
89+
'eventType': item.event_type,
90+
'data': item.get_json(),
91+
'eventTime': cls._format_datetime(
92+
item.event_time)
93+
})
94+
else:
95+
raise NotImplementedError(
96+
'invalid data type in output '
97+
'queue message list: {}'.format(type(item)))
98+
99+
return meta.Datum(
100+
type='json',
101+
value=json.dumps(msgs)
102+
)
103+
104+
raise NotImplementedError
105+
106+
@classmethod
107+
def _format_datetime(cls, dt: Optional[datetime.datetime]):
108+
if dt is None:
109+
return None
110+
else:
111+
return dt.isoformat()

0 commit comments

Comments
 (0)