From 6b87e0a66d4a1004134c25a636295daa96e762fa Mon Sep 17 00:00:00 2001 From: dbmoriar Date: Fri, 9 Oct 2020 23:39:58 +0000 Subject: [PATCH 1/4] Added specs-url-scheme option for Api. --- .gitignore | 6 ++++++ flask_restx/api.py | 33 ++++++++++++++++++++++----------- flask_restx/inputs.py | 11 +++++++++-- flask_restx/model.py | 2 +- flask_restx/reqparse.py | 5 ++++- tests/test_api.py | 10 ++++++++++ 6 files changed, 52 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index ebb3a6ce..b8f00fed 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,9 @@ doc/_build/ # Specifics flask_restx/static node_modules + +# pyenv +.python-version + +# Jet Brains +.idea diff --git a/flask_restx/api.py b/flask_restx/api.py index 0e561f86..983cd7b9 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -94,6 +94,9 @@ class Api(object): :param FormatChecker format_checker: A jsonschema.FormatChecker object that is hooked into the Model validator. A default or a custom FormatChecker can be provided (e.g., with custom checkers), otherwise the default action is to not enforce any format validation. + :param specs_url_scheme: If set to a string (e.g. http, https), then the specs_url will explicitly use this scheme + regardless of how the application is deployed. This is necessary for some deployments such as behind an + AWS elastic load balancer so that the user recieves the full swagger URL. """ def __init__( @@ -123,6 +126,7 @@ def __init__( catch_all_404s=False, serve_challenge_on_401=False, format_checker=None, + specs_url_scheme=None, **kwargs ): self.version = version @@ -144,10 +148,12 @@ def __init__( self._default_error_handler = None self.tags = tags or [] - self.error_handlers = OrderedDict({ - ParseError: mask_parse_error_handler, - MaskError: mask_error_handler, - }) + self.error_handlers = OrderedDict( + { + ParseError: mask_parse_error_handler, + MaskError: mask_error_handler, + } + ) self._schema = None self.models = {} self._refresolver = None @@ -178,6 +184,7 @@ def __init__( api=self, path="/", ) + self.specs_url_scheme = specs_url_scheme if app is not None: self.app = app self.init_app(app) @@ -220,7 +227,6 @@ def init_app(self, app, **kwargs): else: self.blueprint = app - def _init_app(self, app): """ Perform initialization actions with the given :class:`flask.Flask` object. @@ -254,14 +260,13 @@ def _init_app(self, app): # check for deprecated config variable names if "ERROR_404_HELP" in app.config: - app.config['RESTX_ERROR_404_HELP'] = app.config['ERROR_404_HELP'] + app.config["RESTX_ERROR_404_HELP"] = app.config["ERROR_404_HELP"] warnings.warn( "'ERROR_404_HELP' config setting is deprecated and will be " "removed in the future. Use 'RESTX_ERROR_404_HELP' instead.", - DeprecationWarning + DeprecationWarning, ) - def __getattr__(self, name): try: return getattr(self.default_namespace, name) @@ -407,7 +412,8 @@ def make_response(self, data, *args, **kwargs): kwargs.pop("fallback_mediatype", None) or self.default_mediatype ) mediatype = request.accept_mimetypes.best_match( - self.representations, default=default_mediatype, + self.representations, + default=default_mediatype, ) if mediatype is None: raise NotAcceptable() @@ -515,11 +521,16 @@ def endpoint(self, name): @property def specs_url(self): """ - The Swagger specifications relative url (ie. `swagger.json`) + The Swagger specifications relative url (ie. `swagger.json`). If + the spec_url_scheme attribute is set, then the full url is provided instead + (e.g. http://localhost/swaggger.json). :rtype: str """ - return url_for(self.endpoint("specs")) + external = None if self.specs_url_scheme is None else True + return url_for( + self.endpoint("specs"), _scheme=self.specs_url_scheme, _external=external + ) @property def base_url(self): diff --git a/flask_restx/inputs.py b/flask_restx/inputs.py index b05532f3..56796fc4 100644 --- a/flask_restx/inputs.py +++ b/flask_restx/inputs.py @@ -523,9 +523,16 @@ def boolean(value): elif not value: return False value = str(value).lower() - if value in ("true", "1", "on",): + if value in ( + "true", + "1", + "on", + ): return True - if value in ("false", "0",): + if value in ( + "false", + "0", + ): return False raise ValueError("Invalid literal for boolean(): {0}".format(value)) diff --git a/flask_restx/model.py b/flask_restx/model.py index 1b2f7b7d..a273f1a8 100644 --- a/flask_restx/model.py +++ b/flask_restx/model.py @@ -171,7 +171,7 @@ def _schema(self): } if self.__strict__: - definition['additionalProperties'] = False + definition["additionalProperties"] = False return not_none(definition) diff --git a/flask_restx/reqparse.py b/flask_restx/reqparse.py index 63260660..5d9d8e98 100644 --- a/flask_restx/reqparse.py +++ b/flask_restx/reqparse.py @@ -106,7 +106,10 @@ def __init__( required=False, ignore=False, type=text_type, - location=("json", "values",), + location=( + "json", + "values", + ), choices=(), action="store", help=None, diff --git a/tests/test_api.py b/tests/test_api.py index fb0b9ada..5daceb04 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -342,3 +342,13 @@ class TestResource(restx.Resource): assert decorator1.called is True assert decorator2.called is True assert decorator3.called is True + + def test_specs_url(self, app): + api = restx.Api(app) + specs_url = api.specs_url + assert specs_url == "/swagger.json" + + def test_specs_url_api_scheme(self, app): + api = restx.Api(app, specs_url_scheme="https") + specs_url = api.specs_url + assert specs_url == "https://localhost/swagger.json" From 8988b6372d8aed98bf853ecb0088286f6ca6e2d6 Mon Sep 17 00:00:00 2001 From: dbmoriar Date: Mon, 12 Oct 2020 14:56:51 +0000 Subject: [PATCH 2/4] Switched property from specs_url_scheme to url_scheme and applied to base_url as well. --- flask_restx/api.py | 21 ++++++++++++--------- tests/test_api.py | 9 +++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/flask_restx/api.py b/flask_restx/api.py index 983cd7b9..31060318 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -94,9 +94,9 @@ class Api(object): :param FormatChecker format_checker: A jsonschema.FormatChecker object that is hooked into the Model validator. A default or a custom FormatChecker can be provided (e.g., with custom checkers), otherwise the default action is to not enforce any format validation. - :param specs_url_scheme: If set to a string (e.g. http, https), then the specs_url will explicitly use this scheme - regardless of how the application is deployed. This is necessary for some deployments such as behind an - AWS elastic load balancer so that the user recieves the full swagger URL. + :param url_scheme: If set to a string (e.g. http, https), then the specs_url and base_url will explicitly use this + scheme regardless of how the application is deployed. This is necessary for some deployments behind a reverse + proxy. """ def __init__( @@ -126,7 +126,7 @@ def __init__( catch_all_404s=False, serve_challenge_on_401=False, format_checker=None, - specs_url_scheme=None, + url_scheme=None, **kwargs ): self.version = version @@ -184,7 +184,7 @@ def __init__( api=self, path="/", ) - self.specs_url_scheme = specs_url_scheme + self.url_scheme = url_scheme if app is not None: self.app = app self.init_app(app) @@ -205,7 +205,9 @@ def init_app(self, app, **kwargs): :param str contact: A contact email for the API (used in Swagger documentation) :param str license: The license associated to the API (used in Swagger documentation) :param str license_url: The license page URL (used in Swagger documentation) - + :param url_scheme: If set to a string (e.g. http, https), then the specs_url and base_url will explicitly use + this scheme regardless of how the application is deployed. This is necessary for some deployments behind a + reverse proxy. """ self.app = app self.title = kwargs.get("title", self.title) @@ -216,6 +218,7 @@ def init_app(self, app, **kwargs): self.contact_email = kwargs.get("contact_email", self.contact_email) self.license = kwargs.get("license", self.license) self.license_url = kwargs.get("license_url", self.license_url) + self.url_scheme = kwargs.get("url_scheme", self.url_scheme) self._add_specs = kwargs.get("add_specs", True) # If app is a blueprint, defer the initialization @@ -527,9 +530,9 @@ def specs_url(self): :rtype: str """ - external = None if self.specs_url_scheme is None else True + external = None if self.url_scheme is None else True return url_for( - self.endpoint("specs"), _scheme=self.specs_url_scheme, _external=external + self.endpoint("specs"), _scheme=self.url_scheme, _external=external ) @property @@ -539,7 +542,7 @@ def base_url(self): :rtype: str """ - return url_for(self.endpoint("root"), _external=True) + return url_for(self.endpoint("root"), _scheme=self.url_scheme, _external=True) @property def base_path(self): diff --git a/tests/test_api.py b/tests/test_api.py index 5daceb04..8e2433cc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -348,7 +348,8 @@ def test_specs_url(self, app): specs_url = api.specs_url assert specs_url == "/swagger.json" - def test_specs_url_api_scheme(self, app): - api = restx.Api(app, specs_url_scheme="https") - specs_url = api.specs_url - assert specs_url == "https://localhost/swagger.json" + def test_url_scheme(self, app): + api = restx.Api(app, url_scheme="https") + assert api.specs_url == "https://localhost/swagger.json" + assert api.base_url == "https://localhost/" + From b69a2f8f5aaa3cc1e5dcf85e1790220bb1cf53a5 Mon Sep 17 00:00:00 2001 From: dbmoriar Date: Mon, 12 Oct 2020 15:20:49 +0000 Subject: [PATCH 3/4] Added newline --- tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 8e2433cc..1dd6452a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -352,4 +352,3 @@ def test_url_scheme(self, app): api = restx.Api(app, url_scheme="https") assert api.specs_url == "https://localhost/swagger.json" assert api.base_url == "https://localhost/" - From 44c9dea8683e92b99bb5eb1466996e5beb786ad8 Mon Sep 17 00:00:00 2001 From: dbmoriar Date: Mon, 12 Oct 2020 15:30:49 +0000 Subject: [PATCH 4/4] Reversed formatting changes to unrelated code. --- flask_restx/api.py | 17 +++++++---------- flask_restx/inputs.py | 11 ++--------- flask_restx/model.py | 2 +- flask_restx/reqparse.py | 5 +---- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/flask_restx/api.py b/flask_restx/api.py index 31060318..a793b896 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -148,12 +148,10 @@ def __init__( self._default_error_handler = None self.tags = tags or [] - self.error_handlers = OrderedDict( - { - ParseError: mask_parse_error_handler, - MaskError: mask_error_handler, - } - ) + self.error_handlers = OrderedDict({ + ParseError: mask_parse_error_handler, + MaskError: mask_error_handler, + }) self._schema = None self.models = {} self._refresolver = None @@ -263,11 +261,11 @@ def _init_app(self, app): # check for deprecated config variable names if "ERROR_404_HELP" in app.config: - app.config["RESTX_ERROR_404_HELP"] = app.config["ERROR_404_HELP"] + app.config['RESTX_ERROR_404_HELP'] = app.config['ERROR_404_HELP'] warnings.warn( "'ERROR_404_HELP' config setting is deprecated and will be " "removed in the future. Use 'RESTX_ERROR_404_HELP' instead.", - DeprecationWarning, + DeprecationWarning ) def __getattr__(self, name): @@ -415,8 +413,7 @@ def make_response(self, data, *args, **kwargs): kwargs.pop("fallback_mediatype", None) or self.default_mediatype ) mediatype = request.accept_mimetypes.best_match( - self.representations, - default=default_mediatype, + self.representations, default=default_mediatype, ) if mediatype is None: raise NotAcceptable() diff --git a/flask_restx/inputs.py b/flask_restx/inputs.py index 56796fc4..b05532f3 100644 --- a/flask_restx/inputs.py +++ b/flask_restx/inputs.py @@ -523,16 +523,9 @@ def boolean(value): elif not value: return False value = str(value).lower() - if value in ( - "true", - "1", - "on", - ): + if value in ("true", "1", "on",): return True - if value in ( - "false", - "0", - ): + if value in ("false", "0",): return False raise ValueError("Invalid literal for boolean(): {0}".format(value)) diff --git a/flask_restx/model.py b/flask_restx/model.py index a273f1a8..1b2f7b7d 100644 --- a/flask_restx/model.py +++ b/flask_restx/model.py @@ -171,7 +171,7 @@ def _schema(self): } if self.__strict__: - definition["additionalProperties"] = False + definition['additionalProperties'] = False return not_none(definition) diff --git a/flask_restx/reqparse.py b/flask_restx/reqparse.py index 5d9d8e98..63260660 100644 --- a/flask_restx/reqparse.py +++ b/flask_restx/reqparse.py @@ -106,10 +106,7 @@ def __init__( required=False, ignore=False, type=text_type, - location=( - "json", - "values", - ), + location=("json", "values",), choices=(), action="store", help=None,