diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ccf020b --- /dev/null +++ b/.flake8 @@ -0,0 +1,16 @@ +[flake8] + +# Ignore line too long error +ignore = E501 + +exclude = + .git, + __pycache__, + build, + dist, + +per-file-ignores = + + # allow top level module exposing + __init__.py:F401 + test/__init__.py:F401 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..90baeea --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,31 @@ +name: CI Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [2.7, 3.6, 3.7, 3.8] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + make install_pipenv PIPENV_PYTHON_VERSION=${{ matrix.python-version }} + - name: Lint + run: make lint + - name: Test + run: make test diff --git a/Makefile b/Makefile index f4f6869..6ec8002 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,21 @@ +PIPENV_PYTHON_VERSION ?= 3.6 + + .PHONY: all all: $(error please pick a target) .PHONY: upload -upload: clean - python setup.py sdist bdist_wheel - pipenv run upload +upload: clean build lint test + python -m pipenv run upload + +.PHONY: build +build: + python -m pipenv run build -.PHONE: clean +.PHONY: clean clean: - rm -rf dist + rm -rf dist build nuclio_sdk.egg-info .PHONY: clean_pyc clean_pyc: @@ -17,4 +23,22 @@ clean_pyc: .PHONY: flake8 flake8: - pipenv run flake8 + python -m pipenv run flake8 + +.PHONY: test +test: + python -m pipenv run test + +.PHONY: lint +lint: + python -m pipenv run lint + +.PHONY: install_pipenv +install_pipenv: + python -m pip install --user pipenv + python -m pipenv --python ${PIPENV_PYTHON_VERSION} +ifeq ($(PIPENV_PYTHON_VERSION), 2.7) + python -m pipenv install -r requirements.py2.txt +else + python -m pipenv install --dev +endif diff --git a/Pipfile b/Pipfile index b1c60e1..6686963 100644 --- a/Pipfile +++ b/Pipfile @@ -5,16 +5,17 @@ name = "pypi" [packages] nuclio-sdk = {editable = true,path = "."} -requests = "*" -twine = "*" -bleach = "==3.1.1" [dev-packages] flake8 = "*" +twine = "*" +bleach = "==3.1.1" [requires] python_version = "3.7" [scripts] +build = "python setup.py sdist bdist_wheel" +test = "python -m unittest discover -s nuclio_sdk/test -p 'test_*.py' -v" upload = "twine upload dist/*" -flake8 = "flake8 nuclio_sdk" +lint = "flake8 nuclio_sdk" diff --git a/Pipfile.lock b/Pipfile.lock index 8bc47b7..b7b3a02 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4ed8dee388ac50c0d29644b08edf459ab7f9d95e30d71025a7b97b3521315fab" + "sha256": "973fcc606d5c3d64583f1beba77a0fcf6f7c67f430ca445ede7a4a5b4dab5e84" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,12 @@ ] }, "default": { + "nuclio-sdk": { + "editable": true, + "path": "." + } + }, + "develop": { "bleach": { "hashes": [ "sha256:44f69771e2ac81ff30d929d485b7f9919f3ad6d019b6b20c74f3b8687c3f70df", @@ -38,6 +44,14 @@ ], "version": "==3.0.4" }, + "configparser": { + "hashes": [ + "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c", + "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df" + ], + "markers": "python_version == '2.7'", + "version": "==4.0.2" + }, "docutils": { "hashes": [ "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", @@ -45,31 +59,51 @@ ], "version": "==0.16" }, - "idna": { + "entrypoints": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" ], - "version": "==2.9" + "version": "==0.3" }, - "importlib-metadata": { + "enum34": { "hashes": [ - "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", - "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + "sha256:13ef9a1c478203252107f66c25b99b45b1865693ca1284aab40dafa7e1e7ac17", + "sha256:708aabfb3d5898f99674c390d360d59efdd08547019763622365f19e84a7fef4", + "sha256:98df1f1937840b7d8012fea7f0b36392a3e6fd8a2f429c48a3ff4b1aad907f3f" ], - "markers": "python_version < '3.8'", - "version": "==1.5.0" + "markers": "python_version < '3.4'", + "version": "==1.1.9" }, - "keyring": { + "flake8": { "hashes": [ - "sha256:1f393f7466314068961c7e1d508120c092bd71fa54e3d93b76180b526d4abc56", - "sha256:24ae23ab2d6adc59138339e56843e33ec7b0a6b2f06302662477085c6c0aca00" + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" ], - "version": "==21.1.0" + "index": "pypi", + "version": "==3.7.9" }, - "nuclio-sdk": { - "editable": true, - "path": "." + "functools32": { + "hashes": [ + "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", + "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d" + ], + "markers": "python_version < '3.2'", + "version": "==3.2.3.post2" + }, + "idna": { + "hashes": [ + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + ], + "version": "==2.9" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" }, "pkginfo": { "hashes": [ @@ -78,6 +112,20 @@ ], "version": "==1.5.0.1" }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, "pygments": { "hashes": [ "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", @@ -97,7 +145,6 @@ "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "index": "pypi", "version": "==2.23.0" }, "requests-toolbelt": { @@ -123,11 +170,20 @@ }, "twine": { "hashes": [ - "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124", - "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160" + "sha256:630fadd6e342e725930be6c696537e3f9ccc54331742b16245dab292a17d0460", + "sha256:a3d22aab467b4682a22de4a422632e79d07eebd07ff2a7079effb13f8a693787" ], "index": "pypi", - "version": "==3.1.1" + "version": "==1.15.0" + }, + "typing": { + "hashes": [ + "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", + "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", + "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714" + ], + "markers": "python_version < '3.5'", + "version": "==3.7.4.1" }, "urllib3": { "hashes": [ @@ -142,51 +198,6 @@ "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" ], "version": "==0.5.1" - }, - "zipp": { - "hashes": [ - "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", - "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" - ], - "version": "==3.1.0" - } - }, - "develop": { - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, - "flake8": { - "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" - ], - "index": "pypi", - "version": "==3.7.9" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" - ], - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "version": "==2.1.1" } } } diff --git a/nuclio_sdk/event.py b/nuclio_sdk/event.py index 5f7b137..6852280 100644 --- a/nuclio_sdk/event.py +++ b/nuclio_sdk/event.py @@ -72,6 +72,10 @@ def to_json(self): if isinstance(self.timestamp, datetime.datetime): obj['timestamp'] = str(self.timestamp) + # TEMP: this should be done only on python3 + if isinstance(obj['body'], bytes): + obj['body'] = base64.b64encode(obj['body']).decode('ascii') + return json.dumps(obj) def get_header(self, header_key): @@ -131,13 +135,13 @@ def decode_body(body, content_type): try: decoded_body = base64.b64decode(body) - except: + except: # noqa E722 return body if content_type == 'application/json': try: return json.loads(decoded_body) - except: + except: # noqa E722 pass return decoded_body diff --git a/nuclio_sdk/exceptions.py b/nuclio_sdk/exceptions.py index 0b5d7d8..f8fd0ed 100644 --- a/nuclio_sdk/exceptions.py +++ b/nuclio_sdk/exceptions.py @@ -1,3 +1,18 @@ +# Copyright 2017 The Nuclio Authors. +# +# 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 ExceptionWithResponse(IOError): def __init__(self, status_code, body=None, content_type=None): diff --git a/nuclio_sdk/helpers.py b/nuclio_sdk/helpers.py new file mode 100644 index 0000000..42d0797 --- /dev/null +++ b/nuclio_sdk/helpers.py @@ -0,0 +1,18 @@ +# Copyright 2017 The Nuclio Authors. +# +# 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 sys + +PYTHON3 = sys.version_info[0] == 3 +PYTHON2 = sys.version_info[0] == 2 diff --git a/nuclio_sdk/logger.py b/nuclio_sdk/logger.py index f4c7d4a..8b2127c 100644 --- a/nuclio_sdk/logger.py +++ b/nuclio_sdk/logger.py @@ -54,8 +54,8 @@ def format(self, record): class Logger(object): - def __init__(self, level): - self._logger = logging.getLogger('nuclio_sdk') + def __init__(self, level, name='nuclio_sdk'): + self._logger = logging.getLogger(name) self._logger.setLevel(level) self._bound_variables = {} self._handlers = {} @@ -66,7 +66,7 @@ def set_handler(self, handler_name, file, formatter): if handler_name in self._handlers: # log that we're removing it - self.info('Replacing logger output') + self.info_with('Replacing logger output', handler_name=handler_name) self._logger.removeHandler(self._handlers[handler_name]) diff --git a/nuclio_sdk/platform.py b/nuclio_sdk/platform.py index 4262d2c..6d869a7 100644 --- a/nuclio_sdk/platform.py +++ b/nuclio_sdk/platform.py @@ -13,16 +13,16 @@ # limitations under the License. import json -import sys + +import nuclio_sdk +import nuclio_sdk.helpers # different HTTP client libraries for Python 2/3 -if sys.version_info[:2] < (3, 0): +if nuclio_sdk.helpers.PYTHON2: from httplib import HTTPConnection else: from http.client import HTTPConnection -import nuclio_sdk - class Platform(object): diff --git a/nuclio_sdk/response.py b/nuclio_sdk/response.py index b1e81df..cae2bbf 100644 --- a/nuclio_sdk/response.py +++ b/nuclio_sdk/response.py @@ -19,10 +19,10 @@ class Response(object): def __init__(self, headers=None, body=None, content_type=None, status_code=200): - self.headers = headers + self.headers = headers or {} self.body = body self.status_code = status_code - self.content_type = content_type + self.content_type = content_type or 'text/plain' def __repr__(self): cls = self.__class__.__name__ @@ -35,13 +35,7 @@ def from_entrypoint_output(json_encoder, handler_output): """Given a handler output's type, generates a response towards the processor""" - response = { - 'body': '', - 'content_type': 'text/plain', - 'headers': {}, - 'status_code': 200, - 'body_encoding': 'text', - } + response = Response.empty_response() # if the type of the output is a string, just return that and 200 if isinstance(handler_output, str): @@ -81,3 +75,13 @@ def from_entrypoint_output(json_encoder, handler_output): response['body_encoding'] = 'base64' return response + + @staticmethod + def empty_response(): + return { + 'body': '', + 'content_type': 'text/plain', + 'headers': {}, + 'status_code': 200, + 'body_encoding': 'text', + } diff --git a/nuclio_sdk/test/__init__.py b/nuclio_sdk/test/__init__.py index 2fc0f4d..5511773 100644 --- a/nuclio_sdk/test/__init__.py +++ b/nuclio_sdk/test/__init__.py @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from nuclio_sdk.test.platform import Platform +from nuclio_sdk.test.mock_platform import Platform from nuclio_sdk.test.test_case import TestCase diff --git a/nuclio_sdk/test/platform.py b/nuclio_sdk/test/mock_platform.py similarity index 91% rename from nuclio_sdk/test/platform.py rename to nuclio_sdk/test/mock_platform.py index 88fa1ff..e4f9faf 100644 --- a/nuclio_sdk/test/platform.py +++ b/nuclio_sdk/test/mock_platform.py @@ -14,19 +14,24 @@ import sys import logging -import unittest.mock import nuclio_sdk +import nuclio_sdk.helpers + +if nuclio_sdk.helpers.PYTHON2: + import mock +else: + from unittest import mock class Platform(object): def __init__(self): - self._logger = nuclio_sdk.Logger(logging.DEBUG) + self._logger = nuclio_sdk.Logger(logging.DEBUG, 'mock_platform') self._logger.set_handler('default', sys.stdout, nuclio_sdk.logger.HumanReadableFormatter()) self._handler_contexts = {} - self._call_function_mock = unittest.mock.MagicMock() + self._call_function_mock = mock.MagicMock() self._kind = 'test' # for tests that need a context diff --git a/nuclio_sdk/test/test_case.py b/nuclio_sdk/test/test_case.py index 401fa72..6fa8545 100644 --- a/nuclio_sdk/test/test_case.py +++ b/nuclio_sdk/test/test_case.py @@ -26,4 +26,5 @@ def setUp(self): self._environ_copy = copy.copy(os.environ) def tearDown(self): - os.environ = self._environ_copy + if hasattr(self, '_environ_copy'): + os.environ = self._environ_copy diff --git a/nuclio_sdk/test/test_event.py b/nuclio_sdk/test/test_event.py new file mode 100644 index 0000000..18b7107 --- /dev/null +++ b/nuclio_sdk/test/test_event.py @@ -0,0 +1,44 @@ +# Copyright 2017 The Nuclio Authors. +# +# 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 nuclio_sdk.test +import nuclio_sdk.helpers +import nuclio_sdk.json_encoder + + +class TestEvent(nuclio_sdk.test.TestCase): + + def test_event_to_json_bytes_body(self): + event = nuclio_sdk.Event(body=b'bytes-body', + content_type='content-type', + trigger=nuclio_sdk.TriggerInfo(kind='http', name='my-http-trigger'), + method='GET') + event_json = event.to_json() + serialized_event = nuclio_sdk.json_encoder.json.loads(event_json) + self.assertEqual(serialized_event['body'], 'Ynl0ZXMtYm9keQ==') + self.assertEqual(serialized_event['content_type'], 'content-type') + self.assertEqual(serialized_event['method'], 'GET') + self.assertEqual(serialized_event['trigger'], {'kind': 'http', 'name': 'my-http-trigger'}) + + def test_event_to_json_bytes_non_utf8able_body(self): + event = nuclio_sdk.Event(body=b'\x80abc') + event_json = event.to_json() + serialized_event = nuclio_sdk.json_encoder.json.loads(event_json) + self.assertEqual(serialized_event['body'], 'gGFiYw==') + + def test_event_to_json_string_body(self): + event = nuclio_sdk.Event(body='str-body') + jsonized_event = nuclio_sdk.json_encoder.json.loads(event.to_json()) + expected_response = 'c3RyLWJvZHk=' if nuclio_sdk.helpers.PYTHON2 else 'str-body' + self.assertEqual(jsonized_event['body'], expected_response) diff --git a/nuclio_sdk/test/test_logger.py b/nuclio_sdk/test/test_logger.py new file mode 100644 index 0000000..bd94491 --- /dev/null +++ b/nuclio_sdk/test/test_logger.py @@ -0,0 +1,69 @@ +# Copyright 2017 The Nuclio Authors. +# +# 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 unittest +import logging +import datetime + +import nuclio_sdk.test +import nuclio_sdk.helpers + +if nuclio_sdk.helpers.PYTHON2: + from StringIO import StringIO +else: + from io import StringIO + + +class TestLogger(nuclio_sdk.test.TestCase): + def setUp(self): + self._io = StringIO() + self._logger = nuclio_sdk.Logger(logging.DEBUG, 'test_logger') + self._logger.set_handler('default', self._io, nuclio_sdk.logger.JSONFormatter()) + + def test_log_text(self): + """ + message only log line is printed + """ + + self._logger.debug('TestA') + self.assertIn('TestA', self._io.getvalue()) + + def test_log_with_char(self): + """ + log line with text kwarg + """ + + self._logger.debug_with('TestB', char='a') + self.assertIn('TestB', self._io.getvalue()) + self.assertIn('"with": {"char": "a"}', self._io.getvalue()) + + def test_log_with_number(self): + """ + log line with int kwarg + """ + + self._logger.debug_with('TestC', number=1) + self.assertIn('TestC', self._io.getvalue()) + self.assertIn('"with": {"number": 1}', self._io.getvalue()) + + @unittest.skip('currently unsupported') + def test_log_with_date(self): + """ + log line with datetime kwarg + """ + + date = datetime.datetime.strptime('Oct 1 2020', '%b %d %Y') + self._logger.debug_with('TestD', date=date) + self.assertIn('TestD', self._io.getvalue()) + self.assertIn('"with": {"date": "2020-10-01T00:00:00"}', self._io.getvalue()) diff --git a/nuclio_sdk/test/test_response.py b/nuclio_sdk/test/test_response.py new file mode 100644 index 0000000..5044c91 --- /dev/null +++ b/nuclio_sdk/test/test_response.py @@ -0,0 +1,108 @@ +# Copyright 2017 The Nuclio Authors. +# +# 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 base64 +import unittest +import datetime + +import nuclio_sdk.test +import nuclio_sdk.json_encoder +import nuclio_sdk.helpers + + +class TestResponse(nuclio_sdk.test.TestCase): + def setUp(self): + self._encoder = nuclio_sdk.json_encoder.Encoder() + + def test_str(self): + handler_return = 'test' + expected_response = self._compile_output_response(body='test') + self._validate_response(handler_return, expected_response) + + def test_int(self): + handler_return = 2020 + expected_response = self._compile_output_response(body=2020) + self._validate_response(handler_return, expected_response) + + @unittest.skipIf(nuclio_sdk.helpers.PYTHON2, 'on py2 bytes are just an alias to str') + def test_bytes(self): + handler_return = b'test' + expected_response = self._compile_output_response(body='dGVzdA==', # base64 value for 'test' + body_encoding='base64') + self._validate_response(handler_return, expected_response) + + def test_dict(self): + handler_return = {'json': True} + expected_response = self._compile_output_response(body='{"json": true}', + content_type='application/json') + self._validate_response(handler_return, expected_response) + + def test_iterable(self): + handler_return = [1, 2, 3, True] + expected_response = self._compile_output_response(body='[1, 2, 3, true]', + content_type='application/json') + self._validate_response(handler_return, expected_response) + + def test_datetime(self): + handler_return = datetime.datetime.now() + expected_response = self._compile_output_response(body=handler_return) + self._validate_response(handler_return, expected_response) + + def test_status_code_and_str(self): + handler_return = (201, 'test') + expected_response = self._compile_output_response(body='test', + status_code=handler_return[0]) + self._validate_response(handler_return, expected_response) + + def test_status_code_and_dict(self): + handler_return = (201, {'json': True}) + expected_response = self._compile_output_response(body='{"json": true}', + status_code=handler_return[0], + content_type='application/json') + self._validate_response(handler_return, expected_response) + + def test_sdk_response_str(self): + handler_return = nuclio_sdk.Response(body='test') + expected_response = self._compile_output_response(body='test') + self._validate_response(handler_return, expected_response) + + def test_sdk_response_dict(self): + handler_return = {'json': True} + expected_response = self._compile_output_response(body='{"json": true}', + content_type='application/json') + self._validate_response(handler_return, expected_response) + + def _validate_response(self, handler_return, expected_response): + response = nuclio_sdk.Response.from_entrypoint_output(self._encoder.encode, handler_return) + self.assertDictEqual(response, expected_response) + + def _compile_output_response(self, **kwargs): + + # TEMP: that is a weird situation whereas all string on py2 turns into base64 + if nuclio_sdk.helpers.PYTHON2 and 'body' in kwargs and isinstance(kwargs['body'], str): + kwargs['body'] = base64.b64encode(kwargs['body']).decode('ascii') + kwargs['body_encoding'] = 'base64' + + return self._merge_dicts(nuclio_sdk.Response.empty_response(), kwargs) + + def _merge_dicts(self, d1, d2): + """ + Creates a new dictionary d3, which is the sum of d1 and d2. d1 and d2's values remain unchanged + + :return: d3 which is d1 + d2 + """ + + d3 = d1.copy() + d3.update(d2) + return d3 diff --git a/requirements.py2.txt b/requirements.py2.txt new file mode 100644 index 0000000..4eab018 --- /dev/null +++ b/requirements.py2.txt @@ -0,0 +1,2 @@ +mock==2.0.0 +flake8==3.7.9