Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cloud event to core #16800

Merged
merged 54 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
abe2389
Add cloud event to core
rakshith91 Feb 18, 2021
9ed935d
extensions
rakshith91 Feb 18, 2021
d11e02f
raise on both
rakshith91 Feb 18, 2021
ec3474c
minor
rakshith91 Feb 18, 2021
e658ce7
more changes
rakshith91 Feb 18, 2021
50de6e6
Update sdk/core/azure-core/azure/core/messaging.py
Feb 18, 2021
9f3624b
comments
rakshith91 Feb 18, 2021
04acd47
changes
rakshith91 Feb 19, 2021
f18f35d
test fix
rakshith91 Feb 19, 2021
9400e0e
test
rakshith91 Feb 19, 2021
70e08c0
comments
rakshith91 Feb 21, 2021
b69cd73
lint
rakshith91 Feb 21, 2021
89c80b5
mypy
rakshith91 Feb 21, 2021
10d81c3
type hint
rakshith91 Feb 22, 2021
a121adc
Apply suggestions from code review
Feb 22, 2021
428e35c
serialize date
rakshith91 Feb 24, 2021
c88c1e9
fix
rakshith91 Feb 24, 2021
f638961
fix
rakshith91 Feb 24, 2021
13335c1
fix
rakshith91 Feb 24, 2021
2dec996
Docstring
lmazuel Feb 24, 2021
c3368d5
change util
rakshith91 Feb 24, 2021
f461890
lint
rakshith91 Feb 24, 2021
248db3d
apply black
rakshith91 Feb 24, 2021
32b2532
utilize tz utc
rakshith91 Feb 25, 2021
2441222
comments
rakshith91 Feb 25, 2021
666bbd7
raise on unexpected kwargs
rakshith91 Feb 25, 2021
45d2a90
doc
rakshith91 Feb 25, 2021
8c1f9fc
lint
rakshith91 Feb 25, 2021
8dcf54c
more lint
rakshith91 Feb 25, 2021
f0d718f
attrs are optional
rakshith91 Feb 26, 2021
950ffed
add sentinel
rakshith91 Feb 26, 2021
c15a73a
falsy object
rakshith91 Feb 28, 2021
a756fe3
few more asserts
rakshith91 Feb 28, 2021
6b4a31f
lint
rakshith91 Feb 28, 2021
9371f77
pyt2 compat
rakshith91 Feb 28, 2021
78696ef
tests
rakshith91 Mar 1, 2021
79e74d4
comments
rakshith91 Mar 1, 2021
a9c05f4
update toc tree
rakshith91 Mar 1, 2021
57b1bd0
doc
rakshith91 Mar 1, 2021
e8b53b0
doc
rakshith91 Mar 1, 2021
3a16ca9
doc
rakshith91 Mar 1, 2021
37e887c
unconditional
rakshith91 Mar 1, 2021
8e196ef
test fix
rakshith91 Mar 1, 2021
b4c726e
mypy
rakshith91 Mar 1, 2021
3f49138
wrong import
rakshith91 Mar 1, 2021
22db8b8
type annotations
rakshith91 Mar 2, 2021
6e9ce14
data
rakshith91 Mar 2, 2021
43a79c1
coment
rakshith91 Mar 2, 2021
ba654a0
assets
rakshith91 Mar 2, 2021
4f9d80e
lint
rakshith91 Mar 2, 2021
0a3aa87
unnecessary none
rakshith91 Mar 3, 2021
5051034
format
rakshith91 Mar 3, 2021
c7afd6e
cast to str
rakshith91 Mar 3, 2021
9613fd4
remove cast
rakshith91 Mar 3, 2021
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
6 changes: 5 additions & 1 deletion sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Release History

## 1.11.1 (Unreleased)
## 1.12.0 (Unreleased)

### Features

- Added `azure.core.messaging.CloudEvent` model that follows the cloud event spec.
- Added `azure.core.serialization.NULL` sentinel value

## 1.11.0 (2021-02-08)

Expand Down
70 changes: 70 additions & 0 deletions sdk/core/azure-core/azure/core/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import datetime


class _FixedOffset(datetime.tzinfo):
lmazuel marked this conversation as resolved.
Show resolved Hide resolved
"""Fixed offset in minutes east from UTC.

Copy/pasted from Python doc

:param int offset: offset in minutes
"""

def __init__(self, offset):
self.__offset = datetime.timedelta(minutes=offset)

def utcoffset(self, dt):
return self.__offset

def tzname(self, dt):
return str(self.__offset.total_seconds() / 3600)

