Skip to content

Top level http #83

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

Merged
merged 3 commits into from
Jul 30, 2020
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Below we will provide samples on how to send cloudevents using the popular
### Binary HTTP CloudEvent

```python
from cloudevents.sdk.http import CloudEvent, to_binary_http
from cloudevents.http import CloudEvent, to_binary_http
import requests


Expand All @@ -43,7 +43,7 @@ requests.post("<some-url>", data=body, headers=headers)
### Structured HTTP CloudEvent

```python
from cloudevents.sdk.http import CloudEvent, to_structured_http
from cloudevents.http import CloudEvent, to_structured_http
import requests


Expand All @@ -70,7 +70,7 @@ The code below shows how to consume a cloudevent using the popular python web fr
```python
from flask import Flask, request

from cloudevents.sdk.http import from_http
from cloudevents.http import from_http

app = Flask(__name__)

Expand Down
23 changes: 23 additions & 0 deletions cloudevents/http/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import typing

from cloudevents.http.event import CloudEvent
from cloudevents.http.http_methods import (
from_http,
to_binary_http,
to_structured_http,
)
from cloudevents.http.json_methods import from_json, to_json
100 changes: 4 additions & 96 deletions cloudevents/sdk/http/event.py → cloudevents/http/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,13 @@
# under the License.

import datetime
import json
import typing
import uuid

from cloudevents.sdk import converters, marshaller, types
from cloudevents.sdk.converters import is_binary
from cloudevents.sdk.event import v1, v03
from cloudevents.sdk.marshaller import HTTPMarshaller
from cloudevents.http.mappings import _required_by_version


def default_marshaller(content: any):
if content is None or len(content) == 0:
return None
try:
return json.dumps(content)
except TypeError:
return content


_marshaller_by_format = {
converters.TypeStructured: lambda x: x,
converters.TypeBinary: default_marshaller,
}

_obj_by_version = {"1.0": v1.Event, "0.3": v03.Event}

_required_by_version = {
"1.0": v1.Event._ce_required_fields,
"0.3": v03.Event._ce_required_fields,
}


class EventClass:
class CloudEvent:
"""
Python-friendly cloudevent class supporting v1 events
Supports both binary and structured mode CloudEvents
Expand Down Expand Up @@ -114,71 +88,5 @@ def __len__(self):
def __contains__(self, key):
return key in self._attributes


def _to_http(
event: EventClass,
format: str = converters.TypeStructured,
data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param format: constant specifying an encoding format
:type format: str
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.MarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
if data_marshaller is None:
data_marshaller = _marshaller_by_format[format]

if event._attributes["specversion"] not in _obj_by_version:
raise ValueError(
f"Unsupported specversion: {event._attributes['specversion']}"
)

event_handler = _obj_by_version[event._attributes["specversion"]]()
for k, v in event._attributes.items():
event_handler.Set(k, v)
event_handler.data = event.data

return marshaller.NewDefaultHTTPMarshaller().ToRequest(
event_handler, format, data_marshaller=data_marshaller
)


def to_structured_http(
event: EventClass, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param event: CloudEvent to cast into http data
:type event: CloudEvent
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.MarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
return _to_http(event=event, data_marshaller=data_marshaller)


def to_binary_http(
event: EventClass, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param event: CloudEvent to cast into http data
:type event: CloudEvent
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.UnmarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
return _to_http(
event=event,
format=converters.TypeBinary,
data_marshaller=data_marshaller,
)
def __repr__(self):
return str({"attributes": self._attributes, "data": self.data})
121 changes: 121 additions & 0 deletions cloudevents/http/http_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import json
import typing

from cloudevents.http.event import CloudEvent
from cloudevents.http.mappings import _marshaller_by_format, _obj_by_version
from cloudevents.http.util import _json_or_string
from cloudevents.sdk import converters, marshaller, types


def from_http(
data: typing.Union[str, bytes],
headers: typing.Dict[str, str],
data_unmarshaller: types.UnmarshallerType = None,
):
"""
Unwrap a CloudEvent (binary or structured) from an HTTP request.
:param data: the HTTP request body
:type data: typing.IO
:param headers: the HTTP headers
:type headers: typing.Dict[str, str]
:param data_unmarshaller: Callable function to map data to a python object
e.g. lambda x: x or lambda x: json.loads(x)
:type data_unmarshaller: types.UnmarshallerType
"""
if data_unmarshaller is None:
data_unmarshaller = _json_or_string

marshall = marshaller.NewDefaultHTTPMarshaller()

if converters.is_binary(headers):
specversion = headers.get("ce-specversion", None)
else:
raw_ce = json.loads(data)
specversion = raw_ce.get("specversion", None)

if specversion is None:
raise ValueError("could not find specversion in HTTP request")

event_handler = _obj_by_version.get(specversion, None)

if event_handler is None:
raise ValueError(f"found invalid specversion {specversion}")

event = marshall.FromRequest(
event_handler(), headers, data, data_unmarshaller=data_unmarshaller
)
attrs = event.Properties()
attrs.pop("data", None)
attrs.pop("extensions", None)
attrs.update(**event.extensions)

return CloudEvent(attrs, event.data)


def _to_http(
event: CloudEvent,
format: str = converters.TypeStructured,
data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param format: constant specifying an encoding format
:type format: str
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.MarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
if data_marshaller is None:
data_marshaller = _marshaller_by_format[format]

if event._attributes["specversion"] not in _obj_by_version:
raise ValueError(
f"Unsupported specversion: {event._attributes['specversion']}"
)

event_handler = _obj_by_version[event._attributes["specversion"]]()
for k, v in event._attributes.items():
event_handler.Set(k, v)
event_handler.data = event.data

return marshaller.NewDefaultHTTPMarshaller().ToRequest(
event_handler, format, data_marshaller=data_marshaller
)


def to_structured_http(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param event: CloudEvent to cast into http data
:type event: CloudEvent
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.MarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
return _to_http(event=event, data_marshaller=data_marshaller)


def to_binary_http(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent

:param event: CloudEvent to cast into http data
:type event: CloudEvent
:param data_marshaller: Callable function to cast event.data into
either a string or bytes
:type data_marshaller: types.UnmarshallerType
:returns: (http_headers: dict, http_body: bytes or str)
"""
return _to_http(
event=event,
format=converters.TypeBinary,
data_marshaller=data_marshaller,
)
36 changes: 36 additions & 0 deletions cloudevents/http/json_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import typing

from cloudevents.http.event import CloudEvent
from cloudevents.http.http_methods import from_http, to_structured_http
from cloudevents.sdk import types


def to_json(
event: CloudEvent, data_marshaller: types.MarshallerType = None
) -> typing.Union[str, bytes]:
"""
Cast an CloudEvent into a json object
:param event: CloudEvent which will be converted into a json object
:type event: CloudEvent
:param data_marshaller: Callable function which will cast event.data
into a json object
:type data_marshaller: typing.Callable
:returns: json object representing the given event
"""
return to_structured_http(event, data_marshaller=data_marshaller)[1]


def from_json(
data: typing.Union[str, bytes],
data_unmarshaller: types.UnmarshallerType = None,
) -> CloudEvent:
"""
Cast json encoded data into an CloudEvent
:param data: json encoded cloudevent data
:type event: typing.Union[str, bytes]
:param data_unmarshaller: Callable function which will cast data to a
python object
:type data_unmarshaller: typing.Callable
:returns: CloudEvent representing given cloudevent json object
"""
return from_http(data=data, headers={}, data_unmarshaller=data_unmarshaller)
15 changes: 15 additions & 0 deletions cloudevents/http/mappings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from cloudevents.http.util import default_marshaller
from cloudevents.sdk import converters
from cloudevents.sdk.event import v1, v03

_marshaller_by_format = {
converters.TypeStructured: lambda x: x,
converters.TypeBinary: default_marshaller,
}

_obj_by_version = {"1.0": v1.Event, "0.3": v03.Event}

_required_by_version = {
"1.0": v1.Event._ce_required_fields,
"0.3": v03.Event._ce_required_fields,
}
20 changes: 20 additions & 0 deletions cloudevents/http/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
import typing


def default_marshaller(content: any):
if content is None or len(content) == 0:
return None
try:
return json.dumps(content)
except TypeError:
return content


def _json_or_string(content: typing.Union[str, bytes]):
if len(content) == 0:
return None
try:
return json.loads(content)
except (json.JSONDecodeError, TypeError) as e:
return content
Loading