Skip to content
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
64 changes: 50 additions & 14 deletions azure/functions/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import abc
import datetime
import io
from ._jsonutils import json
import threading
Comment thread
hallvictoria marked this conversation as resolved.
import typing

Expand All @@ -12,6 +13,45 @@
T = typing.TypeVar('T')


class Serializable(abc.ABC):
"""Mixin that provides a uniform serialization contract for binding types.

Binding classes should override :meth:`to_dict` to return a JSON-safe
representation. A default :meth:`to_json` implementation is provided
that serializes the result of :meth:`to_dict` to a JSON string.

:meth:`to_dict` is intentionally non-abstract so that downstream
subclasses of the binding ABCs that predate this contract are not broken
at instantiation time. Calling ``to_dict()`` on a class that has not
implemented it raises :exc:`NotImplementedError` at call time.
"""

def to_dict(self) -> typing.Any:
"""Return a JSON-safe representation of this binding object.

Non-list binding types return a ``dict``; collection types such as
``DocumentList`` or ``SqlRowList`` return a ``list`` of ``dict``.
All ``bytes`` values are decoded as UTF-8 when possible, otherwise
represented as ``{"__encoding": "base64", "value": "<base64>"}``.
All ``datetime`` values are represented as ISO-8601 strings.

Subclasses that do not override this method will raise
:exc:`NotImplementedError` at call time rather than at instantiation.
"""
raise NotImplementedError(
f"{type(self).__name__} does not implement to_dict(). "
"Override this method to enable serialization."
)

def to_json(self) -> str:
"""Return the JSON string representation of this binding object.

Delegates to :meth:`to_dict`; subclasses should override
:meth:`to_dict` rather than this method.
"""
return json.dumps(self.to_dict())


class Out(abc.ABC, typing.Generic[T]):
"""An interface to set function output parameters."""

Expand Down Expand Up @@ -215,7 +255,7 @@ def get_body(self) -> bytes:
pass


class TimerRequest(abc.ABC):
class TimerRequest(Serializable):
"""Timer request object."""

@property
Expand All @@ -225,7 +265,7 @@ def past_due(self) -> bool:
pass


class InputStream(io.BufferedIOBase, abc.ABC):
class InputStream(io.BufferedIOBase, Serializable):
"""File-like object representing an input blob."""

@abc.abstractmethod
Expand Down Expand Up @@ -261,7 +301,7 @@ def uri(self) -> typing.Optional[str]:
pass


class QueueMessage(abc.ABC):
class QueueMessage(Serializable):

@property
@abc.abstractmethod
Expand Down Expand Up @@ -302,7 +342,7 @@ def pop_receipt(self) -> typing.Optional[str]:
pass


class EventGridEvent(abc.ABC):
class EventGridEvent(Serializable):
@property
@abc.abstractmethod
def id(self) -> str:
Expand Down Expand Up @@ -338,7 +378,7 @@ def data_version(self) -> str:
pass


class EventGridOutputEvent(abc.ABC):
class EventGridOutputEvent(Serializable):
@property
@abc.abstractmethod
def id(self) -> str:
Expand Down Expand Up @@ -369,7 +409,7 @@ def data_version(self) -> str:
pass


class CloudEvent(abc.ABC):
class CloudEvent(Serializable):
"""A CloudEvents v1.0 event message."""

@property
Expand Down Expand Up @@ -417,7 +457,7 @@ def get_json(self) -> typing.Any:
pass


class Document(abc.ABC):
class Document(Serializable):

@classmethod
@abc.abstractmethod
Expand All @@ -437,20 +477,16 @@ def __getitem__(self, key):
def __setitem__(self, key, value):
pass

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

@abc.abstractmethod
def to_dict(self) -> dict:
pass


class DocumentList(abc.ABC):
class DocumentList(Serializable):
pass


class EventHubEvent(abc.ABC):
class EventHubEvent(Serializable):

@abc.abstractmethod
def get_body(self) -> bytes:
Expand Down Expand Up @@ -489,7 +525,7 @@ def body(self) -> str:
pass


class ServiceBusMessage(abc.ABC):
class ServiceBusMessage(Serializable):

@abc.abstractmethod
def get_body(self) -> typing.Union[str, bytes]:
Expand Down
12 changes: 12 additions & 0 deletions azure/functions/_blob.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import typing
from typing import Optional

from azure.functions import _abc as azf_abc
Expand Down Expand Up @@ -53,3 +54,14 @@ def read(self, size=-1) -> bytes:
Bytes read from the input stream.
"""
return self._io.read(size)

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of blob metadata fields.

Blob content is intentionally excluded; use :meth:`read` to access it.
"""
return {
'name': self._name,
'uri': self._uri,
'length': self._length,
}
5 changes: 4 additions & 1 deletion azure/functions/_cosmosdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ def __repr__(self) -> str:

class DocumentList(_abc.DocumentList, collections.UserList):
"A ``UserList`` subclass containing a list of :class:`~Document` objects"
pass

def to_dict(self):
"""Return a list of JSON-safe dicts, one per :class:`~Document`."""
return [doc.to_dict() for doc in self]
48 changes: 48 additions & 0 deletions azure/functions/_eventgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import typing

from azure.functions import _abc as azf_abc
from ._utils import _serialize_value