def __repr__(self):
return "<FixedOffset {}>".format(self.tzname(None))

def dst(self, dt):
return datetime.timedelta(0)


try:
from datetime import timezone

TZ_UTC = timezone.utc # type: ignore
except ImportError:
TZ_UTC = _FixedOffset(0) # type: ignore


def _convert_to_isoformat(date_time):
lmazuel marked this conversation as resolved.
Show resolved Hide resolved
"""Deserialize a date in RFC 3339 format to datetime object.
Check https://tools.ietf.org/html/rfc3339#section-5.8 for examples.
"""
if not date_time:
return None
if date_time[-1] == "Z":
delta = 0
timestamp = date_time[:-1]
else:
timestamp = date_time[:-6]
sign, offset = date_time[-6], date_time[-5:]
delta = int(sign + offset[:1]) * 60 + int(sign + offset[-2:])

if delta == 0:
tzinfo = TZ_UTC
else:
try:
tzinfo = datetime.timezone(datetime.timedelta(minutes=delta))
except AttributeError:
tzinfo = _FixedOffset(delta)

try:
deserialized = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f")
except ValueError:
deserialized = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S")

deserialized = deserialized.replace(tzinfo=tzinfo)
return deserialized
2 changes: 1 addition & 1 deletion sdk/core/azure-core/azure/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
# regenerated.
# --------------------------------------------------------------------------

VERSION = "1.11.1"
VERSION = "1.12.0"
168 changes: 168 additions & 0 deletions sdk/core/azure-core/azure/core/messaging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import uuid
from base64 import b64decode
from datetime import datetime
from azure.core._utils import _convert_to_isoformat, TZ_UTC
from azure.core.serialization import NULL

try:
from typing import TYPE_CHECKING, cast, Union
except ImportError:
TYPE_CHECKING = False

if TYPE_CHECKING:
from typing import Any, Optional, Dict


__all__ = ["CloudEvent"]


class CloudEvent(object): # pylint:disable=too-many-instance-attributes
"""Properties of the CloudEvent 1.0 Schema.
All required parameters must be populated in order to send to Azure.

:param source: Required. Identifies the context in which an event happened. The combination of id and source must
be unique for each distinct event. If publishing to a domain topic, source must be the domain name.
:type source: str
:param type: Required. Type of event related to the originating occurrence.
:type type: str
:keyword data: Optional. Event data specific to the event type.
:type data: object
:keyword time: Optional. The time (in UTC) the event was generated.
:type time: ~datetime.datetime
:keyword dataschema: Optional. Identifies the schema that data adheres to.
:type dataschema: str
:keyword datacontenttype: Optional. Content type of data value.
:type datacontenttype: str
:keyword subject: Optional. This describes the subject of the event in the context of the event producer
(identified by source).
:type subject: str
:keyword specversion: Optional. The version of the CloudEvent spec. Defaults to "1.0"
:type specversion: str
:keyword id: Optional. An identifier for the event. The combination of id and source must be
unique for each distinct event. If not provided, a random UUID will be generated and used.
:type id: Optional[str]
:keyword extensions: Optional. A CloudEvent MAY include any number of additional context attributes
with distinct names represented as key - value pairs. Each extension must be alphanumeric, lower cased
and must not exceed the length of 20 characters.
:type extensions: Optional[Dict]
:ivar source: Identifies the context in which an event happened. The combination of id and source must
be unique for each distinct event. If publishing to a domain topic, source must be the domain name.
:vartype source: str
:ivar data: Event data specific to the event type.
:vartype data: object
:ivar type: Type of event related to the originating occurrence.
:vartype type: str
:ivar time: The time (in UTC) the event was generated.
:vartype time: ~datetime.datetime
:ivar dataschema: Identifies the schema that data adheres to.
:vartype dataschema: str
:ivar datacontenttype: Content type of data value.
:vartype datacontenttype: str
:ivar subject: This describes the subject of the event in the context of the event producer
(identified by source).
:vartype subject: str
:ivar specversion: Optional. The version of the CloudEvent spec. Defaults to "1.0"
:vartype specversion: str
:ivar id: An identifier for the event. The combination of id and source must be
unique for each distinct event. If not provided, a random UUID will be generated and used.
:vartype id: str
:ivar extensions: A CloudEvent MAY include any number of additional context attributes
with distinct names represented as key - value pairs. Each extension must be alphanumeric, lower cased
and must not exceed the length of 20 characters.
:vartype extensions: Dict
"""

