Initial release: CloudEvents Python SDK 0.0.1a0
Signed-off-by: Denis Makogon <lildee1991@gmail.com>
This commit is contained in:
parent
159332b754
commit
91224583c4
|
@ -0,0 +1,125 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
*.pyc
|
||||||
|
.testrepository
|
||||||
|
.tox/*
|
||||||
|
dist/*
|
||||||
|
build/*
|
||||||
|
html/*
|
||||||
|
*.egg*
|
||||||
|
cover/*
|
||||||
|
.coverage
|
||||||
|
rdserver.txt
|
||||||
|
python-troveclient.iml
|
||||||
|
|
||||||
|
# Files created by releasenotes build
|
||||||
|
releasenotes/build
|
||||||
|
.coverage.*
|
||||||
|
*.json
|
||||||
|
.cache
|
||||||
|
*.log*
|
||||||
|
*.csv
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
ChangeLog
|
||||||
|
AUTHORS
|
||||||
|
.pytest_cache/
|
|
@ -0,0 +1,19 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from cloudevents.sdk.converters import binary
|
||||||
|
from cloudevents.sdk.converters import structured
|
||||||
|
|
||||||
|
TypeBinary = binary.BinaryHTTPCloudEventConverter.TYPE
|
||||||
|
TypeStructured = structured.JSONHTTPCloudEventConverter.TYPE
|
|
@ -0,0 +1,41 @@
|
||||||
|
# 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 typing
|
||||||
|
|
||||||
|
from cloudevents.sdk.event import base
|
||||||
|
|
||||||
|
|
||||||
|
class Converter(object):
|
||||||
|
|
||||||
|
TYPE = None
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
event_class: base.BaseEvent,
|
||||||
|
supported_media_types: typing.Mapping[str, bool]):
|
||||||
|
self.event = event_class()
|
||||||
|
self.supported_media_types = supported_media_types
|
||||||
|
|
||||||
|
def can_read(self, media_type: str) -> bool:
|
||||||
|
return media_type in self.supported_media_types
|
||||||
|
|
||||||
|
def can_write(self, media_type: str) -> bool:
|
||||||
|
return media_type in self.supported_media_types
|
||||||
|
|
||||||
|
def read(self, headers: dict, body: typing.IO) -> base.BaseEvent:
|
||||||
|
raise Exception("not implemented")
|
||||||
|
|
||||||
|
def write(self, event: base.BaseEvent,
|
||||||
|
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
||||||
|
raise Exception("not implemented")
|
|
@ -0,0 +1,54 @@
|
||||||
|
# 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 typing
|
||||||
|
|
||||||
|
from cloudevents.sdk import exceptions
|
||||||
|
from cloudevents.sdk.converters import base
|
||||||
|
from cloudevents.sdk.event import base as event_base
|
||||||
|
from cloudevents.sdk.event import v01
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryHTTPCloudEventConverter(base.Converter):
|
||||||
|
|
||||||
|
TYPE = "binary"
|
||||||
|
|
||||||
|
def __init__(self, event_class: event_base.BaseEvent,
|
||||||
|
supported_media_types: typing.Mapping[str, bool]):
|
||||||
|
if event_class == v01.Event:
|
||||||
|
raise exceptions.UnsupportedEvent(event_class)
|
||||||
|
|
||||||
|
super().__init__(event_class, supported_media_types)
|
||||||
|
|
||||||
|
def read(self,
|
||||||
|
headers: dict, body: typing.IO) -> event_base.BaseEvent:
|
||||||
|
# we ignore headers, since the whole CE is in request body
|
||||||
|
event = self.event
|
||||||
|
event.UnmarshalBinary(headers, body)
|
||||||
|
return event
|
||||||
|
|
||||||
|
def write(self, event: event_base.BaseEvent,
|
||||||
|
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
||||||
|
hs, data = event.MarshalBinary()
|
||||||
|
return hs, data_marshaller(data)
|
||||||
|
|
||||||
|
|
||||||
|
def NewBinaryHTTPCloudEventConverter(
|
||||||
|
event_class: event_base.BaseEvent) -> BinaryHTTPCloudEventConverter:
|
||||||
|
media_types = {
|
||||||
|
"application/json": True,
|
||||||
|
"application/xml": True,
|
||||||
|
"application/octet-stream": True,
|
||||||
|
}
|
||||||
|
return BinaryHTTPCloudEventConverter(event_class, media_types)
|
|
@ -0,0 +1,48 @@
|
||||||
|
# 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 typing
|
||||||
|
|
||||||
|
from cloudevents.sdk.converters import base
|
||||||
|
from cloudevents.sdk.event import base as event_base
|
||||||
|
|
||||||
|
|
||||||
|
class JSONHTTPCloudEventConverter(base.Converter):
|
||||||
|
|
||||||
|
TYPE = "structured"
|
||||||
|
|
||||||
|
def __init__(self, event_class: event_base.BaseEvent,
|
||||||
|
supported_media_types: typing.Mapping[str, bool]):
|
||||||
|
super().__init__(event_class, supported_media_types)
|
||||||
|
|
||||||
|
def read(self, headers: dict,
|
||||||
|
body: typing.IO) -> event_base.BaseEvent:
|
||||||
|
# we ignore headers, since the whole CE is in request body
|
||||||
|
event = self.event
|
||||||
|
event.UnmarshalJSON(body)
|
||||||
|
return event
|
||||||
|
|
||||||
|
def write(self,
|
||||||
|
event: event_base.BaseEvent,
|
||||||
|
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
||||||
|
return {}, event.MarshalJSON()
|
||||||
|
|
||||||
|
|
||||||
|
def NewJSONHTTPCloudEventConverter(
|
||||||
|
event_class: event_base.BaseEvent) -> JSONHTTPCloudEventConverter:
|
||||||
|
media_types = {
|
||||||
|
"application/cloudevents+json": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONHTTPCloudEventConverter(event_class, media_types)
|
|
@ -0,0 +1,90 @@
|
||||||
|
# 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 io
|
||||||
|
import ujson
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEvent(object):
|
||||||
|
|
||||||
|
def Properties(self) -> dict:
|
||||||
|
props = dict()
|
||||||
|
for name, value in self.__dict__.items():
|
||||||
|
if str(name).startswith("ce__"):
|
||||||
|
props.update(
|
||||||
|
{
|
||||||
|
str(name).replace("ce__", ""): value.get()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return props
|
||||||
|
|
||||||
|
def Extensions(self) -> dict:
|
||||||
|
props = self.Properties()
|
||||||
|
return props.get("extensions")
|
||||||
|
|
||||||
|
def Get(self, key: str) -> (object, bool):
|
||||||
|
formatted_key = "ce__{0}".format(key.lower())
|
||||||
|
ok = hasattr(self, formatted_key)
|
||||||
|
value = getattr(self, formatted_key, None)
|
||||||
|
if not ok:
|
||||||
|
exts = self.Extensions()
|
||||||
|
return exts.get(key), key in exts
|
||||||
|
|
||||||
|
return value.get(), ok
|
||||||
|
|
||||||
|
def Set(self, key: str, value: object):
|
||||||
|
formatted_key = "ce__{0}".format(key)
|
||||||
|
key_exists = hasattr(self, formatted_key)
|
||||||
|
if key_exists:
|
||||||
|
attr = getattr(self, formatted_key)
|
||||||
|
attr.set(value)
|
||||||
|
setattr(self, formatted_key, attr)
|
||||||
|
return
|
||||||
|
|
||||||
|
exts = self.Extensions()
|
||||||
|
exts.update({key: value})
|
||||||
|
self.Set("extensions", exts)
|
||||||
|
|
||||||
|
def MarshalJSON(self) -> typing.IO:
|
||||||
|
return io.StringIO(ujson.dumps(self.Properties()))
|
||||||
|
|
||||||
|
def UnmarshalJSON(self, b: typing.IO):
|
||||||
|
raw_ce = ujson.load(b)
|
||||||
|
for name, value in raw_ce.items():
|
||||||
|
self.Set(name, value)
|
||||||
|
|
||||||
|
def UnmarshalBinary(self, headers: dict, body: typing.IO):
|
||||||
|
props = self.Properties()
|
||||||
|
for key in props:
|
||||||
|
self.Set(key, headers.get("ce-{0}".format(key)))
|
||||||
|
|
||||||
|
data = None
|
||||||
|
if body:
|
||||||
|
data = body.read()
|
||||||
|
|
||||||
|
self.Set("data", data)
|
||||||
|
|
||||||
|
def MarshalBinary(self) -> (dict, object):
|
||||||
|
headers = {}
|
||||||
|
props = self.Properties()
|
||||||
|
for key, value in props.items():
|
||||||
|
if key not in ["data", "extensions"]:
|
||||||
|
headers["ce-{0}".format(key)] = value
|
||||||
|
|
||||||
|
exts = props.get("extensions")
|
||||||
|
headers.update(**exts)
|
||||||
|
data, _ = self.Get("data")
|
||||||
|
return headers, data
|
|
@ -0,0 +1,36 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
class Option(object):
|
||||||
|
|
||||||
|
def __init__(self, name, value, is_required):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
self.is_required = is_required
|
||||||
|
|
||||||
|
def set(self, new_value):
|
||||||
|
is_none = new_value is None
|
||||||
|
if self.is_required and is_none:
|
||||||
|
raise ValueError(
|
||||||
|
"Attribute value error: '{0}', "
|
||||||
|
"invalid new value.".format(self.name))
|
||||||
|
|
||||||
|
self.value = new_value
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def required(self):
|
||||||
|
return self.is_required
|
|
@ -0,0 +1,33 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from cloudevents.sdk.event import opt
|
||||||
|
from cloudevents.sdk.event import base
|
||||||
|
|
||||||
|
|
||||||
|
class Event(base.BaseEvent):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ce__specversion = opt.Option("specversion", "0.1", True)
|
||||||
|
self.ce__type = opt.Option("type", None, True)
|
||||||
|
self.ce__source = opt.Option("source", None, True)
|
||||||
|
self.ce__id = opt.Option("id", None, True)
|
||||||
|
self.ce__time = opt.Option("time", None, True)
|
||||||
|
self.ce__schemaurl = opt.Option("schemaurl", None, False)
|
||||||
|
self.ce__contenttype = opt.Option("contenttype", None, False)
|
||||||
|
self.ce__data = opt.Option("data", None, False)
|
||||||
|
self.ce__extensions = opt.Option("extensions", dict(), False)
|
||||||
|
|
||||||
|
def CloudEventVersion(self) -> str:
|
||||||
|
return self.ce__specversion.get()
|
|
@ -0,0 +1,36 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from cloudevents.sdk.event import opt
|
||||||
|
from cloudevents.sdk.event import base
|
||||||
|
|
||||||
|
|
||||||
|
class Event(base.BaseEvent):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ce__cloudEventsVersion = opt.Option(
|
||||||
|
"cloudEventsVersion", "0.1", True)
|
||||||
|
self.ce__eventType = opt.Option("eventType", None, True)
|
||||||
|
self.ce__eventTypeVersion = opt.Option(
|
||||||
|
"eventTypeVersion", None, False)
|
||||||
|
self.ce__source = opt.Option("source", None, True)
|
||||||
|
self.ce__eventID = opt.Option("eventID", None, True)
|
||||||
|
self.ce__evenTime = opt.Option("eventTime", None, True)
|
||||||
|
self.ce__schemaURL = opt.Option("schemaURL", None, False)
|
||||||
|
self.ce__contentType = opt.Option("contentType", None, False)
|
||||||
|
self.ce__data = opt.Option("data", None, False)
|
||||||
|
self.ce__extensions = opt.Option("extensions", dict(), False)
|
||||||
|
|
||||||
|
def CloudEventVersion(self) -> str:
|
||||||
|
return self.ce__cloudEventsVersion.get()
|
|
@ -0,0 +1,27 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidMimeType(Exception):
|
||||||
|
|
||||||
|
def __init__(self, mime_type):
|
||||||
|
super().__init__(
|
||||||
|
"Invalid MIME type: {0}".format(mime_type))
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedEvent(Exception):
|
||||||
|
|
||||||
|
def __init__(self, event_class):
|
||||||
|
super().__init__("Invalid CloudEvent class: "
|
||||||
|
"'{0}'".format(event_class))
|
|
@ -0,0 +1,57 @@
|
||||||
|
# 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 typing
|
||||||
|
|
||||||
|
from cloudevents.sdk import exceptions
|
||||||
|
|
||||||
|
from cloudevents.sdk.converters import base
|
||||||
|
from cloudevents.sdk.converters import binary
|
||||||
|
from cloudevents.sdk.converters import structured
|
||||||
|
|
||||||
|
from cloudevents.sdk.event import base as event_base
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPMarshaller(object):
|
||||||
|
|
||||||
|
def __init__(self, converters: typing.List[base.Converter]):
|
||||||
|
self.__converters = converters
|
||||||
|
|
||||||
|
def FromRequest(self, headers: dict, body: typing.IO):
|
||||||
|
mimeType = headers.get("Content-Type")
|
||||||
|
for cnvrtr in self.__converters:
|
||||||
|
if cnvrtr.can_read(mimeType):
|
||||||
|
return cnvrtr.read(headers, body)
|
||||||
|
|
||||||
|
raise exceptions.InvalidMimeType(mimeType)
|
||||||
|
|
||||||
|
def ToRequest(self, event: event_base.BaseEvent,
|
||||||
|
converter_type: str,
|
||||||
|
data_marshaller: typing.Callable) -> (dict, typing.IO):
|
||||||
|
for cnvrtv in self.__converters:
|
||||||
|
if converter_type == cnvrtv.TYPE:
|
||||||
|
return cnvrtv.write(event, data_marshaller)
|
||||||
|
|
||||||
|
|
||||||
|
def NewDefaultHTTPMarshaller(
|
||||||
|
event_class: event_base.BaseEvent) -> HTTPMarshaller:
|
||||||
|
return HTTPMarshaller([
|
||||||
|
structured.NewJSONHTTPCloudEventConverter(event_class),
|
||||||
|
binary.NewBinaryHTTPCloudEventConverter(event_class),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def NewHTTPMarshaller(
|
||||||
|
converters: typing.List[base.Converter]) -> HTTPMarshaller:
|
||||||
|
return HTTPMarshaller(converters)
|
|
@ -0,0 +1,102 @@
|
||||||
|
# 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 pytest
|
||||||
|
import io
|
||||||
|
import ujson
|
||||||
|
|
||||||
|
from cloudevents.sdk import exceptions
|
||||||
|
from cloudevents.sdk import marshaller
|
||||||
|
|
||||||
|
from cloudevents.sdk.event import v01
|
||||||
|
from cloudevents.sdk.event import upstream
|
||||||
|
|
||||||
|
from cloudevents.sdk.converters import binary
|
||||||
|
from cloudevents.sdk.converters import structured
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_converter_upstream():
|
||||||
|
headers = {
|
||||||
|
"ce-specversion": "0.1",
|
||||||
|
"ce-type": "word.found.exclamation",
|
||||||
|
"ce-id": "16fb5f0b-211e-1102-3dfe-ea6e2806f124",
|
||||||
|
"ce-time": "2018-10-23T12:28:23.3464579Z",
|
||||||
|
"ce-source": "pytest",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
m = marshaller.NewHTTPMarshaller(
|
||||||
|
[
|
||||||
|
binary.NewBinaryHTTPCloudEventConverter(upstream.Event)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
event = m.FromRequest(headers, None)
|
||||||
|
assert event is not None
|
||||||
|
assert event.Get("type") == (headers.get("ce-type"), True)
|
||||||
|
assert event.Get("id") == (headers.get("ce-id"), True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_structured_converter_upstream():
|
||||||
|
ce = {
|
||||||
|
"specversion": "0.1",
|
||||||
|
"type": "word.found.exclamation",
|
||||||
|
"id": "16fb5f0b-211e-1102-3dfe-ea6e2806f124",
|
||||||
|
"time": "2018-10-23T12:28:23.3464579Z",
|
||||||
|
"source": "pytest",
|
||||||
|
"contenttype": "application/json"
|
||||||
|
}
|
||||||
|
m = marshaller.NewHTTPMarshaller(
|
||||||
|
[
|
||||||
|
structured.NewJSONHTTPCloudEventConverter(upstream.Event)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
event = m.FromRequest(
|
||||||
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
|
io.StringIO(ujson.dumps(ce))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event is not None
|
||||||
|
assert event.Get("type") == (ce.get("type"), True)
|
||||||
|
assert event.Get("id") == (ce.get("id"), True)
|
||||||
|
|
||||||
|
|
||||||
|
# todo: clarify whether spec 0.1 doesn't support binary format
|
||||||
|
def test_binary_converter_v01():
|
||||||
|
pytest.raises(
|
||||||
|
exceptions.UnsupportedEvent,
|
||||||
|
binary.NewBinaryHTTPCloudEventConverter,
|
||||||
|
v01.Event)
|
||||||
|
|
||||||
|
|
||||||
|
def test_structured_converter_v01():
|
||||||
|
ce = {
|
||||||
|
"specversion": "0.1",
|
||||||
|
"type": "word.found.exclamation",
|
||||||
|
"id": "16fb5f0b-211e-1102-3dfe-ea6e2806f124",
|
||||||
|
"time": "2018-10-23T12:28:23.3464579Z",
|
||||||
|
"source": "pytest",
|
||||||
|
"contenttype": "application/json"
|
||||||
|
}
|
||||||
|
m = marshaller.NewHTTPMarshaller(
|
||||||
|
[
|
||||||
|
structured.NewJSONHTTPCloudEventConverter(v01.Event)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
event = m.FromRequest(
|
||||||
|
{"Content-Type": "application/cloudevents+json"},
|
||||||
|
io.StringIO(ujson.dumps(ce))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event is not None
|
||||||
|
assert event.Get("type") == (ce.get("type"), True)
|
||||||
|
assert event.Get("id") == (ce.get("id"), True)
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
git checkout -b v${CLOUDEVENTS_SDK_VERSION}-stable
|
||||||
|
git push origin v${CLOUDEVENTS_SDK_VERSION}-stable
|
||||||
|
PBR_VERSION=${CLOUDEVENTS_SDK_VERSION} python setup.py sdist bdist_wheel
|
||||||
|
twine upload dist/cloudevents-${CLOUDEVENTS_SDK_VERSIONN}*
|
||||||
|
git checkout master
|
|
@ -0,0 +1,69 @@
|
||||||
|
Release process
|
||||||
|
===============
|
||||||
|
|
||||||
|
Run tests on target brunch
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
tox
|
||||||
|
|
||||||
|
Cut off stable branch
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
git checkout -b vX.X.X-stable
|
||||||
|
git push origin vX.X.X-stable
|
||||||
|
|
||||||
|
|
||||||
|
Create GitHub tag
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
Releases ---> Draft New Release
|
||||||
|
Name: CloudEvents Python SDK version X.X.X stable release
|
||||||
|
|
||||||
|
|
||||||
|
Collect changes from previous version
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
git log --oneline --decorate
|
||||||
|
|
||||||
|
|
||||||
|
Build distribution package
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
PBR_VERSION=X.X.X python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
|
|
||||||
|
Check install capability for the wheel
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
python3.7 -m venv .test_venv
|
||||||
|
source .test_venv/bin/activate
|
||||||
|
pip install dist/cloudevents-X.X.X-py3-none-any.whl
|
||||||
|
|
||||||
|
|
||||||
|
Submit release to PYPI
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
twine upload dist/cloudevents-X.X.X-py3-none-any.whl
|
||||||
|
|
||||||
|
Verify install capability for the wheel
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
python3.7 -m venv .test_venv
|
||||||
|
source .new_venv/bin/activate
|
||||||
|
pip install cloudevents --upgrade
|
|
@ -0,0 +1,2 @@
|
||||||
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
|
ujson
|
|
@ -0,0 +1,24 @@
|
||||||
|
[metadata]
|
||||||
|
name = cloudevents
|
||||||
|
summary = CloudEvents SDK Python
|
||||||
|
description-file =
|
||||||
|
README.md
|
||||||
|
author = Denis Makogon
|
||||||
|
author-email = denys.makogon@oracle.com
|
||||||
|
home-page = https://cloudevents.io/
|
||||||
|
classifier =
|
||||||
|
Intended Audience :: Information Technology
|
||||||
|
Intended Audience :: System Administrators
|
||||||
|
License :: OSI Approved :: Apache Software License
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
|
||||||
|
[files]
|
||||||
|
packages =
|
||||||
|
cloudevents
|
||||||
|
|
||||||
|
[global]
|
||||||
|
setup-hooks =
|
||||||
|
pbr.hooks.setup_hook
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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 setuptools
|
||||||
|
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['pbr>=2.0.0'],
|
||||||
|
pbr=True)
|
|
@ -0,0 +1,4 @@
|
||||||
|
flake8==2.5.0
|
||||||
|
hacking<0.11,>=0.10.0
|
||||||
|
pytest==4.0.0
|
||||||
|
pytest-cov==2.4.0
|
|
@ -0,0 +1,35 @@
|
||||||
|
[tox]
|
||||||
|
envlist = py{3.6,3.7},pep8
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
basepython =
|
||||||
|
pep8: python3
|
||||||
|
py3.6: python3.6
|
||||||
|
py3.7: python3.7
|
||||||
|
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
usedevelop = True
|
||||||
|
install_command = pip install -U {opts} {packages}
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = find . -type f -name "*.pyc" -delete
|
||||||
|
whitelist_externals = find
|
||||||
|
rm
|
||||||
|
go
|
||||||
|
docker
|
||||||
|
[testenv:pep8]
|
||||||
|
commands = flake8
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[testenv:py3.6]
|
||||||
|
commands = pytest -v -s --tb=long --cov=cloudevents {toxinidir}/cloudevents/tests
|
||||||
|
|
||||||
|
[testenv:py3.7]
|
||||||
|
commands = pytest -v -s --tb=long --cov=cloudevents {toxinidir}/cloudevents/tests
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
ignore = H405,H404,H403,H401,H306
|
||||||
|
show-source = True
|
||||||
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,docs,venv,.venv
|
Loading…
Reference in New Issue