Skip to content

Commit

Permalink
feat: Add MySql Binding Support V1 (#243)
Browse files Browse the repository at this point in the history
* mysql binding support

* fix linting

* Update __init__.py

* fix indentation

---------

Co-authored-by: wangbill <gggwang1993@gmail.com>
Co-authored-by: Gavin Aguiar <80794152+gavin-aguiar@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 26, 2024
1 parent 87cd092 commit 4307c75
Show file tree
Hide file tree
Showing 4 changed files with 446 additions and 0 deletions.
4 changes: 4 additions & 0 deletions azure/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ._queue import QueueMessage
from ._servicebus import ServiceBusMessage
from ._sql import SqlRow, SqlRowList
from ._mysql import MySqlRow, MySqlRowList

# Import binding implementations to register them
from . import blob # NoQA
Expand All @@ -37,6 +38,7 @@
from . import durable_functions # NoQA
from . import sql # NoQA
from . import warmup # NoQA
from . import mysql # NoQA


__all__ = (
Expand Down Expand Up @@ -67,6 +69,8 @@
'SqlRowList',
'TimerRequest',
'WarmUpContext',
'MySqlRow',
'MySqlRowList',

# Middlewares
'WsgiMiddleware',
Expand Down
71 changes: 71 additions & 0 deletions azure/functions/_mysql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import abc
import collections
import json


class BaseMySqlRow(abc.ABC):

@classmethod
@abc.abstractmethod
def from_json(cls, json_data: str) -> 'BaseMySqlRow':
raise NotImplementedError

@classmethod
@abc.abstractmethod
def from_dict(cls, dct: dict) -> 'BaseMySqlRow':
raise NotImplementedError

@abc.abstractmethod
def __getitem__(self, key):
raise NotImplementedError

@abc.abstractmethod
def __setitem__(self, key, value):
raise NotImplementedError

@abc.abstractmethod
def to_json(self) -> str:
raise NotImplementedError


class BaseMySqlRowList(abc.ABC):
pass


class MySqlRow(BaseMySqlRow, collections.UserDict):
"""A MySql Row.
MySqlRow objects are ''UserDict'' subclasses and behave like dicts.
"""

@classmethod
def from_json(cls, json_data: str) -> 'BaseMySqlRow':
"""Create a MySqlRow from a JSON string."""
return cls.from_dict(json.loads(json_data))

@classmethod
def from_dict(cls, dct: dict) -> 'BaseMySqlRow':
"""Create a MySqlRow from a dict object"""
return cls({k: v for k, v in dct.items()})

def to_json(self) -> str:
"""Return the JSON representation of the MySqlRow"""
return json.dumps(dict(self))

def __getitem__(self, key):
return collections.UserDict.__getitem__(self, key)

def __setitem__(self, key, value):
return collections.UserDict.__setitem__(self, key, value)

def __repr__(self) -> str:
return (
f'<MySqlRow at 0x{id(self):0x}>'
)


class MySqlRowList(BaseMySqlRowList, collections.UserList):
"A ''UserList'' subclass containing a list of :class:'~MySqlRow' objects"
pass
78 changes: 78 additions & 0 deletions azure/functions/mysql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import collections.abc
import json
import typing

from azure.functions import _mysql as mysql

from . import meta


class MySqlConverter(meta.InConverter, meta.OutConverter,
binding='mysql'):

@classmethod
def check_input_type_annotation(cls, pytype: type) -> bool:
return issubclass(pytype, mysql.BaseMySqlRowList)

@classmethod
def check_output_type_annotation(cls, pytype: type) -> bool:
return issubclass(pytype, (mysql.BaseMySqlRowList, mysql.BaseMySqlRow))

@classmethod
def decode(cls,
data: meta.Datum,
*,
trigger_metadata) -> typing.Optional[mysql.MySqlRowList]:
if data is None or data.type is None:
return None

data_type = data.type

if data_type in ['string', 'json']:
body = data.value

elif data_type == 'bytes':
body = data.value.decode('utf-8')

else:
raise NotImplementedError(
f'Unsupported payload type: {data_type}')

rows = json.loads(body)
if not isinstance(rows, list):
rows = [rows]

return mysql.MySqlRowList(
(None if row is None else mysql.MySqlRow.from_dict(row))
for row in rows)

@classmethod
def encode(cls, obj: typing.Any, *,
expected_type: typing.Optional[type]) -> meta.Datum:
if isinstance(obj, mysql.MySqlRow):
data = mysql.MySqlRowList([obj])

elif isinstance(obj, mysql.MySqlRowList):
data = obj

elif isinstance(obj, collections.abc.Iterable):
data = mysql.MySqlRowList()

for row in obj:
if not isinstance(row, mysql.MySqlRow):
raise NotImplementedError(
f'Unsupported list type: {type(obj)}, \
lists must contain MySqlRow objects')
else:
data.append(row)

else:
raise NotImplementedError(f'Unsupported type: {type(obj)}')

return meta.Datum(
type='json',
value=json.dumps([dict(d) for d in data])
)
Loading

0 comments on commit 4307c75

Please sign in to comment.