Skip to content

Commit

Permalink
Add async app
Browse files Browse the repository at this point in the history
  • Loading branch information
RobbeSneyders committed Dec 23, 2022
1 parent db6ebec commit 33c9726
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 150 deletions.
7 changes: 5 additions & 2 deletions connexion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from .apis import AbstractAPI # NOQA
from .apps import AbstractApp # NOQA
from .apps.async_app import AsyncApp
from .datastructures import NoContent # NOQA
from .exceptions import ProblemException # NOQA
from .problem import problem # NOQA
Expand All @@ -20,13 +21,15 @@
try:
from flask import request # NOQA

from .apis.flask_api import FlaskApi # NOQA
from .apps.flask_app import FlaskApp
from connexion.apis.flask_api import FlaskApi # NOQA
from connexion.apps.flask_app import FlaskApp
except ImportError as e: # pragma: no cover
_flask_not_installed_error = not_installed_error(e)
FlaskApi = _flask_not_installed_error # type: ignore
FlaskApp = _flask_not_installed_error # type: ignore

from connexion.apps.async_app import AsyncApi, AsyncApp, ConnexionMiddleware

App = FlaskApp
Api = FlaskApi

Expand Down
22 changes: 14 additions & 8 deletions connexion/apis/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from connexion.options import ConnexionOptions
from connexion.resolver import Resolver
from connexion.spec import Specification
from connexion.utils import is_json_mimetype

MODULE_PATH = pathlib.Path(__file__).absolute().parent.parent
SWAGGER_UI_URL = "ui"
Expand Down Expand Up @@ -103,14 +104,19 @@ def __init__(
self,
*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

Expand Down Expand Up @@ -186,18 +192,13 @@ def __init__(
resolver=None,
debug=False,
resolver_error_handler=None,
pythonic_params=False,
options=None,
**kwargs,
):
"""
:type resolver_error_handler: callable | None
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
to any shadowed built-ins
:type pythonic_params: bool
"""
logger.debug("Pythonic params: %s", str(pythonic_params))
self.pythonic_params = pythonic_params

super().__init__(
specification,
Expand All @@ -207,6 +208,7 @@ def __init__(
resolver_error_handler=resolver_error_handler,
debug=debug,
options=options,
**kwargs,
)

def add_operation(self, path, method):
Expand Down Expand Up @@ -239,7 +241,7 @@ def add_operation(self, path, method):

@classmethod
@abc.abstractmethod
def get_request(cls, uri_parser) -> Request:
def get_request(cls, **kwargs) -> Request:
"""
This method converts the user framework request to a ConnexionRequest.
"""
Expand Down Expand Up @@ -414,9 +416,13 @@ def _prepare_body_and_status_code(cls, data, mimetype, status_code=None):
return body, status_code, mimetype

@classmethod
@abc.abstractmethod
def _serialize_data(cls, data, mimetype):
pass
if isinstance(mimetype, str) and is_json_mimetype(mimetype):
body = cls.jsonifier.dumps(data)
else:
body = data

return body, mimetype

def json_loads(self, data):
return self.jsonifier.loads(data)
13 changes: 2 additions & 11 deletions connexion/apis/flask_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from connexion.apis.abstract import AbstractAPI
from connexion.jsonifier import Jsonifier
from connexion.lifecycle import ConnexionRequest, ConnexionResponse
from connexion.utils import is_json_mimetype

logger = logging.getLogger("connexion.apis.flask_api")

Expand Down Expand Up @@ -121,16 +120,8 @@ def _build_response(
return flask.current_app.response_class(**kwargs)

@classmethod
def _serialize_data(cls, data, mimetype):
if isinstance(mimetype, str) and is_json_mimetype(mimetype):
body = cls.jsonifier.dumps(data)
else:
body = data

return body, mimetype

@classmethod
def get_request(cls, uri_parser) -> ConnexionRequest:
def get_request(cls, **kwargs) -> ConnexionRequest:
uri_parser = kwargs.pop("uri_parser")
return ConnexionRequest(flask.request, uri_parser=uri_parser)

@classmethod
Expand Down
52 changes: 11 additions & 41 deletions connexion/apps/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import abc
import logging
import pathlib
import typing as t

from ..middleware import ConnexionMiddleware
from ..options import ConnexionOptions
from ..resolver import Resolver
from connexion.middleware import ConnexionMiddleware
from connexion.options import ConnexionOptions
from connexion.resolver import Resolver

logger = logging.getLogger("connexion.app")

Expand All @@ -22,8 +23,6 @@ def __init__(
port=None,
specification_dir="",
host=None,
server=None,
server_args=None,
arguments=None,
auth_all_paths=False,
debug=None,
Expand All @@ -41,10 +40,6 @@ def __init__(
:type port: int
:param specification_dir: directory where to look for specifications
:type specification_dir: pathlib.Path | str
:param server: which wsgi server to use
:type server: str | None
:param server_args: dictionary of arguments which are then passed to appropriate http server (Flask or aio_http)
:type server_args: dict | None
:param arguments: arguments to replace on the specification
:type arguments: dict | None
:param auth_all_paths: whether to authenticate not defined paths
Expand All @@ -69,11 +64,6 @@ def __init__(

self.options = ConnexionOptions(options)

self.server = server
self.server_args = dict() if server_args is None else server_args

self.app = self.create_app()

if middlewares is None:
middlewares = ConnexionMiddleware.default_middlewares
self.middleware = self._apply_middleware(middlewares)
Expand All @@ -96,12 +86,6 @@ def __init__(
logger.debug("Setting error handlers")
self.set_errors_handlers()

@abc.abstractmethod
def create_app(self):
"""
Creates the user framework application
"""

@abc.abstractmethod
def _apply_middleware(self, middlewares):
"""
Expand All @@ -122,7 +106,8 @@ def set_errors_handlers(self):

def add_api(
self,
specification,
specification: t.Union[pathlib.Path, str, dict],
*,
base_path=None,
arguments=None,
auth_all_paths=None,
Expand Down Expand Up @@ -264,7 +249,8 @@ def index():
logger.debug("Adding %s", rule, extra=log_details)
self.app.add_url_rule(rule, endpoint, view_func, **options)

def route(self, rule, **options):
@abc.abstractmethod
def route(self, rule: str, **options):
"""
A decorator that is used to register a view function for a
given URL rule. This does the same thing as `add_url_rule`
Expand All @@ -275,31 +261,15 @@ def index():
return 'Hello World'
:param rule: the URL rule as string
:type rule: str
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param options: the options to be forwarded to the underlying `werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods is a list of methods this rule should be
limited to (`GET`, `POST` etc.). By default a rule just listens for `GET` (and implicitly
`HEAD`).
"""
logger.debug("Adding %s with decorator", rule, extra=options)
return self.app.route(rule, **options)

@abc.abstractmethod
def run(
self, port=None, server=None, debug=None, host=None, **options
): # pragma: no cover
def __call__(self, scope, receive, send):
"""
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 options: options to be forwarded to the underlying server
ASGI interface.
"""
return self.middleware(scope, receive, send)
Loading

0 comments on commit 33c9726

Please sign in to comment.