class EventGridEvent(azf_abc.EventGridEvent):
Expand Down Expand Up @@ -61,6 +62,21 @@ def __repr__(self) -> str:
f'at 0x{id(self):0x}>'
)

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of all EventGrid event fields.

``event_time`` is represented as an ISO-8601 string.
"""
return {
'id': self.id,
'topic': self.topic,
'subject': self.subject,
'event_type': self.event_type,
'event_time': _serialize_value(self.event_time),
'data_version': self.data_version,
'data': _serialize_value(self.get_json()),
}
Comment thread
hallvictoria marked this conversation as resolved.


class EventGridOutputEvent(azf_abc.EventGridOutputEvent):
"""An EventGrid event message."""
Expand Down Expand Up @@ -110,6 +126,20 @@ def __repr__(self) -> str:
f'at 0x{id(self):0x}>'
)

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of all EventGrid output event fields.

``event_time`` is represented as an ISO-8601 string.
"""
return {
'id': self.id,
'subject': self.subject,
'event_type': self.event_type,
'event_time': _serialize_value(self.event_time),
'data_version': self.data_version,
'data': _serialize_value(self.get_json()),
}


class CloudEvent(azf_abc.CloudEvent):
"""A CloudEvents v1.0 event message."""
Expand Down Expand Up @@ -182,3 +212,21 @@ def __repr__(self) -> str:
f'type={self.type} '
f'at 0x{id(self):0x}>'
)

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of all CloudEvent fields.

``time`` is represented as an ISO-8601 string.
"""
return {
'id': self.id,
'source': self.source,
'type': self.type,
'specversion': self.specversion,
'subject': self.subject,
'time': _serialize_value(self.time),
'datacontenttype': self.datacontenttype,
'dataschema': self.dataschema,
'data': _serialize_value(self.get_json()),
'extension_attrs': _serialize_value(self.extension_attrs),
}
Comment thread
hallvictoria marked this conversation as resolved.
18 changes: 18 additions & 0 deletions azure/functions/_eventhub.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from azure.functions import _abc as func_abc
from azure.functions import meta
from ._utils import _serialize_value


class EventHubEvent(func_abc.EventHubEvent):
Expand Down Expand Up @@ -80,6 +81,23 @@ def metadata(self) -> typing.Optional[typing.Mapping[str, typing.Any]]:
}
return self._trigger_metadata_pyobj

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of all Event Hub event fields.

``body`` bytes are decoded as UTF-8 when valid, otherwise represented
as ``{"__encoding": "base64", "value": "<base64>"}``.
``enqueued_time`` is an ISO-8601 string.
"""
return {
'body': _serialize_value(self.get_body()),
'partition_key': self.partition_key,
'sequence_number': self.sequence_number,
'offset': self.offset,
'enqueued_time': _serialize_value(self.enqueued_time),
'iothub_metadata': (dict(self.iothub_metadata)
if self.iothub_metadata else None),
}

def __repr__(self) -> str:
return (
f'<azure.EventHubEvent '
Expand Down
4 changes: 3 additions & 1 deletion azure/functions/_kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import abc
import typing

from . import _abc

class AbstractKafkaEvent(abc.ABC):

class AbstractKafkaEvent(_abc.Serializable):

@abc.abstractmethod
def get_body(self) -> bytes:
Expand Down
16 changes: 14 additions & 2 deletions azure/functions/_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import collections

from ._jsonutils import json
from ._utils import _serialize_value


class BaseMySqlRow(abc.ABC):
Expand Down Expand Up @@ -53,7 +54,11 @@ def from_dict(cls, dct: dict) -> 'BaseMySqlRow':

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

def to_dict(self) -> dict:
"""Return the MySqlRow as a plain dict."""
return _serialize_value(dict(self))

def __getitem__(self, key):
return collections.UserDict.__getitem__(self, key)
Expand All @@ -69,4 +74,11 @@ def __repr__(self) -> str:

class MySqlRowList(BaseMySqlRowList, collections.UserList):
"A ''UserList'' subclass containing a list of :class:'~MySqlRow' objects"
pass

def to_dict(self):
"""Return a list of plain dicts, one per :class:`~MySqlRow`."""
return [row.to_dict() for row in self]

def to_json(self) -> str:
"""Return the JSON representation of the MySqlRowList."""
return json.dumps(self.to_dict())
18 changes: 18 additions & 0 deletions azure/functions/_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from . import _abc
from ._jsonutils import json
from ._utils import _serialize_value


class QueueMessage(_abc.QueueMessage):
Expand Down Expand Up @@ -88,6 +89,23 @@ def get_json(self) -> typing.Any:
"""
return json.loads(self.__body)

def to_dict(self) -> typing.Dict[str, typing.Any]:
"""Return a JSON-safe dictionary of all message fields.

``body`` bytes are decoded as UTF-8 when valid, otherwise represented
as ``{"__encoding": "base64", "value": "<base64>"}``.
Datetime fields are ISO-8601 strings.
"""
return {
'id': self.id,
'body': _serialize_value(self.get_body()),
'pop_receipt': self.pop_receipt,
'dequeue_count': self.dequeue_count,
'expiration_time': _serialize_value(self.expiration_time),
'insertion_time': _serialize_value(self.insertion_time),
'time_next_visible': _serialize_value(self.time_next_visible),
}

def __repr__(self) -> str:
return (
f'<azure.QueueMessage id={self.id} at 0x{id(self):0x}>'
Expand Down
Loading
Loading