• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spec-first / connexion / 11758446876

09 Nov 2024 06:19PM UTC coverage: 96.797%. First build
11758446876

Pull #1992

github

web-flow
Merge 839eed973 into 6a859b317
Pull Request #1992: Upgrade dependencies on v2 banch

23 of 25 new or added lines in 3 files covered. (92.0%)

3083 of 3185 relevant lines covered (96.8%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.25
/connexion/apps/flask_app.py
1
"""
2
This module defines a FlaskApp, a Connexion application to wrap a Flask application.
3
"""
4

5
import datetime
1✔
6
import logging
1✔
7
import pathlib
1✔
8
import json
1✔
9
from decimal import Decimal
1✔
10
from types import FunctionType  # NOQA
1✔
11

12
import flask
1✔
13
import werkzeug.exceptions
1✔
14
from flask import signals
1✔
15
from flask.json.provider import DefaultJSONProvider
1✔
16

17
from ..apis.flask_api import FlaskApi
1✔
18
from ..exceptions import ProblemException
1✔
19
from ..problem import problem
1✔
20
from .abstract import AbstractApp
1✔
21

22
logger = logging.getLogger('connexion.app')
1✔
23

24

25
class FlaskApp(AbstractApp):
1✔
26
    def __init__(self, import_name, server='flask', extra_files=None, **kwargs):
1✔
27
        """
28
        :param extra_files: additional files to be watched by the reloader, defaults to the swagger specs of added apis
29
        :type extra_files: list[str | pathlib.Path], optional
30

31
        See :class:`~connexion.AbstractApp` for additional parameters.
32
        """
33
        super().__init__(import_name, FlaskApi, server=server, **kwargs)
1✔
34
        self.extra_files = extra_files or []
1✔
35

36
    def create_app(self):
1✔
37
        app = flask.Flask(self.import_name, **self.server_args)
1✔
38
        app.json = FlaskJSONProvider(app)
1✔
39
        app.url_map.converters['float'] = NumberConverter
1✔
40
        app.url_map.converters['int'] = IntegerConverter
1✔
41
        return app
1✔
42

43
    def get_root_path(self):
1✔
44
        return pathlib.Path(self.app.root_path)
1✔
45

46
    def set_errors_handlers(self):
1✔
47
        for error_code in werkzeug.exceptions.default_exceptions:
1✔
48
            self.add_error_handler(error_code, self.common_error_handler)
1✔
49

50
        self.add_error_handler(ProblemException, self.common_error_handler)
1✔
51

52
    def common_error_handler(self, exception):
1✔
53
        """
54
        :type exception: Exception
55
        """
56
        if isinstance(exception, ProblemException):
1✔
57
            response = problem(
1✔
58
                status=exception.status, title=exception.title, detail=exception.detail,
59
                type=exception.type, instance=exception.instance, headers=exception.headers,
60
                ext=exception.ext)
61
        else:
62
            if not isinstance(exception, werkzeug.exceptions.HTTPException):
1✔
63
                exception = werkzeug.exceptions.InternalServerError()
×
64

65
            response = problem(title=exception.name,
1✔
66
                               detail=exception.description,
67
                               status=exception.code,
68
                               headers=exception.get_headers())
69

70
        if response.status_code >= 500:
1✔
71
            signals.got_request_exception.send(self.app, exception=exception)
1✔
72

73
        return FlaskApi.get_response(response)
1✔
74

75
    def add_api(self, specification, **kwargs):
1✔
76
        api = super().add_api(specification, **kwargs)
1✔
77
        self.app.register_blueprint(api.blueprint)
1✔
78
        if isinstance(specification, (str, pathlib.Path)):
1✔
79
            self.extra_files.append(self.specification_dir / specification)
1✔
80
        return api
1✔
81

82
    def add_error_handler(self, error_code, function):
1✔
83
        # type: (int, FunctionType) -> None
84
        self.app.register_error_handler(error_code, function)
1✔
85

86
    def run(self,
87
            port=None,
88
            server=None,
89
            debug=None,
90
            host=None,
91
            extra_files=None,
92
            **options):  # pragma: no cover
93
        """
94
        Runs the application on a local development server.
95

96
        :param host: the host interface to bind on.
97
        :type host: str
98
        :param port: port to listen to
99
        :type port: int
100
        :param server: which wsgi server to use
101
        :type server: str | None
102
        :param debug: include debugging information
103
        :type debug: bool
104
        :param extra_files: additional files to be watched by the reloader.
105
        :type extra_files: Iterable[str | pathlib.Path]
106
        :param options: options to be forwarded to the underlying server
107
        """
108
        # this functions is not covered in unit tests because we would effectively testing the mocks
109

110
        # overwrite constructor parameter
111
        if port is not None:
112
            self.port = port
113
        elif self.port is None:
114
            self.port = 5000
115

116
        self.host = host or self.host or '0.0.0.0'
117

118
        if server is not None:
119
            self.server = server
120

121
        if debug is not None:
122
            self.debug = debug
123

124
        if extra_files is not None:
125
            self.extra_files.extend(extra_files)
126

127
        logger.debug('Starting %s HTTP server..', self.server, extra=vars(self))
128
        if self.server == 'flask':
129
            self.app.run(self.host, port=self.port, debug=self.debug,
130
                         extra_files=self.extra_files, **options)
131
        elif self.server == 'tornado':
132
            try:
133
                import tornado.httpserver
134
                import tornado.ioloop
135
                import tornado.wsgi
136
            except ImportError:
137
                raise Exception('tornado library not installed')
138
            wsgi_container = tornado.wsgi.WSGIContainer(self.app)
139
            http_server = tornado.httpserver.HTTPServer(wsgi_container, **options)
140
            http_server.listen(self.port, address=self.host)
141
            logger.info('Listening on %s:%s..', self.host, self.port)
142
            tornado.ioloop.IOLoop.instance().start()
143
        elif self.server == 'gevent':
144
            try:
145
                import gevent.pywsgi
146
            except ImportError:
147
                raise Exception('gevent library not installed')
148
            http_server = gevent.pywsgi.WSGIServer((self.host, self.port), self.app, **options)
149
            logger.info('Listening on %s:%s..', self.host, self.port)
150
            http_server.serve_forever()
151
        else:
152
            raise Exception(f'Server {self.server} not recognized')
153

154

155
class FlaskJSONProvider(DefaultJSONProvider):
1✔
156
    def __init__(self, app):
1✔
157
        super().__init__(app)
1✔
158

159
    def default(self, o):
1✔
160
        if isinstance(o, datetime.datetime):
1✔
161
            if o.tzinfo:
1✔
162
                # eg: '2015-09-25T23:14:42.588601+00:00'
163
                return o.isoformat("T")
1✔
164
            else:
165
                # No timezone present - assume UTC.
166
                # eg: '2015-09-25T23:14:42.588601Z'
167
                return o.isoformat("T") + "Z"
1✔
168

169
        if isinstance(o, datetime.date):
1✔
170
            return o.isoformat()
1✔
171

172
        if isinstance(o, Decimal):
1✔
NEW
173
            return float(o)
×
174

175
        return super().default(o)
1✔
176

177

178
class FlaskJSONEncoder(json.JSONEncoder):
1✔
179
    def default(self, o):
1✔
180
        if isinstance(o, datetime.datetime):
1✔
181
            if o.tzinfo:
1✔
182
                # eg: '2015-09-25T23:14:42.588601+00:00'
183
                return o.isoformat('T')
1✔
184
            else:
185
                # No timezone present - assume UTC.
186
                # eg: '2015-09-25T23:14:42.588601Z'
187
                return o.isoformat('T') + 'Z'
1✔
188

189
        if isinstance(o, datetime.date):
1✔
190
            return o.isoformat()
1✔
191

192
        if isinstance(o, Decimal):
1✔
193
            return float(o)
1✔
194

195
        return json.JSONEncoder.default(self, o)
×
196

197

198
class NumberConverter(werkzeug.routing.BaseConverter):
1✔
199
    """ Flask converter for OpenAPI number type """
200
    regex = r"[+-]?[0-9]*(?:\.[0-9]*)?"
1✔
201

202
    def to_python(self, value):
1✔
203
        return float(value)
1✔
204

205

206
class IntegerConverter(werkzeug.routing.BaseConverter):
1✔
207
    """ Flask converter for OpenAPI integer type """
208
    regex = r"[+-]?[0-9]+"
1✔
209

210
    def to_python(self, value):
1✔
211
        return int(value)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc