Skip to content

Commit 0daacab

Browse files
authored
Revert "Revert "Add SQL Binding support (#124)" (#126)" (#127)
This reverts commit f8e1fbf.
1 parent cbad1ce commit 0daacab

File tree

5 files changed

+447
-0
lines changed

5 files changed

+447
-0
lines changed

azure/functions/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .meta import get_binding_registry
1818
from ._queue import QueueMessage
1919
from ._servicebus import ServiceBusMessage
20+
from ._sql import SqlRow, SqlRowList
2021

2122
# Import binding implementations to register them
2223
from . import blob # NoQA
@@ -29,6 +30,7 @@
2930
from . import servicebus # NoQA
3031
from . import timer # NoQA
3132
from . import durable_functions # NoQA
33+
from . import sql # NoQA
3234

3335

3436
__all__ = (
@@ -55,6 +57,8 @@
5557
'EntityContext',
5658
'QueueMessage',
5759
'ServiceBusMessage',
60+
'SqlRow',
61+
'SqlRowList',
5862
'TimerRequest',
5963

6064
# Middlewares

azure/functions/_abc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,32 @@ class OrchestrationContext(abc.ABC):
422422
@abc.abstractmethod
423423
def body(self) -> str:
424424
pass
425+
426+
427+
class SqlRow(abc.ABC):
428+
429+
@classmethod
430+
@abc.abstractmethod
431+
def from_json(cls, json_data: str) -> 'SqlRow':
432+
pass
433+
434+
@classmethod
435+
@abc.abstractmethod
436+
def from_dict(cls, dct: dict) -> 'SqlRow':
437+
pass
438+
439+
@abc.abstractmethod
440+
def __getitem__(self, key):
441+
pass
442+
443+
@abc.abstractmethod
444+
def __setitem__(self, key, value):
445+
pass
446+
447+
@abc.abstractmethod
448+
def to_json(self) -> str:
449+
pass
450+
451+
452+
class SqlRowList(abc.ABC):
453+
pass

azure/functions/_sql.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import collections
5+
import json
6+
7+
from . import _abc
8+
9+
10+
class SqlRow(_abc.SqlRow, collections.UserDict):
11+
"""A SQL Row.
12+
13+
SqlRow objects are ''UserDict'' subclasses and behave like dicts.
14+
"""
15+
16+
@classmethod
17+
def from_json(cls, json_data: str) -> 'SqlRow':
18+
"""Create a SqlRow from a JSON string."""
19+
return cls.from_dict(json.loads(json_data))
20+
21+
@classmethod
22+
def from_dict(cls, dct: dict) -> 'SqlRow':
23+
"""Create a SqlRow from a dict object"""
24+
return cls({k: v for k, v in dct.items()})
25+
26+
def to_json(self) -> str:
27+
"""Return the JSON representation of the SqlRow"""
28+
return json.dumps(dict(self))
29+
30+
def __getitem__(self, key):
31+
return collections.UserDict.__getitem__(self, key)
32+
33+
def __setitem__(self, key, value):
34+
return collections.UserDict.__setitem__(self, key, value)
35+
36+
def __repr__(self) -> str:
37+
return (
38+
f'<SqlRow at 0x{id(self):0x}>'
39+
)
40+
41+
42+
class SqlRowList(_abc.SqlRowList, collections.UserList):
43+
"A ''UserList'' subclass containing a list of :class:'~SqlRow' objects"
44+
pass

azure/functions/sql.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import collections.abc
5+
import json
6+
import typing
7+
8+
from azure.functions import _sql as sql
9+
10+
from . import meta
11+
12+
13+
class SqlConverter(meta.InConverter, meta.OutConverter,
14+
binding='sql'):
15+
16+
@classmethod
17+
def check_input_type_annotation(cls, pytype: type) -> bool:
18+
return issubclass(pytype, sql.SqlRowList)
19+
20+
@classmethod
21+
def check_output_type_annotation(cls, pytype: type) -> bool:
22+
return issubclass(pytype, (sql.SqlRowList, sql.SqlRow))
23+
24+
@classmethod
25+
def decode(cls,
26+
data: meta.Datum,
27+
*,
28+
trigger_metadata) -> typing.Optional[sql.SqlRowList]:
29+
if data is None or data.type is None:
30+
return None
31+
32+
data_type = data.type
33+
34+
if data_type in ['string', 'json']:
35+
body = data.value
36+
37+
elif data_type == 'bytes':
38+
body = data.value.decode('utf-8')
39+
40+
else:
41+
raise NotImplementedError(
42+
f'Unsupported payload type: {data_type}')
43+
44+
rows = json.loads(body)
45+
if not isinstance(rows, list):
46+
rows = [rows]
47+
48+
return sql.SqlRowList(
49+
(None if row is None else sql.SqlRow.from_dict(row))
50+
for row in rows)
51+
52+
@classmethod
53+
def encode(cls, obj: typing.Any, *,
54+
expected_type: typing.Optional[type]) -> meta.Datum:
55+
if isinstance(obj, sql.SqlRow):
56+
data = sql.SqlRowList([obj])
57+
58+
elif isinstance(obj, sql.SqlRowList):
59+
data = obj
60+
61+
elif isinstance(obj, collections.abc.Iterable):
62+
data = sql.SqlRowList()
63+
64+
for row in obj:
65+
if not isinstance(row, sql.SqlRow):
66+
raise NotImplementedError(
67+
f'Unsupported list type: {type(obj)}, \
68+
lists must contain SqlRow objects')
69+
else:
70+
data.append(row)
71+
72+
else:
73+
raise NotImplementedError(f'Unsupported type: {type(obj)}')
74+
75+
return meta.Datum(
76+
type='json',
77+
value=json.dumps([dict(d) for d in data])
78+
)

0 commit comments

Comments
 (0)