From 8b30e5ae337c36588df295a6c9d66aaca169fe75 Mon Sep 17 00:00:00 2001 From: Andrew Standley <17914999+airstandley@users.noreply.github.com> Date: Tue, 17 Sep 2019 11:06:26 -0700 Subject: [PATCH 01/11] Fix marshmallow test helpers so that they work will all unittest compatible frameworks and not just pytest. 'python setup.py test' works again. (#127) --- tests/helpers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index 51d3f900..abfe6e99 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,11 +1,10 @@ -import pytest - +import unittest from flask_rebar.compat import MARSHMALLOW_V2, MARSHMALLOW_V3 -skip_if_marshmallow_not_v2 = pytest.mark.skipif( +skip_if_marshmallow_not_v2 = unittest.skipIf( not MARSHMALLOW_V2, reason="Only applicable for Marshmallow version 2" ) -skip_if_marshmallow_not_v3 = pytest.mark.skipif( +skip_if_marshmallow_not_v3 = unittest.skipIf( not MARSHMALLOW_V3, reason="Only applicable for Marshmallow version 3" ) From 367bfa8f4bc6be2a90059fc29084f90174df8d11 Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Tue, 17 Sep 2019 14:43:43 -0700 Subject: [PATCH 02/11] chg: Accept bare class for schema arguments (#126) --- .gitignore | 3 +- flask_rebar/rebar.py | 21 +++++++- flask_rebar/utils/request_utils.py | 3 +- tests/test_rebar.py | 78 +++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 48bbdb6c..88f54464 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ man/ .pytest_cache #vscode -.vscode/ \ No newline at end of file +.vscode/ +pip-wheel-metadata diff --git a/flask_rebar/rebar.py b/flask_rebar/rebar.py index fc5345b4..72167790 100644 --- a/flask_rebar/rebar.py +++ b/flask_rebar/rebar.py @@ -32,10 +32,16 @@ from flask_rebar.utils.request_utils import get_header_params_or_400 from flask_rebar.utils.request_utils import get_json_body_params_or_400 from flask_rebar.utils.request_utils import get_query_string_params_or_400 +from flask_rebar.utils.request_utils import normalize_schema from flask_rebar.utils.deprecation import deprecated, deprecated_parameters from flask_rebar.swagger_generation import SwaggerV2Generator from flask_rebar.swagger_ui import create_swagger_ui_blueprint +# Deal with maintaining (for now at least) support for 2.7+: +try: + from collections.abc import Mapping # 3.3+ +except ImportError: + from collections import Mapping # 2.7+ # To catch redirection exceptions, app.errorhandler expects 301 in versions # below 0.11.0 but the exception itself in versions greater than 0.11.0. @@ -459,8 +465,19 @@ def add_handler( :param Type[USE_DEFAULT]|None|str mimetype: Content-Type header to add to the response schema """ - if isinstance(response_body_schema, marshmallow.Schema): - response_body_schema = {200: response_body_schema} + # Fix #115: if we were passed bare classes we'll go ahead and instantiate + headers_schema = normalize_schema(headers_schema) + request_body_schema = normalize_schema(request_body_schema) + query_string_schema = normalize_schema(query_string_schema) + if response_body_schema: + # Ensure we wrap in appropriate default (200) dict if we were passed a single Schema or class: + if not isinstance(response_body_schema, Mapping): + response_body_schema = {200: response_body_schema} + # use normalize_schema to convert any class reference(s) to instantiated schema(s): + response_body_schema = { + code: normalize_schema(schema) + for (code, schema) in response_body_schema.items() + } # authenticators can be a list of Authenticators, a single Authenticator, USE_DEFAULT, or None if isinstance(authenticators, Authenticator) or authenticators is USE_DEFAULT: diff --git a/flask_rebar/utils/request_utils.py b/flask_rebar/utils/request_utils.py index d0c41877..a27f38ec 100644 --- a/flask_rebar/utils/request_utils.py +++ b/flask_rebar/utils/request_utils.py @@ -21,6 +21,7 @@ from flask_rebar import compat from flask_rebar import errors from flask_rebar import messages +from flask_rebar.utils.defaults import USE_DEFAULT class HeadersProxy(compat.Mapping): @@ -92,7 +93,7 @@ def normalize_schema(schema): This allows for either an instance of a marshmallow.Schema or the class itself to be passed to functions. """ - if not isinstance(schema, marshmallow.Schema): + if schema not in (None, USE_DEFAULT) and not isinstance(schema, marshmallow.Schema): schema = schema() return schema diff --git a/tests/test_rebar.py b/tests/test_rebar.py index 28af1f95..8fd503d1 100644 --- a/tests/test_rebar.py +++ b/tests/test_rebar.py @@ -12,7 +12,6 @@ import marshmallow as m from flask import Flask -from werkzeug.routing import RequestRedirect from flask_rebar import messages from flask_rebar import HeaderApiKeyAuthenticator, SwaggerV3Generator @@ -677,3 +676,80 @@ def test_redirects_for_missing_trailing_slash(self): resp = app.test_client().get(path="/with_trailing_slash") self.assertIn(resp.status_code, (301, 308)) self.assertTrue(resp.headers["Location"].endswith("/with_trailing_slash/")) + + def test_bare_class_schemas_handled(self): + rebar = Rebar() + registry = rebar.create_handler_registry() + + expected_foo = FooSchema().load({"uid": "some_uid", "name": "Namey McNamerton"}) + expected_headers = {"x-name": "Header Name"} + + def get_foo(*args, **kwargs): + return expected_foo + + def post_foo(*args, **kwargs): + return expected_foo + + register_endpoint( + registry=registry, + method="GET", + path="/my_get_endpoint", + headers_schema=HeadersSchema, + response_body_schema={200: FooSchema}, + query_string_schema=FooListSchema, + func=get_foo, + ) + + register_endpoint( + registry=registry, + method="POST", + path="/my_post_endpoint", + request_body_schema=FooListSchema, + response_body_schema=FooSchema, + func=post_foo, + ) + + app = create_rebar_app(rebar) + # violate headers schema: + resp = app.test_client().get(path="/my_get_endpoint?name=QuerystringName") + self.assertEqual(resp.status_code, 400) + self.assertEqual( + get_json_from_resp(resp)["message"], messages.header_validation_failed + ) + # violate querystring schema: + resp = app.test_client().get(path="/my_get_endpoint", headers=expected_headers) + self.assertEqual(resp.status_code, 400) + self.assertEqual( + get_json_from_resp(resp)["message"], messages.query_string_validation_failed + ) + # valid request: + resp = app.test_client().get( + path="/my_get_endpoint?name=QuerystringName", headers=expected_headers + ) + self.assertEqual(resp.status_code, 200) + self.assertEqual(get_json_from_resp(resp), expected_foo.data) + + resp = app.test_client().post( + path="/my_post_endpoint", + data='{"wrong": "Posted Name"}', + content_type="application/json", + ) + self.assertEqual(resp.status_code, 400) + self.assertEqual( + get_json_from_resp(resp)["message"], messages.body_validation_failed + ) + + resp = app.test_client().post( + path="/my_post_endpoint", + data='{"name": "Posted Name"}', + content_type="application/json", + ) + self.assertEqual(resp.status_code, 200) + + # ensure Swagger generation doesn't break (Issue #115) + from flask_rebar import SwaggerV2Generator, SwaggerV3Generator + + swagger = SwaggerV2Generator().generate(registry) + self.assertIsNotNone(swagger) # really only care that it didn't barf + swagger = SwaggerV3Generator().generate(registry) + self.assertIsNotNone(swagger) From 8d73c9ba29cc6d20a8a02c901e94541afb135f47 Mon Sep 17 00:00:00 2001 From: Artem Revenko Date: Wed, 18 Sep 2019 17:18:05 +0200 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20Treat=20"description"=20key=20the?= =?UTF-8?q?=20same=20way=20as=20"explode"=20key=20for=20query=20and=20h?= =?UTF-8?q?=E2=80=A6=20(#129)=20[Artem=20Revenko]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- flask_rebar/swagger_generation/swagger_generator_v3.py | 3 +++ setup.py | 2 +- .../swagger_generation/registries/exploded_query_string.py | 6 +++++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1b08e77b..462fed3b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.0 +current_version = 1.10.1 commit = True tag = True diff --git a/flask_rebar/swagger_generation/swagger_generator_v3.py b/flask_rebar/swagger_generation/swagger_generator_v3.py index b26289a4..8dbf408f 100644 --- a/flask_rebar/swagger_generation/swagger_generator_v3.py +++ b/flask_rebar/swagger_generation/swagger_generator_v3.py @@ -317,6 +317,7 @@ def _convert_schema_to_list_of_parameters(self, schema, converter, in_): # Pardon the ugliness. # We need the "explode" key to be at the parameters level, not at the schema level. explode = jsonschema.pop(sw.explode, None) + description = jsonschema.pop(sw.description, None) parameter = { sw.name: prop, @@ -327,6 +328,8 @@ def _convert_schema_to_list_of_parameters(self, schema, converter, in_): if explode is not None: parameter[sw.explode] = explode + if description is not None: + parameter[sw.description] = description parameters.append(parameter) diff --git a/setup.py b/setup.py index e9033a4a..69f01a35 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ if __name__ == "__main__": setup( name="flask-rebar", - version="1.10.0", + version="1.10.1", author="Barak Alon", author_email="barak.s.alon@gmail.com", description="Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.", diff --git a/tests/swagger_generation/registries/exploded_query_string.py b/tests/swagger_generation/registries/exploded_query_string.py index c093bb54..0f5fcd42 100644 --- a/tests/swagger_generation/registries/exploded_query_string.py +++ b/tests/swagger_generation/registries/exploded_query_string.py @@ -9,7 +9,9 @@ class ExplodedQueryStringSchema(RequestSchema): - foos = QueryParamList(marshmallow.fields.String(), required=True) + foos = QueryParamList( + marshmallow.fields.String(), required=True, description="foo string" + ) @registry.handles( @@ -53,6 +55,7 @@ def get_foos(): "collectionFormat": "multi", "type": "array", "items": {"type": "string"}, + "description": "foo string", } ], } @@ -95,6 +98,7 @@ def get_foos(): "name": "foos", "in": "query", "required": True, + "description": "foo string", "schema": {"type": "array", "items": {"type": "string"}}, "explode": True, } From a3bf084c9e01f04346c53caa5c3c193228918fd6 Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Wed, 18 Sep 2019 21:27:23 -0700 Subject: [PATCH 04/11] chg: Tweaking build rules, updating docs, and prepping for bumpversion do-over --- .travis.yml | 1 + CONTRIBUTING.rst | 22 +++++++++++++++------- setup.py | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8701b1d4..1daedc65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,4 +49,5 @@ jobs: password: secure: GoFaAo3a08PoPklGQ68q7EMYE59hb1Z1+PEMuSSXKelL2amyG6/kq5t7r8p6trjFabco1u94IwZFFlmUdfr4PqDQ/eaM9MVF1VvI8dxp/fg3WgSxjtxy4io/ELUWTGXTGs3V8Lp61haRdHjrqk5DwG6SHDVVzjuJBZ4Kydr6fuo0RWu9C4qAkU1MWmoIPeyeB4ZydXimD15uOvMp13UA7SqLUdXlAhwyL7c2h6pihu2gnbLXEnfkh8rlvRT8fSp7PUkFsg3FOvgMZg8dn8/J0I/pTmG7c71v0hacOVTKLsfAtSSHAjgcc8KffvMBOcE/YXgUr+lgR4R181lMqXv2LBNMuE5Gyc8GBjTb8Liiowpvg8XXH86vC/k+0/3toKWPobjsXJCAgR21+B7FFzE8hmZdP43HcSfCqbzS8ELUWF9AvUBlFtdakr0a0wLjcKsJyBNiKwwmw50kKsb7agpIXgS/I0Y6EyGwyXi8/q7IPmR7wffg3bAGnvvg5yz7vOZTGYJnFM+Ky1LdQ7fJ44D+Ze/SraB1syK2pmednX6YVMgIlPU5QpaprmQ9AkeoWmPZBShWl07Js73BnbQKWklkV8yAXq9qy51cjRA9SWQIFzeiHScOwhUalb1CpggOyG1sx4/GMFLg5nNE0ldKDfqUGJ4kbcNh25jiiBEJ7mEQkB0= on: + branch: master tags: true diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1596efc8..848de21a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -67,22 +67,30 @@ Pull Requests Releasing to PyPI ----------------- -Travis CI handles releasing package versions to PyPI. +We use Travis CI to automate releasing package versions to PyPI. + +.. warning:: These steps must be completed by an administrator. We generally do at least patch releases fairly frequently, but if you have a feature that urgently requires release, feel free to reach out and request one and we'll do our best to accommodate. + Flask-Rebar uses `semantic versions `_. Once you know the appropriate version part to bump, use the ``bumpversion`` tool which will bump the package version, add a commit, and tag the commit appropriately. Note, it's not a bad idea to do a manual inspection and any cleanup you deem necessary after running ``gitchangelog`` to ensure it looks good before then committing a "@cosmetic" update. +.. note:: Before completing the following steps, you will need to temporarily change settings on GitHub under branch protection rules to NOT include administrators. This is required to allow you to push the changelog update. + .. code-block:: bash - git checkout -b your-release-branch - bumpversion minor + git checkout master + git pull # just to play it safe and make sure you're up to date + bumpversion patch # or major or minor if applicable gitchangelog + # STOP HERE: inspect CHANGELOG.rst and clean up as needed before continuing git commit -a -m "@cosmetic - changelog" - -Then push the new commit and tags: +Then push the new commits and tags: .. code-block:: bash - git push -u origin your-release-branch --tags + git push --tags + +Finally, while you're waiting for Travis CI to pick up the tagged version, build it, and deploy it to PyPi, don't forget to reset branch protection settings (for normal operation, administrators should be subject to these restrictions to enforce PR code review requirements). + -Create a Pull Request and merge back into master. Voila. diff --git a/setup.py b/setup.py index 69f01a35..3800a011 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ if __name__ == "__main__": setup( name="flask-rebar", - version="1.10.1", + version="1.10.0", author="Barak Alon", author_email="barak.s.alon@gmail.com", description="Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.", @@ -43,6 +43,7 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Software Development :: Libraries :: Application Frameworks", From 8787f6fb6d45f5962c63070aa272ee40e49b97d5 Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Wed, 18 Sep 2019 21:44:38 -0700 Subject: [PATCH 05/11] @minor correct current version in bumpversion.cfg --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 462fed3b..1b08e77b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.1 +current_version = 1.10.0 commit = True tag = True From 8dea2ec6acccd184813401d746e35249c0224a14 Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Wed, 18 Sep 2019 21:44:45 -0700 Subject: [PATCH 06/11] =?UTF-8?q?Bump=20version:=201.10.0=20=E2=86=92=201.?= =?UTF-8?q?10.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1b08e77b..462fed3b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.0 +current_version = 1.10.1 commit = True tag = True diff --git a/setup.py b/setup.py index 3800a011..1f530abd 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ if __name__ == "__main__": setup( name="flask-rebar", - version="1.10.0", + version="1.10.1", author="Barak Alon", author_email="barak.s.alon@gmail.com", description="Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.", From f5bb2fe8dea742cc27815930c119cc446099d1db Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Wed, 18 Sep 2019 21:46:01 -0700 Subject: [PATCH 07/11] @cosmetic - changelog --- CHANGELOG.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 17282b36..f82fb484 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,27 @@ Changelog ========= +v1.10.1 (2019-09-19) +-------------------- + +Changes +~~~~~~~ +- Tweaking build rules, updating docs, and prepping for bumpversion do- + over. [Rick Riensche] + +Fix +~~~ +- Treat "description" key the same way as "explode" key for query and h… + (#129) [Artem Revenko] + +Other +~~~~~ +- Accept bare class for schema arguments (#126) [Rick Riensche] +- Fix marshmallow test helpers so that they work will all unittest + compatible frameworks and not just pytest. 'python setup.py test' + works again. (#127) [Andrew Standley] + + v1.10.0 (2019-09-11) -------------------- - BP-763: Add support for multiple authenticators (#122) [Andrew From c549bf3540d820be3992f48dbe081c2e60c7a3bf Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Wed, 18 Sep 2019 21:53:21 -0700 Subject: [PATCH 08/11] @minor - whoops screwed up git push in doc.. --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 848de21a..1304853e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -89,7 +89,7 @@ Then push the new commits and tags: .. code-block:: bash - git push --tags + git push && git push --tags Finally, while you're waiting for Travis CI to pick up the tagged version, build it, and deploy it to PyPi, don't forget to reset branch protection settings (for normal operation, administrators should be subject to these restrictions to enforce PR code review requirements). From 5cca61e37ab03a549fe504c25c58f7f6659e45f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-=C3=89ric?= Date: Thu, 19 Sep 2019 14:53:04 -0400 Subject: [PATCH 09/11] Update authenticators to catch Forbidden exception (#133) --- flask_rebar/rebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_rebar/rebar.py b/flask_rebar/rebar.py index 72167790..becf9594 100644 --- a/flask_rebar/rebar.py +++ b/flask_rebar/rebar.py @@ -125,7 +125,7 @@ def wrapped(*args, **kwargs): try: authenticator.authenticate() break # Short-circuit on first successful authentication - except errors.Unauthorized as e: + except (errors.Unauthorized, errors.Forbidden) as e: first_error = first_error or e else: raise first_error or errors.Unauthorized From dacbd6ae503032f2505f2435aeca525552fe00e7 Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Thu, 19 Sep 2019 12:45:13 -0700 Subject: [PATCH 10/11] =?UTF-8?q?Bump=20version:=201.10.1=20=E2=86=92=201.?= =?UTF-8?q?10.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 462fed3b..ec28c2d1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.1 +current_version = 1.10.2 commit = True tag = True diff --git a/setup.py b/setup.py index 1f530abd..4a73b22a 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ if __name__ == "__main__": setup( name="flask-rebar", - version="1.10.1", + version="1.10.2", author="Barak Alon", author_email="barak.s.alon@gmail.com", description="Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.", From 075d1bfccd96beb4dcb7996d1fd87f8d33b6d1ec Mon Sep 17 00:00:00 2001 From: Rick Riensche Date: Thu, 19 Sep 2019 12:46:14 -0700 Subject: [PATCH 11/11] @cosmetic - changelog --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f82fb484..2e502c33 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +v1.10.2 (2019-09-19) +-------------------- + +Fix +~~~ +- Update authenticators to catch Forbidden exception (#133) [Marc-Éric] + + v1.10.1 (2019-09-19) --------------------