diff --git a/connexion/apis/abstract.py b/connexion/apis/abstract.py index c4af0c655..36b3351fa 100644 --- a/connexion/apis/abstract.py +++ b/connexion/apis/abstract.py @@ -105,19 +105,16 @@ def __init__( *args, resolver_error_handler: t.Optional[t.Callable] = None, pythonic_params=False, - debug: bool = False, **kwargs, ) -> None: """Minimal interface of an API, with only functionality related to routing. :param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended to any shadowed built-ins - :param debug: Flag to run in debug mode """ super().__init__(*args, **kwargs) logger.debug("Pythonic params: %s", str(pythonic_params)) self.pythonic_params = pythonic_params - self.debug = debug self.resolver_error_handler = resolver_error_handler self.add_paths() @@ -171,12 +168,9 @@ def _handle_add_operation_error(self, path: str, method: str, exc_info: tuple): error_msg = "Failed to add operation for {method} {url}".format( method=method.upper(), url=url ) - if self.debug: - logger.exception(error_msg) - else: - logger.error(error_msg) - _type, value, traceback = exc_info - raise value.with_traceback(traceback) + logger.error(error_msg) + _type, value, traceback = exc_info + raise value.with_traceback(traceback) class AbstractAPI(AbstractRoutingAPI, metaclass=AbstractAPIMeta): @@ -190,7 +184,6 @@ def __init__( base_path=None, arguments=None, resolver=None, - debug=False, resolver_error_handler=None, options=None, **kwargs, @@ -206,7 +199,6 @@ def __init__( arguments=arguments, resolver=resolver, resolver_error_handler=resolver_error_handler, - debug=debug, options=options, **kwargs, ) diff --git a/connexion/apps/abstract.py b/connexion/apps/abstract.py index ecf89d9e3..80c1c9879 100644 --- a/connexion/apps/abstract.py +++ b/connexion/apps/abstract.py @@ -20,12 +20,9 @@ def __init__( self, import_name, api_cls, - port=None, specification_dir="", - host=None, arguments=None, auth_all_paths=False, - debug=None, resolver=None, options=None, skip_error_handlers=False, @@ -34,30 +31,22 @@ def __init__( """ :param import_name: the name of the application package :type import_name: str - :param host: the host interface to bind on. - :type host: str - :param port: port to listen to - :type port: int :param specification_dir: directory where to look for specifications :type specification_dir: pathlib.Path | str :param arguments: arguments to replace on the specification :type arguments: dict | None :param auth_all_paths: whether to authenticate not defined paths :type auth_all_paths: bool - :param debug: include debugging information - :type debug: bool :param resolver: Callable that maps operationID to a function :param middlewares: Callable that maps operationID to a function :type middlewares: list | None """ - self.port = port - self.host = host - self.debug = debug self.resolver = resolver self.import_name = import_name self.arguments = arguments or {} self.api_cls = api_cls self.resolver_error = None + self.extra_files = [] # Options self.auth_all_paths = auth_all_paths @@ -169,7 +158,9 @@ def add_api( if isinstance(specification, dict): specification = specification else: - specification = self.specification_dir / specification + specification = t.cast(pathlib.Path, self.specification_dir / specification) + # Add specification as file to watch for reloading + self.extra_files.append(str(specification.relative_to(pathlib.Path.cwd()))) api_options = self.options.extend(options) @@ -182,7 +173,6 @@ def add_api( validate_responses=validate_responses, strict_validation=strict_validation, auth_all_paths=auth_all_paths, - debug=self.debug, validator_map=validator_map, pythonic_params=pythonic_params, options=api_options.as_dict(), @@ -197,7 +187,6 @@ def add_api( validate_responses=validate_responses, strict_validation=strict_validation, auth_all_paths=auth_all_paths, - debug=self.debug, pythonic_params=pythonic_params, options=api_options.as_dict(), ) @@ -267,6 +256,38 @@ def index(): `HEAD`). """ + def run(self, import_string: str = None, **kwargs): + """Run the application using uvicorn. + + :param import_string: application as import string (eg. "main:app"). This is needed to run + using reload. + :param kwargs: kwargs to pass to `uvicorn.run`. + """ + try: + import uvicorn + except ImportError: + raise RuntimeError( + "uvicorn is not installed. Please install connexion using the uvicorn extra " + "(connexion[uvicorn])" + ) + + logger.warning( + f"`{self.__class__.__name__}.run` is optimized for development. " + "For production, run using a dedicated ASGI server." + ) + + app: t.Union[str, AbstractApp] + if import_string is not None: + app = import_string + kwargs.setdefault("reload", True) + kwargs["reload_includes"] = self.extra_files + kwargs.get( + "reload_includes", [] + ) + else: + app = self + + uvicorn.run(app, **kwargs) + @abc.abstractmethod def __call__(self, scope, receive, send): """ diff --git a/connexion/apps/async_app.py b/connexion/apps/async_app.py index e1987124c..365a31619 100644 --- a/connexion/apps/async_app.py +++ b/connexion/apps/async_app.py @@ -83,7 +83,11 @@ async def asgi_app(self, scope: Scope, receive: Receive, send: Send) -> None: ) api_base_path = connexion_context.get("api_base_path") - if api_base_path and not api_base_path == self.base_path: + if ( + api_base_path is not None + and api_base_path in self.apis + and not api_base_path == self.base_path + ): api = self.apis[api_base_path] return await api(scope, receive, send) diff --git a/connexion/apps/flask_app.py b/connexion/apps/flask_app.py index 3be2481cd..48d013f6c 100644 --- a/connexion/apps/flask_app.py +++ b/connexion/apps/flask_app.py @@ -23,9 +23,7 @@ class FlaskApp(AbstractApp): - def __init__( - self, import_name, server="flask", server_args=None, extra_files=None, **kwargs - ): + def __init__(self, import_name, server_args=None, **kwargs): """ :param extra_files: additional files to be watched by the reloader, defaults to the swagger specs of added apis :type extra_files: list[str | pathlib.Path], optional @@ -34,9 +32,7 @@ def __init__( """ self.import_name = import_name - self.server = server self.server_args = dict() if server_args is None else server_args - self.extra_files = extra_files or [] self.app = self.create_app() @@ -100,8 +96,6 @@ def common_error_handler(self, exception): def add_api(self, specification, **kwargs): api = super().add_api(specification, **kwargs) self.app.register_blueprint(api.blueprint) - if isinstance(specification, (str, pathlib.Path)): - self.extra_files.append(self.specification_dir / specification) return api def add_error_handler(self, error_code, function): @@ -124,89 +118,11 @@ def index(): logger.debug("Adding %s with decorator", rule, extra=kwargs) return self.app.route(rule, **kwargs) - def run( - self, port=None, server=None, debug=None, host=None, extra_files=None, **options - ): # pragma: no cover - """ - Runs the application on a local development server. - - :param host: the host interface to bind on. - :type host: str - :param port: port to listen to - :type port: int - :param server: which wsgi server to use - :type server: str | None - :param debug: include debugging information - :type debug: bool - :param extra_files: additional files to be watched by the reloader. - :type extra_files: Iterable[str | pathlib.Path] - :param options: options to be forwarded to the underlying server - """ - # this functions is not covered in unit tests because we would effectively testing the mocks - - # overwrite constructor parameter - if port is not None: - self.port = port - elif self.port is None: - self.port = 5000 - - self.host = host or self.host or "0.0.0.0" - - if server is not None: - self.server = server - - if debug is not None: - self.debug = debug - - if extra_files is not None: - self.extra_files.extend(extra_files) - - logger.debug("Starting %s HTTP server..", self.server, extra=vars(self)) - if self.server == "flask": - self.app.run( - self.host, - port=self.port, - debug=self.debug, - extra_files=self.extra_files, - **options, - ) - elif self.server == "tornado": - try: - import tornado.autoreload - import tornado.httpserver - import tornado.ioloop - import tornado.wsgi - except ImportError: - raise Exception("tornado library not installed") - wsgi_container = tornado.wsgi.WSGIContainer(self.app) - http_server = tornado.httpserver.HTTPServer(wsgi_container, **options) - http_server.listen(self.port, address=self.host) - if self.debug: - tornado.autoreload.start() - logger.info("Listening on %s:%s..", self.host, self.port) - tornado.ioloop.IOLoop.instance().start() - elif self.server == "gevent": - try: - import gevent.pywsgi - except ImportError: - raise Exception("gevent library not installed") - if self.debug: - logger.warning( - "gevent server doesn't support debug mode. Please switch to flask/tornado server." - ) - http_server = gevent.pywsgi.WSGIServer( - (self.host, self.port), self.app, **options - ) - logger.info("Listening on %s:%s..", self.host, self.port) - http_server.serve_forever() - else: - raise Exception(f"Server {self.server} not recognized") - - def __call__(self, scope, receive, send): + async def __call__(self, scope, receive, send): """ ASGI interface. Calls the middleware wrapped around the wsgi app. """ - return self.middleware(scope, receive, send) + return await self.middleware(scope, receive, send) class FlaskJSONProvider(flask.json.provider.DefaultJSONProvider): diff --git a/connexion/cli.py b/connexion/cli.py index e43766a9e..7884d2f8c 100644 --- a/connexion/cli.py +++ b/connexion/cli.py @@ -230,7 +230,7 @@ def run( "swagger_url": console_ui_url or None, } - app = app_cls(__name__, debug=debug, auth_all_paths=auth_all_paths, options=options) + app = app_cls(__name__, auth_all_paths=auth_all_paths, options=options) app.add_api( spec_file_full_path, diff --git a/connexion/decorators/lifecycle.py b/connexion/decorators/lifecycle.py index e2bbf164a..c1808ce0e 100644 --- a/connexion/decorators/lifecycle.py +++ b/connexion/decorators/lifecycle.py @@ -53,7 +53,7 @@ async def wrapper(*args, **kwargs): @functools.wraps(function) def wrapper(*args, **kwargs): - request = self.api.get_request() + request = self.api.get_request(*args, uri_parser=uri_parser, **kwargs) response = function(request) return self.api.get_response(response, self.mimetype) diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 2ce8a2c46..237d4ce9d 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -38,6 +38,7 @@ def parameter_to_arg( sanitize = pythonic if pythonic_params else sanitized arguments, has_kwargs = inspect_function_arguments(function) + # TODO: should always be used for AsyncApp if asyncio.iscoroutinefunction(function): @functools.wraps(function) @@ -72,7 +73,7 @@ async def wrapper( else: @functools.wraps(function) - async def wrapper(request: ConnexionRequest) -> t.Any: + def wrapper(request: ConnexionRequest) -> t.Any: body_name = sanitize(operation.body_name(request.content_type)) # Pass form contents separately for Swagger2 for backward compatibility with # Connexion 2 Checking for body_name is not enough diff --git a/connexion/middleware/abstract.py b/connexion/middleware/abstract.py index 75c263c31..4dc1ccddc 100644 --- a/connexion/middleware/abstract.py +++ b/connexion/middleware/abstract.py @@ -120,7 +120,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: "you have a routing middleware registered upstream. " ) api_base_path = connexion_context.get("api_base_path") - if api_base_path: + if api_base_path is not None and api_base_path in self.apis: api = self.apis[api_base_path] operation_id = connexion_context.get("operation_id") try: diff --git a/connexion/middleware/main.py b/connexion/middleware/main.py index fcd4ff08c..b47c04e58 100644 --- a/connexion/middleware/main.py +++ b/connexion/middleware/main.py @@ -55,7 +55,7 @@ def _apply_middlewares( for middleware in reversed(middlewares): app = middleware(app) # type: ignore apps.append(app) - return app, reversed(apps) + return app, list(reversed(apps)) def add_api( self, diff --git a/connexion/middleware/swagger_ui.py b/connexion/middleware/swagger_ui.py index 5ef7b765b..ff6b1d2f6 100644 --- a/connexion/middleware/swagger_ui.py +++ b/connexion/middleware/swagger_ui.py @@ -4,6 +4,7 @@ import typing as t from contextvars import ContextVar +from starlette.requests import Request as StarletteRequest from starlette.responses import RedirectResponse from starlette.responses import Response as StarletteResponse from starlette.routing import Router @@ -42,16 +43,11 @@ def __init__(self, *args, default: ASGIApp, **kwargs): def normalize_string(string): return re.sub(r"[^a-zA-Z0-9]", "_", string.strip("/")) - def _base_path_for_prefix(self, request): + def _base_path_for_prefix(self, request: StarletteRequest) -> str: """ - returns a modified basePath which includes the incoming request's - path prefix. + returns a modified basePath which includes the incoming root_path. """ - base_path = self.base_path - if not request.url.path.startswith(self.base_path): - prefix = request.url.path.split(self.base_path)[0] - base_path = prefix + base_path - return base_path + return request.scope.get("root_path", "").rstrip("/") def _spec_for_prefix(self, request): """ @@ -68,7 +64,7 @@ def add_openapi_json(self): (or {base_path}/swagger.json for swagger2) """ logger.info( - "Adding spec json: %s/%s", self.base_path, self.options.openapi_spec_path + "Adding spec json: %s%s", self.base_path, self.options.openapi_spec_path ) self.router.add_route( methods=["GET"], @@ -132,8 +128,11 @@ def add_swagger_ui(self): # normalize_path_middleware because we also serve static files # from this dir (below) - async def redirect(_request): - return RedirectResponse(url=self.base_path + console_ui_path + "/") + async def redirect(request): + url = request.scope.get("root_path", "").rstrip("/") + url += console_ui_path + url += "/" + return RedirectResponse(url=url) self.router.add_route(methods=["GET"], path=console_ui_path, endpoint=redirect) diff --git a/examples/apikey/README.rst b/examples/apikey/README.rst new file mode 100644 index 000000000..98d6e475d --- /dev/null +++ b/examples/apikey/README.rst @@ -0,0 +1,22 @@ +======================= +API Key Example +======================= + +Running: + +.. code-block:: bash + + $ pip install --upgrade connexion[swagger-ui] # install Connexion from PyPI + $ python app.py + +Now open your browser and go to http://localhost:8080/openapi/ui/ or +http://localhost:8080/swagger/ui/ to see the Swagger UI. + +The hardcoded apikey is `asdf1234567890`. + +Test it out (in another terminal): + +.. code-block:: bash + + $ curl -H 'X-Auth: asdf1234567890' http://localhost:8080/openapi/secret + $ curl -H 'X-Auth: asdf1234567890' http://localhost:8080/swagger/secret diff --git a/examples/openapi3/apikey/app.py b/examples/apikey/app.py similarity index 68% rename from examples/openapi3/apikey/app.py rename to examples/apikey/app.py index f43f896c5..7e98b00cc 100755 --- a/examples/openapi3/apikey/app.py +++ b/examples/apikey/app.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python3 """ Basic example of a resource server """ +from pathlib import Path import connexion from connexion.exceptions import OAuthProblem @@ -22,7 +22,10 @@ def get_secret(user) -> str: return f"You are {user} and the secret is 'wbevuec'" +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml") +app.add_api("swagger.yaml") + + if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("openapi.yaml") - app.run(port=8080) + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/apikey/openapi.yaml b/examples/apikey/spec/openapi.yaml similarity index 94% rename from examples/openapi3/apikey/openapi.yaml rename to examples/apikey/spec/openapi.yaml index 4088b421a..f6bed1aea 100644 --- a/examples/openapi3/apikey/openapi.yaml +++ b/examples/apikey/spec/openapi.yaml @@ -2,6 +2,8 @@ openapi: 3.0.0 info: title: API Key Example version: '1.0' +servers: + - url: /openapi paths: /secret: get: diff --git a/examples/apikey/spec/swagger.yaml b/examples/apikey/spec/swagger.yaml new file mode 100644 index 000000000..374a49074 --- /dev/null +++ b/examples/apikey/spec/swagger.yaml @@ -0,0 +1,23 @@ +swagger: "2.0" +info: + title: API Key Example + version: '1.0' +basePath: /swagger +paths: + /secret: + get: + summary: Return secret string + operationId: app.get_secret + responses: + '200': + description: secret response + schema: + type: string + security: + - api_key: [] +securityDefinitions: + api_key: + type: apiKey + name: X-Auth + in: header + x-apikeyInfoFunc: app.apikey_auth diff --git a/examples/basicauth/README.rst b/examples/basicauth/README.rst new file mode 100644 index 000000000..7f10ffcae --- /dev/null +++ b/examples/basicauth/README.rst @@ -0,0 +1,15 @@ +======================= +HTTP Basic Auth Example +======================= + +Running: + +.. code-block:: bash + + $ pip install --upgrade connexion[swagger-ui] # install Connexion from PyPI + $ python app.py + +Now open your browser and go to http://localhost:8080/openapi/ui/ or +http://localhost:8080/swagger/ui/ to see the Swagger UI. + +The hardcoded credentials are ``admin:secret`` and ``foo:bar``. diff --git a/examples/openapi3/basicauth/app.py b/examples/basicauth/app.py similarity index 67% rename from examples/openapi3/basicauth/app.py rename to examples/basicauth/app.py index d9fc944fd..91eb8aa4d 100755 --- a/examples/openapi3/basicauth/app.py +++ b/examples/basicauth/app.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python3 """ Basic example of a resource server """ +from pathlib import Path import connexion @@ -19,7 +19,10 @@ def get_secret(user) -> str: return f"You are {user} and the secret is 'wbevuec'" +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml") +app.add_api("swagger.yaml") + + if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("openapi.yaml") - app.run(port=8080) + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/basicauth/openapi.yaml b/examples/basicauth/spec/openapi.yaml similarity index 94% rename from examples/openapi3/basicauth/openapi.yaml rename to examples/basicauth/spec/openapi.yaml index a83e66df4..fb3583935 100644 --- a/examples/openapi3/basicauth/openapi.yaml +++ b/examples/basicauth/spec/openapi.yaml @@ -2,6 +2,8 @@ openapi: 3.0.0 info: title: Basic Auth Example version: '1.0' +servers: + - url: /openapi paths: /secret: get: diff --git a/examples/swagger2/basicauth/swagger.yaml b/examples/basicauth/spec/swagger.yaml similarity index 95% rename from examples/swagger2/basicauth/swagger.yaml rename to examples/basicauth/spec/swagger.yaml index fe0d0fd6e..12d098bdd 100644 --- a/examples/swagger2/basicauth/swagger.yaml +++ b/examples/basicauth/spec/swagger.yaml @@ -1,9 +1,8 @@ swagger: "2.0" - info: title: Basic Auth Example version: "1.0" - +basePath: /swagger paths: /secret: get: diff --git a/examples/swagger2/enforcedefaults/README.rst b/examples/enforcedefaults/README.rst similarity index 74% rename from examples/swagger2/enforcedefaults/README.rst rename to examples/enforcedefaults/README.rst index 47f841787..2729001ba 100644 --- a/examples/swagger2/enforcedefaults/README.rst +++ b/examples/enforcedefaults/README.rst @@ -2,6 +2,11 @@ Custom Validator Example ======================== +.. warning:: + + This example is outdated. Currently validation no longer adapts the body. + TODO: decide if validation should adapt body or how we want to enable defaults otherwise. + In this example we fill-in non-provided properties with their defaults. Validator code is based on example from `python-jsonschema docs`_. @@ -9,7 +14,7 @@ Running: .. code-block:: bash - $ ./enforcedefaults.py + $ python app.py Now open your browser and go to http://localhost:8080/v1/ui/ to see the Swagger UI. If you send a ``POST`` request with empty body ``{}``, you should receive diff --git a/examples/swagger2/enforcedefaults/enforcedefaults.py b/examples/enforcedefaults/app.py similarity index 64% rename from examples/swagger2/enforcedefaults/enforcedefaults.py rename to examples/enforcedefaults/app.py index fda4e7719..4c7b6ceee 100755 --- a/examples/swagger2/enforcedefaults/enforcedefaults.py +++ b/examples/enforcedefaults/app.py @@ -1,12 +1,13 @@ -#!/usr/bin/env python3 +from pathlib import Path import connexion import jsonschema -from connexion.decorators.validation import RequestBodyValidator from connexion.json_schema import Draft4RequestValidator +from connexion.validators import JSONRequestBodyValidator -def echo(data): +# TODO: should work as sync endpoint when parameter decorator is fixed +async def echo(data): return data @@ -27,15 +28,17 @@ def set_defaults(validator, properties, instance, schema): DefaultsEnforcingDraft4Validator = extend_with_set_default(Draft4RequestValidator) -class DefaultsEnforcingRequestBodyValidator(RequestBodyValidator): +class DefaultsEnforcingRequestBodyValidator(JSONRequestBodyValidator): def __init__(self, *args, **kwargs): super().__init__(*args, validator=DefaultsEnforcingDraft4Validator, **kwargs) -validator_map = {"body": DefaultsEnforcingRequestBodyValidator} +validator_map = {"body": {"application/json": DefaultsEnforcingRequestBodyValidator}} + + +app = connexion.AsyncApp(__name__, specification_dir="spec") +app.add_api("swagger.yaml", validator_map=validator_map) if __name__ == "__main__": - app = connexion.FlaskApp(__name__, port=8080, specification_dir=".") - app.add_api("enforcedefaults-api.yaml", validator_map=validator_map) - app.run() + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/swagger2/enforcedefaults/enforcedefaults-api.yaml b/examples/enforcedefaults/spec/swagger.yaml similarity index 95% rename from examples/swagger2/enforcedefaults/enforcedefaults-api.yaml rename to examples/enforcedefaults/spec/swagger.yaml index dbcd2b877..e68128d0e 100644 --- a/examples/swagger2/enforcedefaults/enforcedefaults-api.yaml +++ b/examples/enforcedefaults/spec/swagger.yaml @@ -11,7 +11,7 @@ paths: /echo: post: description: Echo passed data - operationId: enforcedefaults.echo + operationId: app.echo parameters: - name: data in: body diff --git a/examples/helloworld/README.rst b/examples/helloworld/README.rst new file mode 100644 index 000000000..44a656108 --- /dev/null +++ b/examples/helloworld/README.rst @@ -0,0 +1,12 @@ +=================== +Hello World Example +=================== + +Running: + +.. code-block:: bash + + $ python hello.py + +Now open your browser and go to http://localhost:8080/openapi/ui/ or +http://localhost:8080/swagger/ui/ to see the Swagger UI. diff --git a/examples/helloworld/hello.py b/examples/helloworld/hello.py new file mode 100755 index 000000000..7ba26d60a --- /dev/null +++ b/examples/helloworld/hello.py @@ -0,0 +1,16 @@ +from pathlib import Path + +import connexion + + +def post_greeting(name: str) -> str: + return f"Hello {name}" + + +app = connexion.FlaskApp(__name__, specification_dir="spec/") +app.add_api("openapi.yaml", arguments={"title": "Hello World Example"}) +app.add_api("swagger.yaml", arguments={"title": "Hello World Example"}) + + +if __name__ == "__main__": + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/helloworld/openapi/helloworld-api.yaml b/examples/helloworld/spec/openapi.yaml similarity index 94% rename from examples/openapi3/helloworld/openapi/helloworld-api.yaml rename to examples/helloworld/spec/openapi.yaml index 214dd151d..1b94d288b 100644 --- a/examples/openapi3/helloworld/openapi/helloworld-api.yaml +++ b/examples/helloworld/spec/openapi.yaml @@ -4,7 +4,7 @@ info: title: Hello World version: "1.0" servers: - - url: http://localhost:9090/v1.0 + - url: /openapi paths: /greeting/{name}: diff --git a/examples/swagger2/helloworld/swagger/helloworld-api.yaml b/examples/helloworld/spec/swagger.yaml similarity index 96% rename from examples/swagger2/helloworld/swagger/helloworld-api.yaml rename to examples/helloworld/spec/swagger.yaml index f75cb742a..9319818e6 100644 --- a/examples/swagger2/helloworld/swagger/helloworld-api.yaml +++ b/examples/helloworld/spec/swagger.yaml @@ -4,7 +4,7 @@ info: title: "{{title}}" version: "1.0" -basePath: /v1.0 +basePath: /swagger paths: /greeting/{name}: diff --git a/examples/helloworld_async/README.rst b/examples/helloworld_async/README.rst new file mode 100644 index 000000000..9f86cb1cc --- /dev/null +++ b/examples/helloworld_async/README.rst @@ -0,0 +1,12 @@ +=================================== +Hello World Example using async App +=================================== + +Running: + +.. code-block:: bash + + $ python hello.py + +Now open your browser and go to http://localhost:8000/openapi/ui/ or +http://localhost:8000/swagger/ui/ to see the Swagger UI. diff --git a/examples/helloworld_async/hello.py b/examples/helloworld_async/hello.py new file mode 100755 index 000000000..2410eea3b --- /dev/null +++ b/examples/helloworld_async/hello.py @@ -0,0 +1,21 @@ +from pathlib import Path + +import connexion + + +async def test(): + pass + + +async def post_greeting(name: str): + await test() + return f"Hello {name}", 201 + + +app = connexion.AsyncApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml", arguments={"title": "Hello World Example"}) +app.add_api("swagger.yaml", arguments={"title": "Hello World Example"}) + + +if __name__ == "__main__": + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/helloworld_async/openapi/helloworld-api.yaml b/examples/helloworld_async/spec/openapi.yaml similarity index 94% rename from examples/openapi3/helloworld_async/openapi/helloworld-api.yaml rename to examples/helloworld_async/spec/openapi.yaml index 214dd151d..1b94d288b 100644 --- a/examples/openapi3/helloworld_async/openapi/helloworld-api.yaml +++ b/examples/helloworld_async/spec/openapi.yaml @@ -4,7 +4,7 @@ info: title: Hello World version: "1.0" servers: - - url: http://localhost:9090/v1.0 + - url: /openapi paths: /greeting/{name}: diff --git a/examples/helloworld_async/spec/swagger.yaml b/examples/helloworld_async/spec/swagger.yaml new file mode 100644 index 000000000..d42b32696 --- /dev/null +++ b/examples/helloworld_async/spec/swagger.yaml @@ -0,0 +1,25 @@ +swagger: "2.0" + +info: + title: Hello World + version: "1.0" +basePath: /swagger + +paths: + /greeting/{name}: + post: + summary: Generate greeting + description: Generates a greeting message. + operationId: hello.post_greeting + responses: + 200: + description: greeting response + schema: + type: string + example: "hello dave!" + parameters: + - name: name + in: path + description: Name of the person to greet. + required: true + type: string diff --git a/examples/openapi3/jwt/README.rst b/examples/jwt/README.rst similarity index 67% rename from examples/openapi3/jwt/README.rst rename to examples/jwt/README.rst index bade19de7..cbce38804 100644 --- a/examples/openapi3/jwt/README.rst +++ b/examples/jwt/README.rst @@ -2,12 +2,16 @@ JWT Auth Example ======================= +.. note:: + + jwt is not supported by swagger 2.0: https://swagger.io/docs/specification/2-0/authentication/ + Running: .. code-block:: bash - $ sudo pip3 install -r requirements.txt - $ ./app.py + $ pip install -r requirements.txt + $ python app.py Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. Use endpoint **/auth** to generate JWT token, copy it, then click **Authorize** button and paste the token. diff --git a/examples/openapi3/jwt/app.py b/examples/jwt/app.py similarity index 86% rename from examples/openapi3/jwt/app.py rename to examples/jwt/app.py index 2d03b307c..fdcbc908b 100755 --- a/examples/openapi3/jwt/app.py +++ b/examples/jwt/app.py @@ -1,9 +1,8 @@ -#!/usr/bin/env python3 """ Basic example of a resource server """ - import time +from pathlib import Path import connexion from jose import JWTError, jwt @@ -47,7 +46,9 @@ def _current_timestamp() -> int: return int(time.time()) +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml") + + if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("openapi.yaml") - app.run(port=8080) + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/jwt/requirements.txt b/examples/jwt/requirements.txt new file mode 100644 index 000000000..5ef6e028c --- /dev/null +++ b/examples/jwt/requirements.txt @@ -0,0 +1,3 @@ +..[swagger-ui] +python-jose[cryptography] +Flask>=0.10.1 diff --git a/examples/openapi3/jwt/openapi.yaml b/examples/jwt/spec/openapi.yaml similarity index 100% rename from examples/openapi3/jwt/openapi.yaml rename to examples/jwt/spec/openapi.yaml diff --git a/examples/openapi3/methodresolver/README.rst b/examples/methodresolver/README.rst similarity index 90% rename from examples/openapi3/methodresolver/README.rst rename to examples/methodresolver/README.rst index 41e82c8fa..13677f616 100644 --- a/examples/openapi3/methodresolver/README.rst +++ b/examples/methodresolver/README.rst @@ -6,6 +6,6 @@ Running: .. code-block:: bash - $ ./app.py + $ python app.py Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/openapi3/methodresolver/api/__init__.py b/examples/methodresolver/api/__init__.py similarity index 100% rename from examples/openapi3/methodresolver/api/__init__.py rename to examples/methodresolver/api/__init__.py diff --git a/examples/openapi3/methodresolver/api/petsview.py b/examples/methodresolver/api/petsview.py similarity index 100% rename from examples/openapi3/methodresolver/api/petsview.py rename to examples/methodresolver/api/petsview.py diff --git a/examples/methodresolver/app.py b/examples/methodresolver/app.py new file mode 100755 index 000000000..dc5a340d2 --- /dev/null +++ b/examples/methodresolver/app.py @@ -0,0 +1,43 @@ +import logging +from pathlib import Path + +import connexion +from connexion.resolver import MethodViewResolver + +logging.basicConfig(level=logging.INFO) + +zoo = { + 1: { + "id": 1, + "name": "giraffe", + "tags": ["africa", "yellow", "hoofs", "herbivore", "long neck"], + }, + 2: { + "id": 2, + "name": "lion", + "tags": ["africa", "yellow", "paws", "carnivore", "mane"], + }, +} + + +app = connexion.FlaskApp(__name__, specification_dir="spec/", debug=True) + +options = {"swagger_ui": True} +app.add_api( + "openapi.yaml", + options=options, + arguments={"title": "MethodViewResolver Example"}, + resolver=MethodViewResolver( + "api", + # class params are entirely optional + # they allow to inject dependencies top down + # so that the app can be wired, in the entrypoint + class_arguments={"PetsView": {"kwargs": {"pets": zoo}}}, + ), + strict_validation=True, + validate_responses=True, +) + + +if __name__ == "__main__": + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/methodresolver/openapi/pets-api.yaml b/examples/methodresolver/spec/openapi.yaml similarity index 99% rename from examples/openapi3/methodresolver/openapi/pets-api.yaml rename to examples/methodresolver/spec/openapi.yaml index bb834a9cd..138f5b0e6 100644 --- a/examples/openapi3/methodresolver/openapi/pets-api.yaml +++ b/examples/methodresolver/spec/openapi.yaml @@ -5,7 +5,7 @@ info: license: name: MIT servers: - - url: http://localhost:9090/v1.0 + - url: /openapi paths: /pets: get: diff --git a/examples/swagger2/oauth2/README.rst b/examples/oauth2/README.rst similarity index 56% rename from examples/swagger2/oauth2/README.rst rename to examples/oauth2/README.rst index 7ab5851f5..2803a63bc 100644 --- a/examples/swagger2/oauth2/README.rst +++ b/examples/oauth2/README.rst @@ -9,17 +9,17 @@ Running: .. code-block:: bash - $ sudo pip3 install --upgrade connexion # install Connexion from PyPI - $ ./mock_tokeninfo.py & # start mock in background - $ ./app.py + $ pip install --upgrade connexion # install Connexion from PyPI + $ python mock_tokeninfo.py & # start mock in background + $ python app.py -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. +Now open your browser and go to http://localhost:8080/openapi/ui/ to see the Swagger UI. You can use the hardcoded tokens to request the endpoint: .. code-block:: bash - $ curl http://localhost:8080/secret # missing authentication - $ curl -H 'Authorization: Bearer 123' http://localhost:8080/secret - $ curl -H 'Authorization: Bearer 456' http://localhost:8080/secret + $ curl http://localhost:8080/openapi/secret # missing authentication + $ curl -H 'Authorization: Bearer 123' http://localhost:8080/openapi/secret + $ curl -H 'Authorization: Bearer 456' http://localhost:8080/swagger/secret diff --git a/examples/oauth2/app.py b/examples/oauth2/app.py new file mode 100755 index 000000000..a327629a2 --- /dev/null +++ b/examples/oauth2/app.py @@ -0,0 +1,19 @@ +""" +Basic example of a resource server +""" +from pathlib import Path + +import connexion + + +def get_secret(user) -> str: + return f"You are: {user}" + + +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml") +app.add_api("swagger.yaml") + + +if __name__ == "__main__": + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/swagger2/oauth2/mock_tokeninfo.py b/examples/oauth2/mock_tokeninfo.py similarity index 82% rename from examples/swagger2/oauth2/mock_tokeninfo.py rename to examples/oauth2/mock_tokeninfo.py index 03484466f..8182aa554 100755 --- a/examples/swagger2/oauth2/mock_tokeninfo.py +++ b/examples/oauth2/mock_tokeninfo.py @@ -1,9 +1,9 @@ -#!/usr/bin/env python3 """ Mock OAuth2 token info """ import connexion +import uvicorn from connexion import request # our hardcoded mock "Bearer" access tokens @@ -25,6 +25,6 @@ def get_tokeninfo() -> dict: if __name__ == "__main__": - app = connexion.FlaskApp(__name__) + app = connexion.FlaskApp(__name__, specification_dir="spec") app.add_api("mock_tokeninfo.yaml") - app.run(port=7979) + uvicorn.run(app, port=7979) diff --git a/examples/swagger2/oauth2/mock_tokeninfo.yaml b/examples/oauth2/spec/mock_tokeninfo.yaml similarity index 100% rename from examples/swagger2/oauth2/mock_tokeninfo.yaml rename to examples/oauth2/spec/mock_tokeninfo.yaml diff --git a/examples/oauth2/spec/openapi.yaml b/examples/oauth2/spec/openapi.yaml new file mode 100644 index 000000000..1d07ea2e8 --- /dev/null +++ b/examples/oauth2/spec/openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.0 + +info: + title: OAuth Example + version: "1.0" + +servers: + - url: /openapi + +paths: + /secret: + get: + summary: Return secret string + operationId: app.get_secret + responses: + 200: + description: secret response + content: + 'text/plain': + schema: + type: string + security: + # enable authentication and require the "uid" scope for this endpoint + - oauth2: ['uid'] + +components: + securitySchemes: + oauth2: + type: oauth2 + x-tokenInfoUrl: http://localhost:7979/tokeninfo + flows: + implicit: + authorizationUrl: https://example.com/oauth2/dialog + # the token info URL is hardcoded for our mock_tokeninfo.py script + # you can also pass it as an environment variable TOKENINFO_URL + scopes: + uid: Unique identifier of the user accessing the service. diff --git a/examples/swagger2/oauth2/app.yaml b/examples/oauth2/spec/swagger.yaml similarity index 97% rename from examples/swagger2/oauth2/app.yaml rename to examples/oauth2/spec/swagger.yaml index 6d01a3a27..1354164b8 100644 --- a/examples/swagger2/oauth2/app.yaml +++ b/examples/oauth2/spec/swagger.yaml @@ -4,6 +4,8 @@ info: title: OAuth Example version: "1.0" +basePath: /swagger + paths: /secret: get: diff --git a/examples/swagger2/oauth2_local_tokeninfo/README.rst b/examples/oauth2_local_tokeninfo/README.rst similarity index 62% rename from examples/swagger2/oauth2_local_tokeninfo/README.rst rename to examples/oauth2_local_tokeninfo/README.rst index d1f4bcbdc..6880453db 100644 --- a/examples/swagger2/oauth2_local_tokeninfo/README.rst +++ b/examples/oauth2_local_tokeninfo/README.rst @@ -9,16 +9,16 @@ Running: .. code-block:: bash - $ sudo pip3 install --upgrade connexion # install Connexion from PyPI - $ ./app.py + $ pip install --upgrade connexion # install Connexion from PyPI + $ python app.py -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. +Now open your browser and go to http://localhost:8080/openapi/ui/ to see the Swagger UI. You can use the hardcoded tokens to request the endpoint: .. code-block:: bash - $ curl http://localhost:8080/secret # missing authentication - $ curl -H 'Authorization: Bearer 123' http://localhost:8080/secret - $ curl -H 'Authorization: Bearer 456' http://localhost:8080/secret + $ curl http://localhost:8080/openapi/secret # missing authentication + $ curl -H 'Authorization: Bearer 123' http://localhost:8080/openapi/secret + $ curl -H 'Authorization: Bearer 456' http://localhost:8080/swagger/secret diff --git a/examples/swagger2/oauth2_local_tokeninfo/app.py b/examples/oauth2_local_tokeninfo/app.py similarity index 66% rename from examples/swagger2/oauth2_local_tokeninfo/app.py rename to examples/oauth2_local_tokeninfo/app.py index 3c0568c7d..f12a29b89 100755 --- a/examples/swagger2/oauth2_local_tokeninfo/app.py +++ b/examples/oauth2_local_tokeninfo/app.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python3 """ Basic example of a resource server """ +from pathlib import Path import connexion @@ -20,7 +20,10 @@ def token_info(access_token) -> dict: return {"uid": uid, "scope": ["uid"]} +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api("openapi.yaml") +app.add_api("swagger.yaml") + + if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("app.yaml") - app.run(port=8080) + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/oauth2_local_tokeninfo/spec/openapi.yaml b/examples/oauth2_local_tokeninfo/spec/openapi.yaml new file mode 100644 index 000000000..aaa2b57a2 --- /dev/null +++ b/examples/oauth2_local_tokeninfo/spec/openapi.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.0 + +info: + title: OAuth Example + version: "1.0" + +servers: + - url: /openapi + +paths: + /secret: + get: + summary: Return secret string + operationId: app.get_secret + responses: + 200: + description: secret response + content: + text/plain: + schema: + type: string + security: + # enable authentication and require the "uid" scope for this endpoint + - oauth2: ['uid'] + +components: + securitySchemes: + oauth2: + type: oauth2 + x-tokenInfoFunc: app.token_info + flows: + implicit: + authorizationUrl: https://example.com/oauth2/dialog + scopes: + uid: Unique identifier of the user accessing the service. diff --git a/examples/swagger2/oauth2_local_tokeninfo/app.yaml b/examples/oauth2_local_tokeninfo/spec/swagger.yaml similarity index 96% rename from examples/swagger2/oauth2_local_tokeninfo/app.yaml rename to examples/oauth2_local_tokeninfo/spec/swagger.yaml index 67b45ddfd..9e0d2f6b1 100644 --- a/examples/swagger2/oauth2_local_tokeninfo/app.yaml +++ b/examples/oauth2_local_tokeninfo/spec/swagger.yaml @@ -4,6 +4,8 @@ info: title: OAuth Example version: "1.0" +basePath: /swagger + paths: /secret: get: diff --git a/examples/openapi3/apikey/README.rst b/examples/openapi3/apikey/README.rst deleted file mode 100644 index 299f36395..000000000 --- a/examples/openapi3/apikey/README.rst +++ /dev/null @@ -1,20 +0,0 @@ -======================= -API Key Example -======================= - -Running: - -.. code-block:: bash - - $ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI - $ ./app.py - -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. - -The hardcoded apikey is `asdf1234567890`. - -Test it out (in another terminal): - -.. code-block:: bash - - $ curl -H 'X-Auth: asdf1234567890' http://localhost:8080/secret diff --git a/examples/openapi3/basicauth/README.rst b/examples/openapi3/basicauth/README.rst deleted file mode 100644 index 8f6bb8f89..000000000 --- a/examples/openapi3/basicauth/README.rst +++ /dev/null @@ -1,14 +0,0 @@ -======================= -HTTP Basic Auth Example -======================= - -Running: - -.. code-block:: bash - - $ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI - $ ./app.py - -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. - -The hardcoded credentials are ``admin:secret`` and ``foo:bar``. diff --git a/examples/openapi3/helloworld/README.rst b/examples/openapi3/helloworld/README.rst deleted file mode 100644 index b154571d2..000000000 --- a/examples/openapi3/helloworld/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -=================== -Hello World Example -=================== - -Running: - -.. code-block:: bash - - $ ./hello.py - -Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/openapi3/helloworld/hello.py b/examples/openapi3/helloworld/hello.py deleted file mode 100755 index 1d3a7cd2c..000000000 --- a/examples/openapi3/helloworld/hello.py +++ /dev/null @@ -1,11 +0,0 @@ -import connexion - - -def post_greeting(name: str) -> str: - return f"Hello {name}" - - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__, port=9090, specification_dir="openapi/") - app.add_api("helloworld-api.yaml", arguments={"title": "Hello World Example"}) - app.run() diff --git a/examples/openapi3/helloworld_async/README.rst b/examples/openapi3/helloworld_async/README.rst deleted file mode 100644 index 0f02c841b..000000000 --- a/examples/openapi3/helloworld_async/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -=================== -Hello World Example -=================== - -Running: - -.. code-block:: bash - - $ uvicorn hello:app - -Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/openapi3/helloworld_async/hello.py b/examples/openapi3/helloworld_async/hello.py deleted file mode 100755 index 2be91c364..000000000 --- a/examples/openapi3/helloworld_async/hello.py +++ /dev/null @@ -1,15 +0,0 @@ -import connexion -from starlette.responses import PlainTextResponse - - -async def test(): - pass - - -async def post_greeting(name: str) -> PlainTextResponse: - await test() - return f"Hello {name}", 201 - - -app = connexion.AsyncApp(__name__, port=9090, specification_dir="openapi/") -app.add_api("helloworld-api.yaml", arguments={"title": "Hello World Example"}) diff --git a/examples/openapi3/jwt/requirements.txt b/examples/openapi3/jwt/requirements.txt deleted file mode 100644 index a112b1d19..000000000 --- a/examples/openapi3/jwt/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Install swagger-ui before connexion. -connexion[swagger-ui] - -connexion>=2.2.0 -python-jose[cryptography] -Flask>=0.10.1 diff --git a/examples/openapi3/jwt/tox.ini b/examples/openapi3/jwt/tox.ini deleted file mode 100644 index dd63e6777..000000000 --- a/examples/openapi3/jwt/tox.ini +++ /dev/null @@ -1,14 +0,0 @@ -# -# Run all project with: -# -# tox -# -[tox] -envlist = py3 -skipsdist = true - -[testenv] -deps = -rrequirements.txt - -commands = - python app.py diff --git a/examples/openapi3/methodresolver/app.py b/examples/openapi3/methodresolver/app.py deleted file mode 100755 index 2886bcfbb..000000000 --- a/examples/openapi3/methodresolver/app.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -import logging - -import connexion -from connexion.resolver import MethodViewResolver - -logging.basicConfig(level=logging.INFO) - -zoo = { - 1: { - "id": 1, - "name": "giraffe", - "tags": ["africa", "yellow", "hoofs", "herbivore", "long neck"], - }, - 2: { - "id": 2, - "name": "lion", - "tags": ["africa", "yellow", "paws", "carnivore", "mane"], - }, -} - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__, specification_dir="openapi/", debug=True) - - options = {"swagger_ui": True} - app.add_api( - "pets-api.yaml", - options=options, - arguments={"title": "MethodViewResolver Example"}, - resolver=MethodViewResolver( - "api", - # class params are entirely optional - # they allow to inject dependencies top down - # so that the app can be wired, in the entrypoint - class_arguments={"PetsView": {"kwargs": {"pets": zoo}}}, - ), - strict_validation=True, - validate_responses=True, - ) - app.run(port=9090, debug=True) diff --git a/examples/openapi3/restyresolver/README.rst b/examples/openapi3/restyresolver/README.rst deleted file mode 100644 index b0f06625e..000000000 --- a/examples/openapi3/restyresolver/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -===================== -RestyResolver Example -===================== - -Running: - -.. code-block:: bash - - $ ./resty.py - -Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/openapi3/restyresolver/resty.py b/examples/openapi3/restyresolver/resty.py deleted file mode 100755 index 082994f7c..000000000 --- a/examples/openapi3/restyresolver/resty.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -import logging - -import connexion -from connexion.resolver import RestyResolver - -logging.basicConfig(level=logging.INFO) - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api( - "resty-api.yaml", - arguments={"title": "RestyResolver Example"}, - resolver=RestyResolver("api"), - ) - app.run(port=9090) diff --git a/examples/openapi3/reverseproxy/README.rst b/examples/openapi3/reverseproxy/README.rst deleted file mode 100644 index 0d1f8350f..000000000 --- a/examples/openapi3/reverseproxy/README.rst +++ /dev/null @@ -1,58 +0,0 @@ -===================== -Reverse Proxy Example -===================== - -This example demonstrates how to run a connexion application behind a path-altering reverse proxy. - -You can either set the path in your app, or set the ``X-Forwarded-Path`` header. - -Running: - -.. code-block:: bash - - $ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI - $ ./app.py - -Now open your browser and go to http://localhost:8080/reverse_proxied/ui/ to see the Swagger UI. - - -You can also use the ``X-Forwarded-Path`` header to modify the reverse proxy path. -For example: - -.. code-block:: bash - - curl -H "X-Forwarded-Path: /banana/" http://localhost:8080/openapi.json - - { - "servers" : [ - { - "url" : "banana/" - } - ], - "paths" : { - "/hello" : { - "get" : { - "responses" : { - "200" : { - "description" : "hello", - "content" : { - "text/plain" : { - "schema" : { - "type" : "string" - } - } - } - } - }, - "operationId" : "app.hello", - "summary" : "say hi" - } - } - }, - "openapi" : "3.0.0", - "info" : { - "version" : "1.0", - "title" : "Path-Altering Reverse Proxy Example" - } - } - diff --git a/examples/openapi3/reverseproxy/app.py b/examples/openapi3/reverseproxy/app.py deleted file mode 100755 index a6aaffe3a..000000000 --- a/examples/openapi3/reverseproxy/app.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -""" -example of connexion running behind a path-altering reverse-proxy - -NOTE this demo is not secure by default!! -You'll want to make sure these headers are coming from your proxy, and not -directly from users on the web! - -""" -import logging - -import connexion - - -# adapted from http://flask.pocoo.org/snippets/35/ -class ReverseProxied: - """Wrap the application in this middleware and configure the - reverse proxy to add these headers, to let you quietly bind - this to a URL other than / and to an HTTP scheme that is - different than what is used locally. - - In nginx: - - location /proxied { - proxy_pass http://192.168.0.1:5001; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Scheme $scheme; - proxy_set_header X-Forwarded-Path /proxied; - } - - :param app: the WSGI application - :param script_name: override the default script name (path) - :param scheme: override the default scheme - :param server: override the default server - """ - - def __init__(self, app, script_name=None, scheme=None, server=None): - self.app = app - self.script_name = script_name - self.scheme = scheme - self.server = server - - def __call__(self, environ, start_response): - logging.warning( - "this demo is not secure by default!! " - "You'll want to make sure these headers are coming from your proxy, " - "and not directly from users on the web!" - ) - script_name = environ.get("HTTP_X_FORWARDED_PATH", "") or self.script_name - if script_name: - environ["SCRIPT_NAME"] = "/" + script_name.lstrip("/") - path_info = environ["PATH_INFO"] - if path_info.startswith(script_name): - environ["PATH_INFO_OLD"] = path_info - environ["PATH_INFO"] = path_info[len(script_name) :] - scheme = environ.get("HTTP_X_SCHEME", "") or self.scheme - if scheme: - environ["wsgi.url_scheme"] = scheme - server = environ.get("HTTP_X_FORWARDED_SERVER", "") or self.server - if server: - environ["HTTP_HOST"] = server - return self.app(environ, start_response) - - -def hello(): - return "hello" - - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("openapi.yaml") - flask_app = app.app - proxied = ReverseProxied(flask_app.wsgi_app, script_name="/reverse_proxied/") - flask_app.wsgi_app = proxied - flask_app.run(port=8080) diff --git a/examples/openapi3/sqlalchemy/README.rst b/examples/openapi3/sqlalchemy/README.rst deleted file mode 100644 index 24dd174bd..000000000 --- a/examples/openapi3/sqlalchemy/README.rst +++ /dev/null @@ -1,15 +0,0 @@ -================== -SQLAlchemy Example -================== - -A simple example of how one might use SQLAlchemy as a backing store for a -Connexion based application. - -Running: - -.. code-block:: bash - - $ sudo pip3 install -r requirements.txt - $ ./app.py - -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. diff --git a/examples/restyresolver/README.rst b/examples/restyresolver/README.rst new file mode 100644 index 000000000..5930326a5 --- /dev/null +++ b/examples/restyresolver/README.rst @@ -0,0 +1,12 @@ +===================== +RestyResolver Example +===================== + +Running: + +.. code-block:: bash + + $ python resty.py + +Now open your browser and go to http://localhost:8080/openapi/ui/ or +http://localhost:8080/swagger/ui/ to see the Swagger UI. diff --git a/examples/openapi3/restyresolver/api/__init__.py b/examples/restyresolver/api/__init__.py similarity index 100% rename from examples/openapi3/restyresolver/api/__init__.py rename to examples/restyresolver/api/__init__.py diff --git a/examples/openapi3/restyresolver/api/pets.py b/examples/restyresolver/api/pets.py similarity index 100% rename from examples/openapi3/restyresolver/api/pets.py rename to examples/restyresolver/api/pets.py diff --git a/examples/restyresolver/resty.py b/examples/restyresolver/resty.py new file mode 100755 index 000000000..8f00bef1a --- /dev/null +++ b/examples/restyresolver/resty.py @@ -0,0 +1,23 @@ +import logging +from pathlib import Path + +import connexion +from connexion.resolver import RestyResolver + +logging.basicConfig(level=logging.INFO) + +app = connexion.FlaskApp(__name__, specification_dir="spec") +app.add_api( + "openapi.yaml", + arguments={"title": "RestyResolver Example"}, + resolver=RestyResolver("api"), +) +app.add_api( + "swagger.yaml", + arguments={"title": "RestyResolver Example"}, + resolver=RestyResolver("api"), +) + + +if __name__ == "__main__": + app.run(f"{Path(__file__).stem}:app", port=8080) diff --git a/examples/openapi3/restyresolver/resty-api.yaml b/examples/restyresolver/spec/openapi.yaml similarity index 98% rename from examples/openapi3/restyresolver/resty-api.yaml rename to examples/restyresolver/spec/openapi.yaml index b948a25f0..b11194684 100644 --- a/examples/openapi3/restyresolver/resty-api.yaml +++ b/examples/restyresolver/spec/openapi.yaml @@ -5,7 +5,7 @@ info: license: name: MIT servers: - - url: http://localhost:9090/v1.0 + - url: /openapi paths: /pets: get: diff --git a/examples/swagger2/restyresolver/resty-api.yaml b/examples/restyresolver/spec/swagger.yaml similarity index 98% rename from examples/swagger2/restyresolver/resty-api.yaml rename to examples/restyresolver/spec/swagger.yaml index 7911bd9fb..c24cf0e75 100644 --- a/examples/swagger2/restyresolver/resty-api.yaml +++ b/examples/restyresolver/spec/swagger.yaml @@ -4,7 +4,7 @@ info: title: "{{title}}" version: "1.0" -basePath: /v1.0 +basePath: /swagger paths: /pets: diff --git a/examples/reverseproxy/README.rst b/examples/reverseproxy/README.rst new file mode 100644 index 000000000..2f5453ed8 --- /dev/null +++ b/examples/reverseproxy/README.rst @@ -0,0 +1,88 @@ +===================== +Reverse Proxy Example +===================== + +This example demonstrates how to run a connexion application behind a path-altering reverse proxy. + +You can set the path in three ways: + +- Via the Middleware +.. code-block:: + + app = ReverseProxied(app, root_path="/reverse_proxied/") + +- Via the ASGI server +.. code-block:: + + uvicorn ... --root_path="/reverse_proxied/" + +- By using the ``X-Forwarded-Path`` header in your proxy server. Eg in nginx: +.. code-block:: + + location /proxied { + proxy_pass http://192.168.0.1:5001; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Path /proxied; + } + +To run this example, install Connexion from PyPI: + +.. code-block:: + + $ pip install --upgrade connexion[swagger-ui] + +and then run it either directly +.. code-block:: + + $ python app.py + +or using uvicorn (or another async server): +.. code-block:: + $ uvicorn --factory app:create_app --port 8080 + +If your proxy server is running at http://localhost:8080/revers_proxied/, you can go to +http://localhost:8080/reverse_proxied/openapi/ui/ to see the Swagger UI. + + +Or you can test this using the ``X-Forwarded-Path`` header to modify the reverse proxy path. +For example, note the servers block: + +.. code-block:: bash + + curl -H "X-Forwarded-Path: /banana/" http://localhost:8080/openapi/openapi.json + + { + "servers" : [ + { + "url" : "/banana/openapi" + } + ], + "paths" : { + "/hello" : { + "get" : { + "responses" : { + "200" : { + "description" : "hello", + "content" : { + "text/plain" : { + "schema" : { + "type" : "string" + } + } + } + } + }, + "operationId" : "app.hello", + "summary" : "say hi" + } + } + }, + "openapi" : "3.0.0", + "info" : { + "version" : "1.0", + "title" : "Path-Altering Reverse Proxy Example" + } + } + diff --git a/examples/reverseproxy/app.py b/examples/reverseproxy/app.py new file mode 100755 index 000000000..829f77221 --- /dev/null +++ b/examples/reverseproxy/app.py @@ -0,0 +1,83 @@ +""" +example of connexion running behind a path-altering reverse-proxy + +NOTE this demo is not secure by default!! +You'll want to make sure these headers are coming from your proxy, and not +directly from users on the web! + +""" +import logging +from pathlib import Path + +import connexion +import uvicorn +from starlette.types import Receive, Scope, Send + + +class ReverseProxied: + """Wrap the application in this middleware and configure the + reverse proxy to add these headers, to let you quietly bind + this to a URL other than / and to an HTTP scheme that is + different than what is used locally. + + In nginx: + + location /proxied { + proxy_pass http://192.168.0.1:5001; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Path /proxied; + } + + :param app: the WSGI application + :param root_path: override the default script name (path) + :param scheme: override the default scheme + :param server: override the default server + """ + + def __init__(self, app, root_path=None, scheme=None, server=None): + self.app = app + self.root_path = root_path + self.scheme = scheme + self.server = server + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + logging.warning( + "this demo is not secure by default!! " + "You'll want to make sure these headers are coming from your proxy, " + "and not directly from users on the web!" + ) + root_path = scope.get("root_path") or self.root_path + for header, value in scope.get("headers", []): + if header == b"x-forwarded-path": + root_path = value.decode() + break + if root_path: + scope["root_path"] = "/" + root_path.strip("/") + path_info = scope.get("PATH_INFO", scope.get("path")) + if path_info.startswith(root_path): + scope["PATH_INFO"] = path_info[len(root_path) :] + + scope["scheme"] = scope.get("scheme") or self.scheme + scope["server"] = scope.get("server") or (self.server, None) + + return await self.app(scope, receive, send) + + +def hello(): + return "hello" + + +def create_app(): + app = connexion.FlaskApp(__name__, specification_dir="spec") + app.add_api("openapi.yaml") + app.add_api("swagger.yaml") + app = ReverseProxied(app, root_path="/reverse_proxied/") + return app + + +if __name__ == "__main__": + uvicorn.run( + f"{Path(__file__).stem}:create_app", factory=True, port=8080, proxy_headers=True + ) diff --git a/examples/openapi3/reverseproxy/openapi.yaml b/examples/reverseproxy/spec/openapi.yaml similarity index 92% rename from examples/openapi3/reverseproxy/openapi.yaml rename to examples/reverseproxy/spec/openapi.yaml index 90a825af1..0c12854b2 100644 --- a/examples/openapi3/reverseproxy/openapi.yaml +++ b/examples/reverseproxy/spec/openapi.yaml @@ -2,6 +2,8 @@ openapi: 3.0.0 info: title: Path-Altering Reverse Proxy Example version: '1.0' +servers: + - url: /openapi paths: /hello: get: diff --git a/examples/reverseproxy/spec/swagger.yaml b/examples/reverseproxy/spec/swagger.yaml new file mode 100644 index 000000000..11633a04b --- /dev/null +++ b/examples/reverseproxy/spec/swagger.yaml @@ -0,0 +1,15 @@ +swagger: "2.0" +info: + title: Path-Altering Reverse Proxy Example + version: '1.0' +basePath: /swagger +paths: + /hello: + get: + summary: say hi + operationId: app.hello + responses: + '200': + description: hello + schema: + type: string diff --git a/examples/swagger2/sqlalchemy/README.rst b/examples/sqlalchemy/README.rst similarity index 82% rename from examples/swagger2/sqlalchemy/README.rst rename to examples/sqlalchemy/README.rst index 24dd174bd..2e2f04820 100644 --- a/examples/swagger2/sqlalchemy/README.rst +++ b/examples/sqlalchemy/README.rst @@ -9,7 +9,7 @@ Running: .. code-block:: bash - $ sudo pip3 install -r requirements.txt - $ ./app.py + $ pip install -r requirements.txt + $ python app.py Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. diff --git a/examples/openapi3/sqlalchemy/app.py b/examples/sqlalchemy/app.py similarity index 92% rename from examples/openapi3/sqlalchemy/app.py rename to examples/sqlalchemy/app.py index dfbf23818..04870d96d 100755 --- a/examples/openapi3/sqlalchemy/app.py +++ b/examples/sqlalchemy/app.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import datetime import logging @@ -48,8 +47,9 @@ def delete_pet(pet_id): logging.basicConfig(level=logging.INFO) db_session = orm.init_db("sqlite:///:memory:") -app = connexion.FlaskApp(__name__) +app = connexion.FlaskApp(__name__, specification_dir="spec") app.add_api("openapi.yaml") +app.add_api("swagger.yaml") application = app.app @@ -60,4 +60,4 @@ def shutdown_session(exception=None): if __name__ == "__main__": - app.run(port=8081, use_reloader=False, threaded=False) + app.run(port=8080, reload=False) diff --git a/examples/openapi3/sqlalchemy/orm.py b/examples/sqlalchemy/orm.py similarity index 100% rename from examples/openapi3/sqlalchemy/orm.py rename to examples/sqlalchemy/orm.py diff --git a/examples/openapi3/sqlalchemy/requirements.txt b/examples/sqlalchemy/requirements.txt similarity index 100% rename from examples/openapi3/sqlalchemy/requirements.txt rename to examples/sqlalchemy/requirements.txt diff --git a/examples/openapi3/sqlalchemy/openapi.yaml b/examples/sqlalchemy/spec/openapi.yaml similarity index 98% rename from examples/openapi3/sqlalchemy/openapi.yaml rename to examples/sqlalchemy/spec/openapi.yaml index 3b683df29..c346f1846 100644 --- a/examples/openapi3/sqlalchemy/openapi.yaml +++ b/examples/sqlalchemy/spec/openapi.yaml @@ -1,6 +1,6 @@ openapi: 3.0.0 servers: - - url: http://localhost:8081/ + - url: /openapi info: title: Pet Shop Example API version: '0.1' diff --git a/examples/swagger2/sqlalchemy/swagger.yaml b/examples/sqlalchemy/spec/swagger.yaml similarity index 99% rename from examples/swagger2/sqlalchemy/swagger.yaml rename to examples/sqlalchemy/spec/swagger.yaml index 65b40a212..147b34bb9 100644 --- a/examples/swagger2/sqlalchemy/swagger.yaml +++ b/examples/sqlalchemy/spec/swagger.yaml @@ -2,6 +2,7 @@ swagger: '2.0' info: title: Pet Shop Example API version: "0.1" +basePath: /swagger consumes: - application/json produces: diff --git a/examples/swagger2/basicauth/README.rst b/examples/swagger2/basicauth/README.rst deleted file mode 100644 index 8f6bb8f89..000000000 --- a/examples/swagger2/basicauth/README.rst +++ /dev/null @@ -1,14 +0,0 @@ -======================= -HTTP Basic Auth Example -======================= - -Running: - -.. code-block:: bash - - $ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI - $ ./app.py - -Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI. - -The hardcoded credentials are ``admin:secret`` and ``foo:bar``. diff --git a/examples/swagger2/basicauth/app.py b/examples/swagger2/basicauth/app.py deleted file mode 100755 index f1163780c..000000000 --- a/examples/swagger2/basicauth/app.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -""" -Basic example of a resource server -""" - -import connexion - -PASSWD = {"admin": "secret", "foo": "bar"} - - -def basic_auth(username, password): - if PASSWD.get(username) == password: - return {"sub": username} - # optional: raise exception for custom error response - return None - - -def get_secret(user) -> str: - return f"You are {user} and the secret is 'wbevuec'" - - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("swagger.yaml") - app.run(port=8080) diff --git a/examples/swagger2/helloworld/README.rst b/examples/swagger2/helloworld/README.rst deleted file mode 100644 index b154571d2..000000000 --- a/examples/swagger2/helloworld/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -=================== -Hello World Example -=================== - -Running: - -.. code-block:: bash - - $ ./hello.py - -Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/swagger2/helloworld/hello.py b/examples/swagger2/helloworld/hello.py deleted file mode 100755 index c5fc1260d..000000000 --- a/examples/swagger2/helloworld/hello.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 - -import connexion - - -def post_greeting(name: str) -> str: - return f"Hello {name}" - - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__, port=9090, specification_dir="swagger/") - app.add_api("helloworld-api.yaml", arguments={"title": "Hello World Example"}) - app.run() diff --git a/examples/swagger2/mock/swagger.yaml b/examples/swagger2/mock/swagger.yaml deleted file mode 100644 index 013c9f415..000000000 --- a/examples/swagger2/mock/swagger.yaml +++ /dev/null @@ -1,116 +0,0 @@ -swagger: '2.0' -info: - title: Pet Shop Example API - version: "0.1" -consumes: - - application/json -produces: - - application/json -paths: - /pets: - get: - tags: [Pets] - summary: Get all pets - parameters: - - name: animal_type - in: query - type: string - pattern: "^[a-zA-Z0-9]*$" - - name: limit - in: query - type: integer - minimum: 0 - default: 100 - responses: - 200: - description: Return pets - schema: - type: array - items: - $ref: '#/definitions/Pet' - examples: - application/json: - - id: 1 - name: Susie - animal_type: cat - - /pets/{pet_id}: - get: - tags: [Pets] - summary: Get a single pet - parameters: - - $ref: '#/parameters/pet_id' - responses: - 200: - description: Return pet - schema: - $ref: '#/definitions/Pet' - 404: - description: Pet does not exist - put: - tags: [Pets] - summary: Create or update a pet - parameters: - - $ref: '#/parameters/pet_id' - - name: pet - in: body - schema: - $ref: '#/definitions/Pet' - responses: - 200: - description: Pet updated - 201: - description: New pet created - delete: - tags: [Pets] - summary: Remove a pet - parameters: - - $ref: '#/parameters/pet_id' - responses: - 204: - description: Pet was deleted - 404: - description: Pet does not exist - - -parameters: - pet_id: - name: pet_id - description: Pet's Unique identifier - in: path - type: string - required: true - pattern: "^[a-zA-Z0-9-]+$" - -definitions: - Pet: - type: object - required: - - name - - animal_type - properties: - id: - type: string - description: Unique identifier - example: "123" - readOnly: true - name: - type: string - description: Pet's name - example: "Susie" - minLength: 1 - maxLength: 100 - animal_type: - type: string - description: Kind of animal - example: "cat" - minLength: 1 - tags: - type: object - description: Custom tags - created: - type: string - format: date-time - description: Creation time - example: "2015-07-07T15:49:51.230+02:00" - readOnly: true diff --git a/examples/swagger2/oauth2/app.py b/examples/swagger2/oauth2/app.py deleted file mode 100755 index 745061645..000000000 --- a/examples/swagger2/oauth2/app.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -""" -Basic example of a resource server -""" - -import connexion - - -def get_secret(user) -> str: - return f"You are: {user}" - - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api("app.yaml") - app.run(port=8080) diff --git a/examples/swagger2/restyresolver/README.rst b/examples/swagger2/restyresolver/README.rst deleted file mode 100644 index b0f06625e..000000000 --- a/examples/swagger2/restyresolver/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -===================== -RestyResolver Example -===================== - -Running: - -.. code-block:: bash - - $ ./resty.py - -Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI. diff --git a/examples/swagger2/restyresolver/api/__init__.py b/examples/swagger2/restyresolver/api/__init__.py deleted file mode 100644 index 6a0ecd676..000000000 --- a/examples/swagger2/restyresolver/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import api.pets # noqa diff --git a/examples/swagger2/restyresolver/api/pets.py b/examples/swagger2/restyresolver/api/pets.py deleted file mode 100644 index 3a6bc23fe..000000000 --- a/examples/swagger2/restyresolver/api/pets.py +++ /dev/null @@ -1,43 +0,0 @@ -import datetime - -from connexion import NoContent - -pets = {} - - -def post(pet): - count = len(pets) - pet["id"] = count + 1 - pet["registered"] = datetime.datetime.now() - pets[pet["id"]] = pet - return pet, 201 - - -def put(id, pet): - id = int(id) - if pets.get(id) is None: - return NoContent, 404 - pets[id] = pet - - return pets[id] - - -def delete(id): - id = int(id) - if pets.get(id) is None: - return NoContent, 404 - del pets[id] - return NoContent, 204 - - -def get(id): - id = int(id) - if pets.get(id) is None: - return NoContent, 404 - - return pets[id] - - -def search(): - # NOTE: we need to wrap it with list for Python 3 as dict_values is not JSON serializable - return list(pets.values()) diff --git a/examples/swagger2/restyresolver/resty.py b/examples/swagger2/restyresolver/resty.py deleted file mode 100755 index 082994f7c..000000000 --- a/examples/swagger2/restyresolver/resty.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -import logging - -import connexion -from connexion.resolver import RestyResolver - -logging.basicConfig(level=logging.INFO) - -if __name__ == "__main__": - app = connexion.FlaskApp(__name__) - app.add_api( - "resty-api.yaml", - arguments={"title": "RestyResolver Example"}, - resolver=RestyResolver("api"), - ) - app.run(port=9090) diff --git a/examples/swagger2/sqlalchemy/app.py b/examples/swagger2/sqlalchemy/app.py deleted file mode 100755 index e07e50db7..000000000 --- a/examples/swagger2/sqlalchemy/app.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -import datetime -import logging - -import connexion -import orm -from connexion import NoContent - -db_session = None - - -def get_pets(limit, animal_type=None): - q = db_session.query(orm.Pet) - if animal_type: - q = q.filter(orm.Pet.animal_type == animal_type) - return [p.dump() for p in q][:limit] - - -def get_pet(pet_id): - pet = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none() - return pet.dump() if pet is not None else ("Not found", 404) - - -def put_pet(pet_id, pet): - p = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none() - pet["id"] = pet_id - if p is not None: - logging.info("Updating pet %s..", pet_id) - p.update(**pet) - else: - logging.info("Creating pet %s..", pet_id) - pet["created"] = datetime.datetime.utcnow() - db_session.add(orm.Pet(**pet)) - db_session.commit() - return NoContent, (200 if p is not None else 201) - - -def delete_pet(pet_id): - pet = db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).one_or_none() - if pet is not None: - logging.info("Deleting pet %s..", pet_id) - db_session.query(orm.Pet).filter(orm.Pet.id == pet_id).delete() - db_session.commit() - return NoContent, 204 - else: - return NoContent, 404 - - -logging.basicConfig(level=logging.INFO) -db_session = orm.init_db("sqlite:///:memory:") -app = connexion.FlaskApp(__name__) -app.add_api("swagger.yaml") - -application = app.app - - -@application.teardown_appcontext -def shutdown_session(exception=None): - db_session.remove() - - -if __name__ == "__main__": - app.run(port=8080, threaded=False) # in-memory database isn't shared across threads diff --git a/examples/swagger2/sqlalchemy/orm.py b/examples/swagger2/sqlalchemy/orm.py deleted file mode 100644 index 52c4c6f79..000000000 --- a/examples/swagger2/sqlalchemy/orm.py +++ /dev/null @@ -1,34 +0,0 @@ -from sqlalchemy import Column, DateTime, String, create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session, sessionmaker - -Base = declarative_base() - - -class Pet(Base): - __tablename__ = "pets" - id = Column(String(20), primary_key=True) - name = Column(String(100)) - animal_type = Column(String(20)) - created = Column(DateTime()) - - def update(self, id=None, name=None, animal_type=None, tags=None, created=None): - if name is not None: - self.name = name - if animal_type is not None: - self.animal_type = animal_type - if created is not None: - self.created = created - - def dump(self): - return {k: v for k, v in vars(self).items() if not k.startswith("_")} - - -def init_db(uri): - engine = create_engine(uri, convert_unicode=True) - db_session = scoped_session( - sessionmaker(autocommit=False, autoflush=False, bind=engine) - ) - Base.query = db_session.query_property() - Base.metadata.create_all(bind=engine) - return db_session diff --git a/examples/swagger2/sqlalchemy/requirements.txt b/examples/swagger2/sqlalchemy/requirements.txt deleted file mode 100644 index 37d94281f..000000000 --- a/examples/swagger2/sqlalchemy/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -connexion>=1.0.97 -Flask>=0.10.1 -SQLAlchemy>=1.0.13 \ No newline at end of file diff --git a/setup.py b/setup.py index c87444452..b19c2ce08 100755 --- a/setup.py +++ b/setup.py @@ -52,6 +52,10 @@ def read_version(package): 'sphinx-autoapi==1.8.1' ] +uvicorn_requires = [ + 'uvicorn[standard]>=0.17.6' +] + class PyTest(TestCommand): @@ -100,7 +104,8 @@ def readme(): 'tests': tests_require, 'flask': flask_require, 'swagger-ui': swagger_ui_require, - 'docs': docs_require + 'docs': docs_require, + 'uvicorn': uvicorn_requires, }, cmdclass={'test': PyTest}, test_suite='tests', diff --git a/tests/api/test_bootstrap.py b/tests/api/test_bootstrap.py index d71755fe7..50b8d7d7b 100644 --- a/tests/api/test_bootstrap.py +++ b/tests/api/test_bootstrap.py @@ -19,9 +19,7 @@ def test_app_with_relative_path(simple_api_spec_dir, spec): # Create the app with a relative path and run the test_app testcase below. app = App( __name__, - port=5001, specification_dir=".." / simple_api_spec_dir.relative_to(TEST_FOLDER), - debug=True, ) app.add_api(spec) @@ -38,7 +36,6 @@ def test_app_with_resolver(simple_api_spec_dir, spec): resolver = Resolver() app = App( __name__, - port=5001, specification_dir=".." / simple_api_spec_dir.relative_to(TEST_FOLDER), resolver=resolver, ) @@ -46,33 +43,13 @@ def test_app_with_resolver(simple_api_spec_dir, spec): assert api.resolver is resolver -@pytest.mark.parametrize("spec", SPECS) -def test_app_with_different_server_option(simple_api_spec_dir, spec): - # Create the app with a relative path and run the test_app testcase below. - app = App( - __name__, - port=5001, - server="gevent", - specification_dir=".." / simple_api_spec_dir.relative_to(TEST_FOLDER), - debug=True, - ) - app.add_api(spec) - - app_client = app.app.test_client() - get_bye = app_client.get("/v1.0/bye/jsantos") # type: flask.Response - assert get_bye.status_code == 200 - assert get_bye.data == b"Goodbye jsantos" - - def test_app_with_different_uri_parser(simple_api_spec_dir): from connexion.uri_parsing import FirstValueURIParser app = App( __name__, - port=5001, specification_dir=".." / simple_api_spec_dir.relative_to(TEST_FOLDER), options={"uri_parser_class": FirstValueURIParser}, - debug=True, ) app.add_api("swagger.yaml") @@ -87,7 +64,7 @@ def test_app_with_different_uri_parser(simple_api_spec_dir): @pytest.mark.parametrize("spec", SPECS) def test_swagger_ui(simple_api_spec_dir, spec): - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec) app_client = app.app.test_client() swagger_ui = app_client.get("/v1.0/ui/") # type: flask.Response @@ -104,10 +81,8 @@ def test_swagger_ui_with_config(simple_api_spec_dir, spec): options = {"swagger_ui_config": swagger_ui_config} app = App( __name__, - port=5001, specification_dir=simple_api_spec_dir, options=options, - debug=True, ) app.add_api(spec) app_client = app.app.test_client() @@ -122,10 +97,8 @@ def test_no_swagger_ui(simple_api_spec_dir, spec): options = {"swagger_ui": False} app = App( __name__, - port=5001, specification_dir=simple_api_spec_dir, options=options, - debug=True, ) app.add_api(spec) @@ -133,7 +106,7 @@ def test_no_swagger_ui(simple_api_spec_dir, spec): swagger_ui = app_client.get("/v1.0/ui/") # type: flask.Response assert swagger_ui.status_code == 404 - app2 = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app2 = App(__name__, specification_dir=simple_api_spec_dir) app2.add_api(spec, options={"swagger_ui": False}) app2_client = app2.app.test_client() swagger_ui2 = app2_client.get("/v1.0/ui/") # type: flask.Response @@ -147,10 +120,8 @@ def test_swagger_ui_config_json(simple_api_spec_dir, spec): options = {"swagger_ui_config": swagger_ui_config} app = App( __name__, - port=5001, specification_dir=simple_api_spec_dir, options=options, - debug=True, ) app.add_api(spec) app_client = app.app.test_client() @@ -165,7 +136,7 @@ def test_swagger_ui_config_json(simple_api_spec_dir, spec): @pytest.mark.parametrize("spec", SPECS) def test_no_swagger_ui_config_json(simple_api_spec_dir, spec): """Verify the swagger-ui-config.json file is not returned when the swagger_ui_config option not passed to app.""" - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec) app_client = app.app.test_client() url = "/v1.0/ui/swagger-ui-config.json" @@ -176,7 +147,7 @@ def test_no_swagger_ui_config_json(simple_api_spec_dir, spec): @pytest.mark.parametrize("spec", SPECS) def test_swagger_json_app(simple_api_spec_dir, spec): """Verify the spec json file is returned for default setting passed to app.""" - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec) app_client = app.app.test_client() url = "/v1.0/{spec}" @@ -188,7 +159,7 @@ def test_swagger_json_app(simple_api_spec_dir, spec): @pytest.mark.parametrize("spec", SPECS) def test_swagger_yaml_app(simple_api_spec_dir, spec): """Verify the spec yaml file is returned for default setting passed to app.""" - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec) app_client = app.app.test_client() url = "/v1.0/{spec}" @@ -203,10 +174,8 @@ def test_no_swagger_json_app(simple_api_spec_dir, spec): options = {"serve_spec": False} app = App( __name__, - port=5001, specification_dir=simple_api_spec_dir, options=options, - debug=True, ) app.add_api(spec) @@ -231,7 +200,7 @@ def test_dict_as_yaml_path(simple_api_spec_dir, spec): openapi_string = jinja2.Template(openapi_template).render({}) specification = yaml.load(openapi_string, ExtendedSafeLoader) # type: dict - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(specification) app_client = app.app.test_client() @@ -243,7 +212,7 @@ def test_dict_as_yaml_path(simple_api_spec_dir, spec): @pytest.mark.parametrize("spec", SPECS) def test_swagger_json_api(simple_api_spec_dir, spec): """Verify the spec json file is returned for default setting passed to api.""" - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec) app_client = app.app.test_client() @@ -255,7 +224,7 @@ def test_swagger_json_api(simple_api_spec_dir, spec): @pytest.mark.parametrize("spec", SPECS) def test_no_swagger_json_api(simple_api_spec_dir, spec): """Verify the spec json file is not returned when set to False when adding api.""" - app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True) + app = App(__name__, specification_dir=simple_api_spec_dir) app.add_api(spec, options={"serve_spec": False}) app_client = app.app.test_client() @@ -321,20 +290,7 @@ def test_add_api_with_function_resolver_function_is_wrapped(simple_api_spec_dir, def test_default_query_param_does_not_match_defined_type(default_param_error_spec_dir): with pytest.raises(InvalidSpecification): - build_app_from_fixture( - default_param_error_spec_dir, validate_responses=True, debug=False - ) - - -def test_handle_add_operation_error_debug(simple_api_spec_dir): - app = App(__name__, specification_dir=simple_api_spec_dir, debug=True) - app.api_cls = type("AppTest", (app.api_cls,), {}) - app.api_cls.add_operation = mock.MagicMock( - side_effect=Exception("operation error!") - ) - api = app.add_api("swagger.yaml", resolver=lambda oid: (lambda foo: "bar")) - assert app.api_cls.add_operation.called - assert api.resolver.resolve_function_from_operation_id("faux")("bah") == "bar" + build_app_from_fixture(default_param_error_spec_dir, validate_responses=True) def test_handle_add_operation_error(simple_api_spec_dir): diff --git a/tests/api/test_responses.py b/tests/api/test_responses.py index 127245c6e..2e310564f 100644 --- a/tests/api/test_responses.py +++ b/tests/api/test_responses.py @@ -7,8 +7,6 @@ def test_app(simple_app): - assert simple_app.port == 5001 - app_client = simple_app.app.test_client() # by default the Swagger UI is enabled diff --git a/tests/conftest.py b/tests/conftest.py index 92a125def..f6a4a2f79 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -112,7 +112,7 @@ def security_handler_factory(): @pytest.fixture def app(): - cnx_app = App(__name__, port=5001, specification_dir=SPEC_FOLDER, debug=True) + cnx_app = App(__name__, specification_dir=SPEC_FOLDER) cnx_app.add_api("api.yaml", validate_responses=True) return cnx_app @@ -157,10 +157,8 @@ def build_app_from_fixture( cnx_app = App( __name__, - port=5001, specification_dir=FIXTURES_FOLDER / api_spec_folder, middlewares=middlewares, - debug=debug, ) cnx_app.add_api(spec_file, **kwargs) diff --git a/tests/decorators/test_parameter.py b/tests/decorators/test_parameter.py index 685c9f83e..497bff851 100644 --- a/tests/decorators/test_parameter.py +++ b/tests/decorators/test_parameter.py @@ -31,7 +31,7 @@ def body_name(self, *args, **kwargs): return "body" parameter_decorator = parameter_to_arg(Op(), handler) - await parameter_decorator(request) + parameter_decorator(request) func.assert_called_with(p1="123") @@ -61,7 +61,7 @@ def body_name(self, *args, **kwargs): return "body" parameter_decorator = parameter_to_arg(Op2(), handler) - await parameter_decorator(request) + parameter_decorator(request) func.assert_called_with(request.context, p1="123") diff --git a/tests/test_api.py b/tests/test_api.py index 54ae562be..f102a010e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -89,40 +89,6 @@ def test_invalid_operation_does_stop_application_to_setup(): ) -def test_invalid_operation_does_not_stop_application_in_debug_mode(): - api = FlaskApi( - TEST_FOLDER / "fixtures/op_error_api/swagger.yaml", - base_path="/api/v1.0", - arguments={"title": "OK"}, - debug=True, - ) - assert api.specification["info"]["title"] == "OK" - - api = FlaskApi( - TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", - base_path="/api/v1.0", - arguments={"title": "OK"}, - debug=True, - ) - assert api.specification["info"]["title"] == "OK" - - api = FlaskApi( - TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml", - base_path="/api/v1.0", - arguments={"title": "OK"}, - debug=True, - ) - assert api.specification["info"]["title"] == "OK" - - api = FlaskApi( - TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml", - base_path="/api/v1.0", - arguments={"title": "OK"}, - debug=True, - ) - assert api.specification["info"]["title"] == "OK" - - def test_other_errors_stop_application_to_setup(): # Errors should still result exceptions! with pytest.raises(InvalidSpecification): @@ -139,7 +105,6 @@ def test_invalid_schema_file_structure(): TEST_FOLDER / "fixtures/invalid_schema/swagger.yaml", base_path="/api/v1.0", arguments={"title": "OK"}, - debug=True, ) diff --git a/tests/test_app.py b/tests/test_app.py index 71440dc9a..c01a47260 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -6,13 +6,9 @@ def test_flask_app_default_params(): app = FlaskApp("MyApp") assert app.import_name == "MyApp" - assert app.server == "flask" assert app.api_cls == FlaskApi assert app.arguments == {} # debug should be None so that user can use Flask environment variables to set it - assert app.debug is None - assert app.host is None - assert app.port is None assert app.resolver is None assert app.resolver_error is None assert not app.auth_all_paths diff --git a/tests/test_cli.py b/tests/test_cli.py index 22c9c78b0..773815e14 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,7 +40,6 @@ def expected_arguments(): "swagger_url": None, }, "auth_all_paths": False, - "debug": False, } @@ -62,27 +61,21 @@ def test_run_missing_spec(): def test_run_simple_spec(mock_app_run, spec_file): - default_port = 5000 runner = CliRunner() runner.invoke(main, ["run", spec_file], catch_exceptions=False) app_instance = mock_app_run() - app_instance.run.assert_called_with( - port=default_port, host=None, server="flask", debug=False - ) + app_instance.run.assert_called() def test_run_spec_with_host(mock_app_run, spec_file): - default_port = 5000 runner = CliRunner() runner.invoke( main, ["run", spec_file, "--host", "custom.host"], catch_exceptions=False ) app_instance = mock_app_run() - app_instance.run.assert_called_with( - port=default_port, host="custom.host", server="flask", debug=False - ) + app_instance.run.assert_called() def test_run_no_options_all_default(mock_app_run, expected_arguments, spec_file): @@ -137,19 +130,6 @@ def test_run_using_option_auth_all_paths(mock_app_run, expected_arguments, spec_ mock_app_run.assert_called_with("connexion.cli", **expected_arguments) -def test_run_in_debug_mode(mock_app_run, expected_arguments, spec_file, monkeypatch): - logging_config = MagicMock(name="connexion.cli.logging.basicConfig") - monkeypatch.setattr("connexion.cli.logging.basicConfig", logging_config) - - runner = CliRunner() - runner.invoke(main, ["run", spec_file, "-d"], catch_exceptions=False) - - logging_config.assert_called_with(level=logging.DEBUG) - - expected_arguments["debug"] = True - mock_app_run.assert_called_with("connexion.cli", **expected_arguments) - - def test_run_in_very_verbose_mode( mock_app_run, expected_arguments, spec_file, monkeypatch ): @@ -161,7 +141,6 @@ def test_run_in_very_verbose_mode( logging_config.assert_called_with(level=logging.DEBUG) - expected_arguments["debug"] = True mock_app_run.assert_called_with("connexion.cli", **expected_arguments) @@ -174,7 +153,6 @@ def test_run_in_verbose_mode(mock_app_run, expected_arguments, spec_file, monkey logging_config.assert_called_with(level=logging.INFO) - expected_arguments["debug"] = False mock_app_run.assert_called_with("connexion.cli", **expected_arguments)