Skip to content

Top level extensions fix #70

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

Closed
wants to merge 10 commits into from
Closed
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
4 changes: 4 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[settings]
line_length = 80
multi_line_output = 3
include_trailing_comma = True
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/timothycrosley/isort/
rev: 5.0.4
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
language_version: python3.8
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0]
### Added
- Added a user friendly CloudEvent class with data validation ([#36])
- CloudEvent structured cloudevent support ([#47])

### Removed
- Removed support for Cloudevents V0.2 and V0.1 ([#43])

## [0.3.0]
### Added
- Added Cloudevents V0.3 and V1 implementations ([#22])
Expand Down Expand Up @@ -66,3 +74,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#23]: https://github.com/cloudevents/sdk-python/pull/23
[#25]: https://github.com/cloudevents/sdk-python/pull/25
[#27]: https://github.com/cloudevents/sdk-python/pull/27
[#36]: https://github.com/cloudevents/sdk-python/pull/36
[#43]: https://github.com/cloudevents/sdk-python/pull/43
[#47]: https://github.com/cloudevents/sdk-python/pull/47
192 changes: 66 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,159 +14,87 @@ This SDK current supports the following versions of CloudEvents:

Package **cloudevents** provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec.

Parsing upstream structured Event from HTTP request:
## Sending CloudEvents

```python
import io

from cloudevents.sdk.event import v1
from cloudevents.sdk import marshaller

m = marshaller.NewDefaultHTTPMarshaller()

event = m.FromRequest(
v1.Event(),
{"content-type": "application/cloudevents+json"},
io.StringIO(
"""
{
"specversion": "1.0",
"datacontenttype": "application/json",
"type": "word.found.name",
"id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
"time": "2018-10-23T12:28:22.4579346Z",
"source": "<source-url>"
}
"""
),
lambda x: x.read(),
)
```
Below we will provide samples on how to send cloudevents using the popular
[`requests`](http://docs.python-requests.org) library.

Parsing upstream binary Event from HTTP request:
### Binary HTTP CloudEvent

```python
import io

from cloudevents.sdk.event import v1
from cloudevents.sdk import marshaller

m = marshaller.NewDefaultHTTPMarshaller()

event = m.FromRequest(
v1.Event(),
{
"ce-specversion": "1.0",
"content-type": "application/json",
"ce-type": "word.found.name",
"ce-id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
"ce-time": "2018-10-23T12:28:22.4579346Z",
"ce-source": "<source-url>",
},
io.BytesIO(b"this is where your CloudEvent data"),
lambda x: x.read(),
)
```
from cloudevents.sdk.http import CloudEvent, to_binary_http
import requests

Creating a minimal CloudEvent in version 0.1:

```python
from cloudevents.sdk.event import v1

event = (
v1.Event()
.SetContentType("application/json")
.SetData('{"name":"john"}')
.SetEventID("my-id")
.SetSource("from-galaxy-far-far-away")
.SetEventTime("tomorrow")
.SetEventType("cloudevent.greet.you")
)
# This data defines a binary cloudevent
attributes = {
"type": "com.example.sampletype1",
"source": "https://example.com/event-producer",
}
data = {"message": "Hello World!"}

event = CloudEvent(attributes, data)
headers, body = to_binary_http(event)

# POST
requests.post("<some-url>", data=body, headers=headers)
```

Creating HTTP request from CloudEvent:
### Structured HTTP CloudEvent

```python
from cloudevents.sdk import converters
from cloudevents.sdk import marshaller
from cloudevents.sdk.converters import structured
from cloudevents.sdk.event import v1

event = (
v1.Event()
.SetContentType("application/json")
.SetData('{"name":"john"}')
.SetEventID("my-id")
.SetSource("from-galaxy-far-far-away")
.SetEventTime("tomorrow")
.SetEventType("cloudevent.greet.you")
)

m = marshaller.NewHTTPMarshaller([structured.NewJSONHTTPCloudEventConverter()])

headers, body = m.ToRequest(event, converters.TypeStructured, lambda x: x)
```
from cloudevents.sdk.http import CloudEvent, to_structured_http
import requests

## HOWTOs with various Python HTTP frameworks

In this topic you'd find various example how to integrate an SDK with various HTTP frameworks.
# This data defines a structured cloudevent
attributes = {
"type": "com.example.sampletype2",
"source": "https://example.com/event-producer",
}
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_structured_http(event)

### Python requests
# POST
requests.post("<some-url>", data=body, headers=headers)
```

One of popular framework is [`requests`](http://docs.python-requests.org/en/master/).
You can find a complete example of turning a CloudEvent into a HTTP request [in the samples directory](samples/http-cloudevents/client.py).

#### CloudEvent to request
#### Request to CloudEvent

The code below shows how integrate both libraries in order to convert a CloudEvent into an HTTP request:
The code below shows how to consume a cloudevent using the popular python web framework
[flask](https://flask.palletsprojects.com/en/1.1.x/quickstart/):

```python
def run_binary(event, url):
binary_headers, binary_data = http_marshaller.ToRequest(
event, converters.TypeBinary, json.dumps)

print("binary CloudEvent")
for k, v in binary_headers.items():
print("{0}: {1}\r\n".format(k, v))
print(binary_data.getvalue())
response = requests.post(url,
headers=binary_headers,
data=binary_data.getvalue())
response.raise_for_status()


def run_structured(event, url):
structured_headers, structured_data = http_marshaller.ToRequest(
event, converters.TypeStructured, json.dumps
)
print("structured CloudEvent")
print(structured_data.getvalue())
from flask import Flask, request

response = requests.post(url,
headers=structured_headers,
data=structured_data.getvalue())
response.raise_for_status()
from cloudevents.sdk.http import from_http

```
app = Flask(__name__)

Complete example of turning a CloudEvent into a request you can find [here](samples/python-requests/cloudevent_to_request.py).

#### Request to CloudEvent
# create an endpoint at http://localhost:/3000/
@app.route("/", methods=["POST"])
def home():
# create a CloudEvent
event = from_http(request.get_data(), request.headers)

The code below shows how integrate both libraries in order to create a CloudEvent from an HTTP request:
# you can access cloudevent fields as seen below
print(
f"Found {event['id']} from {event['source']} with type "
f"{event['type']} and specversion {event['specversion']}"
)

```python
response = requests.get(url)
response.raise_for_status()
headers = response.headers
data = io.BytesIO(response.content)
event = v1.Event()
http_marshaller = marshaller.NewDefaultHTTPMarshaller()
event = http_marshaller.FromRequest(
event, headers, data, json.load)
return "", 204


if __name__ == "__main__":
app.run(port=3000)
```

Complete example of turning a CloudEvent into a request you can find [here](samples/python-requests/request_to_cloudevent.py).
You can find a complete example of turning a CloudEvent into a HTTP request [in the samples directory](samples/http-cloudevents/server.py).

## SDK versioning

Expand All @@ -189,3 +117,15 @@ the same API. It will use semantic versioning with following rules:
[CNCF's Slack workspace](https://slack.cncf.io/).
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
- Contact for additional information: Denis Makogon (`@denysmakogon` on slack).

## Maintenance

We use black and isort for autoformatting. We setup a tox environment to reformat
the codebase.

e.g.

```python
pip install tox
tox -e reformat
```
31 changes: 29 additions & 2 deletions cloudevents/sdk/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,35 @@
# License for the specific language governing permissions and limitations
# under the License.

from cloudevents.sdk.converters import binary
from cloudevents.sdk.converters import structured
import typing

from cloudevents.sdk.converters import binary, structured

TypeBinary = binary.BinaryHTTPCloudEventConverter.TYPE
TypeStructured = structured.JSONHTTPCloudEventConverter.TYPE


def is_binary(headers: typing.Dict[str, str]) -> bool:
"""Uses internal marshallers to determine whether this event is binary
:param headers: the HTTP headers
:type headers: typing.Dict[str, str]
:returns bool: returns a bool indicating whether the headers indicate a binary event type
"""
headers = {key.lower(): value for key, value in headers.items()}
content_type = headers.get("content-type", "")
binary_parser = binary.BinaryHTTPCloudEventConverter()
return binary_parser.can_read(content_type=content_type, headers=headers)


def is_structured(headers: typing.Dict[str, str]) -> bool:
"""Uses internal marshallers to determine whether this event is structured
:param headers: the HTTP headers
:type headers: typing.Dict[str, str]
:returns bool: returns a bool indicating whether the headers indicate a structured event type
"""
headers = {key.lower(): value for key, value in headers.items()}
content_type = headers.get("content-type", "")
structured_parser = structured.JSONHTTPCloudEventConverter()
return structured_parser.can_read(
content_type=content_type, headers=headers
)
6 changes: 2 additions & 4 deletions cloudevents/sdk/converters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def read(
event,
headers: dict,
body: typing.IO,
data_unmarshaller: typing.Callable
data_unmarshaller: typing.Callable,
) -> base.BaseEvent:
raise Exception("not implemented")

Expand All @@ -37,8 +37,6 @@ def can_read(self, content_type: str) -> bool:
raise Exception("not implemented")

def write(
self,
event: base.BaseEvent,
data_marshaller: typing.Callable
self, event: base.BaseEvent, data_marshaller: typing.Callable
) -> (dict, object):
raise Exception("not implemented")
22 changes: 15 additions & 7 deletions cloudevents/sdk/converters/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,27 @@

import typing

from cloudevents.sdk import exceptions
from cloudevents.sdk import exceptions, types
from cloudevents.sdk.converters import base
from cloudevents.sdk.converters.structured import JSONHTTPCloudEventConverter
from cloudevents.sdk.event import base as event_base
from cloudevents.sdk.event import v03, v1
from cloudevents.sdk.event import v1, v03


class BinaryHTTPCloudEventConverter(base.Converter):

TYPE = "binary"
SUPPORTED_VERSIONS = [v03.Event, v1.Event]

def can_read(self, content_type: str) -> bool:
return True
def can_read(
self,
content_type: str,
headers: typing.Dict[str, str] = {"ce-specversion": None},
) -> bool:
return ("ce-specversion" in headers) and not (
isinstance(content_type, str)
and content_type.startswith(JSONHTTPCloudEventConverter.MIME_TYPE)
)

def event_supported(self, event: object) -> bool:
return type(event) in self.SUPPORTED_VERSIONS
Expand All @@ -36,16 +44,16 @@ def read(
event: event_base.BaseEvent,
headers: dict,
body: typing.IO,
data_unmarshaller: typing.Callable,
data_unmarshaller: types.UnmarshallerType,
) -> event_base.BaseEvent:
if type(event) not in self.SUPPORTED_VERSIONS:
raise exceptions.UnsupportedEvent(type(event))
event.UnmarshalBinary(headers, body, data_unmarshaller)
return event

def write(
self, event: event_base.BaseEvent, data_marshaller: typing.Callable
) -> (dict, typing.IO):
self, event: event_base.BaseEvent, data_marshaller: types.MarshallerType
) -> (dict, bytes):
return event.MarshalBinary(data_marshaller)


Expand Down
Loading