def __init__(self, source, type, **kwargs): # pylint: disable=redefined-builtin
# type: (str, str, **Any) -> None
self.source = source # type: str
self.type = type # type: str
self.specversion = kwargs.pop("specversion", "1.0") # type: Optional[str]
self.id = kwargs.pop("id", str(uuid.uuid4())) # type: Optional[str]
self.time = kwargs.pop("time", datetime.now(TZ_UTC)) # type: Optional[datetime]

self.datacontenttype = kwargs.pop("datacontenttype", None) # type: Optional[str]
self.dataschema = kwargs.pop("dataschema", None) # type: Optional[str]
self.subject = kwargs.pop("subject", None) # type: Optional[str]
self.data = kwargs.pop("data", None) # type: Optional[object]

try:
self.extensions = kwargs.pop("extensions") # type: Optional[Dict]
for key in self.extensions.keys(): # type:ignore # extensions won't be None here
if not key.islower() or not key.isalnum():
raise ValueError(
"Extension attributes should be lower cased and alphanumeric."
)
except KeyError:
self.extensions = None

if kwargs:
remaining = ", ".join(kwargs.keys())
raise ValueError(
"Unexpected keyword arguments {}. Any extension attributes must be passed explicitly using extensions."
.format(remaining)
)

def __repr__(self):
return "CloudEvent(source={}, type={}, specversion={}, id={}, time={})".format(
self.source, self.type, self.specversion, self.id, self.time
)[:1024]

@classmethod
def from_dict(cls, event):
# type: (Dict) -> CloudEvent
"""
Returns the deserialized CloudEvent object when a dict is provided.
:param event: The dict representation of the event which needs to be deserialized.
:type event: dict
:rtype: CloudEvent
"""
kwargs = {} # type: Dict[Any, Any]
reserved_attr = [
"data",
"data_base64",
"id",
"source",
"type",
"specversion",
"time",
"dataschema",
"datacontenttype",
"subject",
]

if "data" in event and "data_base64" in event:
raise ValueError(
"Invalid input. Only one of data and data_base64 must be present."
)

if "data" in event:
data = event.get("data")
kwargs["data"] = data if data is not None else NULL
elif "data_base64" in event:
kwargs["data"] = b64decode(
cast(Union[str, bytes], event.get("data_base64"))
)

for item in ["datacontenttype", "dataschema", "subject"]:
if item in event:
val = event.get(item)
kwargs[item] = val if val is not None else NULL

extensions = {k: v for k, v in event.items() if k not in reserved_attr}
if extensions:
kwargs["extensions"] = extensions

return cls(
id=event.get("id"),
source=event["source"],
type=event["type"],
specversion=event.get("specversion"),
time=_convert_to_isoformat(event.get("time")),
**kwargs
)
24 changes: 1 addition & 23 deletions sdk/core/azure-core/azure/core/pipeline/policies/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,7 @@
import datetime
import email.utils
from requests.structures import CaseInsensitiveDict

class _FixedOffset(datetime.tzinfo):
"""Fixed offset in minutes east from UTC.

Copy/pasted from Python doc

:param int offset: offset in minutes
"""

def __init__(self, offset):
self.__offset = datetime.timedelta(minutes=offset)

def utcoffset(self, dt):
return self.__offset

def tzname(self, dt):
return str(self.__offset.total_seconds()/3600)

def __repr__(self):
return "<FixedOffset {}>".format(self.tzname(None))

def dst(self, dt):
return datetime.timedelta(0)
from ..._utils import _FixedOffset

def _parse_http_date(text):
"""Parse a HTTP date format into datetime."""
Expand Down
23 changes: 23 additions & 0 deletions sdk/core/azure-core/azure/core/serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------

__all__ = ["NULL"]

class _Null(object):
"""To create a Falsy object
"""
def __bool__(self):
return False

__nonzero__ = __bool__ # Python2 compatibility


NULL = _Null()
johanste marked this conversation as resolved.
Show resolved Hide resolved
"""
A falsy sentinel object which is supposed to be used to specify attributes
with no data. This gets serialized to `null` on the wire.
"""
15 changes: 15 additions & 0 deletions sdk/core/azure-core/doc/azure.core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ azure.core.exceptions
:members:
:undoc-members:

azure.core.messaging
-------------------

.. automodule:: azure.core.messaging
:members:
:undoc-members:
:inherited-members:

azure.core.paging
-----------------

Expand All @@ -57,3 +65,10 @@ azure.core.settings
:undoc-members:
:inherited-members:

azure.core.serialization
-------------------

.. automodule:: azure.core.serialization
:members:
:undoc-members:
:inherited-members:
Loading