diff --git a/httpbin/core.py b/httpbin/core.py index a82c1b88..4e7f0264 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -33,7 +33,10 @@ except ImportError: # werkzeug < 2.1 from werkzeug.wrappers import BaseResponse as Response -from flasgger import Swagger, NO_SANITIZER +try: + from flasgger import Swagger, NO_SANITIZER +except ImportError: + Swagger = None from . import filters from .helpers import ( @@ -93,79 +96,82 @@ def jsonify(*args, **kwargs): app.add_template_global("HTTPBIN_TRACKING" in os.environ, name="tracking_enabled") -app.config["SWAGGER"] = {"title": "httpbin.org", "uiversion": 3} - -template = { - "swagger": "2.0", - "info": { - "title": "httpbin.org", - "description": ( - "A simple HTTP Request & Response Service." - "
A Kenneth Reitz project." - "

Run locally:
" - "$ docker pull ghcr.io/psf/httpbin
" - "$ docker run -p 80:8080 ghcr.io/psf/httpbin" - ), - "contact": { - "responsibleOrganization": "Python Software Foundation", - "responsibleDeveloper": "Kenneth Reitz", - "url": "https://github.com/psf/httpbin/", - }, - # "termsOfService": "http://me.com/terms", - "version": version, - }, - "host": "httpbin.org", # overrides localhost:5000 - "basePath": "/", # base bash for blueprint registration - "schemes": ["https"], - "protocol": "https", - "tags": [ - { - "name": "HTTP Methods", - "description": "Testing different HTTP verbs", - # 'externalDocs': {'description': 'Learn more', 'url': 'https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html'} - }, - {"name": "Auth", "description": "Auth methods"}, - { - "name": "Status codes", - "description": "Generates responses with given status code", - }, - {"name": "Request inspection", "description": "Inspect the request data"}, - { - "name": "Response inspection", - "description": "Inspect the response data like caching and headers", - }, - { - "name": "Response formats", - "description": "Returns responses in different data formats", - }, - {"name": "Dynamic data", "description": "Generates random and dynamic data"}, - {"name": "Cookies", "description": "Creates, reads and deletes Cookies"}, - {"name": "Images", "description": "Returns different image formats"}, - {"name": "Redirects", "description": "Returns different redirect responses"}, - { - "name": "Anything", - "description": "Returns anything that is passed to request", +if Swagger is not None: + app.config["SWAGGER"] = {"title": "httpbin.org", "uiversion": 3} + + template = { + "swagger": "2.0", + "info": { + "title": "httpbin.org", + "description": ( + "A simple HTTP Request & Response Service." + "
A Kenneth Reitz project." + "

Run locally:
" + "$ docker pull ghcr.io/psf/httpbin
" + "$ docker run -p 80:8080 ghcr.io/psf/httpbin" + ), + "contact": { + "responsibleOrganization": "Python Software Foundation", + "responsibleDeveloper": "Kenneth Reitz", + "url": "https://github.com/psf/httpbin/", + }, + # "termsOfService": "http://me.com/terms", + "version": version, }, - ], -} - -swagger_config = { - "headers": [], - "specs": [ - { - "endpoint": "spec", - "route": "/spec.json", - "rule_filter": lambda rule: True, # all in - "model_filter": lambda tag: True, # all in - } - ], - "static_url_path": "/flasgger_static", - # "static_folder": "static", # must be set by user - "swagger_ui": True, - "specs_route": "/", -} + "host": "httpbin.org", # overrides localhost:5000 + "basePath": "/", # base bash for blueprint registration + "schemes": ["https"], + "protocol": "https", + "tags": [ + { + "name": "HTTP Methods", + "description": "Testing different HTTP verbs", + # 'externalDocs': {'description': 'Learn more', 'url': 'https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html'} + }, + {"name": "Auth", "description": "Auth methods"}, + { + "name": "Status codes", + "description": "Generates responses with given status code", + }, + {"name": "Request inspection", "description": "Inspect the request data"}, + { + "name": "Response inspection", + "description": "Inspect the response data like caching and headers", + }, + { + "name": "Response formats", + "description": "Returns responses in different data formats", + }, + {"name": "Dynamic data", "description": "Generates random and dynamic data"}, + {"name": "Cookies", "description": "Creates, reads and deletes Cookies"}, + {"name": "Images", "description": "Returns different image formats"}, + {"name": "Redirects", "description": "Returns different redirect responses"}, + { + "name": "Anything", + "description": "Returns anything that is passed to request", + }, + ], + } -swagger = Swagger(app, sanitizer=NO_SANITIZER, template=template, config=swagger_config) + swagger_config = { + "headers": [], + "specs": [ + { + "endpoint": "spec", + "route": "/spec.json", + "rule_filter": lambda rule: True, # all in + "model_filter": lambda tag: True, # all in + } + ], + "static_url_path": "/flasgger_static", + # "static_folder": "static", # must be set by user + "swagger_ui": True, + "specs_route": "/", + } + + swagger = Swagger(app, sanitizer=NO_SANITIZER, template=template, config=swagger_config) +else: + app.logger.warning("Swagger not found, legacy index will be used.") # Set up Bugsnag exception tracking, if desired. To use Bugsnag, install the # Bugsnag Python client with the command "pip install bugsnag", and set the @@ -244,6 +250,13 @@ def set_cors_headers(response): # ------ +if Swagger is None: + @app.route("/") + def no_flasgger_index(): + """Redirect to legacy index if flasgger is not available.""" + return redirect(url_for("view_landing_page")) + + @app.route("/legacy") def view_landing_page(): """Generates Landing Page in legacy layout.""" diff --git a/pyproject.toml b/pyproject.toml index c5bdb811..34e2c43d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ classifiers = [ dependencies = [ "brotlicffi", "decorator", - "flasgger", "flask >= 2.2.4", 'greenlet < 3.0; python_version<"3.12"', 'greenlet >= 3.0.0a1; python_version>="3.12.0rc0"', @@ -44,6 +43,9 @@ dependencies = [ [project.optional-dependencies] test = ["pytest", "tox"] +flasgger = [ + "flasgger", +] mainapp = [ "gunicorn", "gevent", diff --git a/tests/test_httpbin.py b/tests/test_httpbin.py index 6b751245..2ce63a61 100755 --- a/tests/test_httpbin.py +++ b/tests/test_httpbin.py @@ -10,6 +10,7 @@ from io import BytesIO import httpbin +from httpbin.core import Swagger from httpbin.helpers import parse_multi_value_header @@ -115,7 +116,8 @@ def setUp(self): def test_index(self): response = self.app.get('/', headers={'User-Agent': 'test'}) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, + 200 if Swagger is not None else 302) def get_data(self, response): if 'get_data' in dir(response):