diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c8a0d8 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/cloudevents/__init__.py b/cloudevents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudevents/sdk/__init__.py b/cloudevents/sdk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudevents/sdk/converters/__init__.py b/cloudevents/sdk/converters/__init__.py new file mode 100644 index 0000000..ee2fc41 --- /dev/null +++ b/cloudevents/sdk/converters/__init__.py @@ -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 diff --git a/cloudevents/sdk/converters/base.py b/cloudevents/sdk/converters/base.py new file mode 100644 index 0000000..1c515d4 --- /dev/null +++ b/cloudevents/sdk/converters/base.py @@ -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") diff --git a/cloudevents/sdk/converters/binary.py b/cloudevents/sdk/converters/binary.py new file mode 100644 index 0000000..53165c4 --- /dev/null +++ b/cloudevents/sdk/converters/binary.py @@ -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) diff --git a/cloudevents/sdk/converters/structured.py b/cloudevents/sdk/converters/structured.py new file mode 100644 index 0000000..b3569bd --- /dev/null +++ b/cloudevents/sdk/converters/structured.py @@ -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) diff --git a/cloudevents/sdk/event/__init__.py b/cloudevents/sdk/event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudevents/sdk/event/base.py b/cloudevents/sdk/event/base.py new file mode 100644 index 0000000..f2a3982 --- /dev/null +++ b/cloudevents/sdk/event/base.py @@ -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 diff --git a/cloudevents/sdk/event/opt.py b/cloudevents/sdk/event/opt.py new file mode 100644 index 0000000..ca2a9e6 --- /dev/null +++ b/cloudevents/sdk/event/opt.py @@ -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 diff --git a/cloudevents/sdk/event/upstream.py b/cloudevents/sdk/event/upstream.py new file mode 100644 index 0000000..4effef5 --- /dev/null +++ b/cloudevents/sdk/event/upstream.py @@ -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() diff --git a/cloudevents/sdk/event/v01.py b/cloudevents/sdk/event/v01.py new file mode 100644 index 0000000..6edb777 --- /dev/null +++ b/cloudevents/sdk/event/v01.py @@ -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() diff --git a/cloudevents/sdk/exceptions.py b/cloudevents/sdk/exceptions.py new file mode 100644 index 0000000..0eb944a --- /dev/null +++ b/cloudevents/sdk/exceptions.py @@ -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)) diff --git a/cloudevents/sdk/marshaller.py b/cloudevents/sdk/marshaller.py new file mode 100644 index 0000000..161e96a --- /dev/null +++ b/cloudevents/sdk/marshaller.py @@ -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) diff --git a/cloudevents/tests/__init__.py b/cloudevents/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudevents/tests/test_event_converter.py b/cloudevents/tests/test_event_converter.py new file mode 100644 index 0000000..5baffff --- /dev/null +++ b/cloudevents/tests/test_event_converter.py @@ -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) diff --git a/release.sh b/release.sh new file mode 100644 index 0000000..cce630e --- /dev/null +++ b/release.sh @@ -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 diff --git a/release_doc.md b/release_doc.md new file mode 100644 index 0000000..194d543 --- /dev/null +++ b/release_doc.md @@ -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 diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..445288f --- /dev/null +++ b/requirement.txt @@ -0,0 +1,2 @@ +pbr!=2.1.0,>=2.0.0 # Apache-2.0 +ujson diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..505a303 --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b242731 --- /dev/null +++ b/setup.py @@ -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) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..93414a1 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,4 @@ +flake8==2.5.0 +hacking<0.11,>=0.10.0 +pytest==4.0.0 +pytest-cov==2.4.0 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2a8402c --- /dev/null +++ b/tox.ini @@ -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