Skip to content

Commit da9842f

Browse files
committed
Merge branch 'main' into feat-log-format-updated
2 parents 9b4af08 + 3192e85 commit da9842f

File tree

12 files changed

+2402
-2408
lines changed

12 files changed

+2402
-2408
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ build
22
dist
33
firetail.egg-info
44
__pycache__
5-
.coverage
5+
.coverage*
66
*.egg
77
htmlcov/
88
*.pyc

firetail/apis/abstract.py

Lines changed: 464 additions & 464 deletions
Large diffs are not rendered by default.

firetail/apps/flask_app.py

Lines changed: 184 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,184 +1,184 @@
1-
"""
2-
This module defines a FlaskApp, a Firetail application to wrap a Flask application.
3-
"""
4-
5-
import datetime
6-
import logging
7-
import pathlib
8-
from decimal import Decimal
9-
from types import FunctionType # NOQA
10-
11-
import flask
12-
import werkzeug.exceptions
13-
from flask import json, signals
14-
15-
from ..apis.flask_api import FlaskApi
16-
from ..exceptions import ProblemException
17-
from ..problem import problem
18-
from .abstract import AbstractApp
19-
20-
logger = logging.getLogger('firetail.app')
21-
22-
23-
class FlaskApp(AbstractApp):
24-
def __init__(self, import_name, server='flask', extra_files=None, **kwargs):
25-
"""
26-
:param extra_files: additional files to be watched by the reloader, defaults to the swagger specs of added apis
27-
:type extra_files: list[str | pathlib.Path], optional
28-
29-
See :class:`~firetail.AbstractApp` for additional parameters.
30-
"""
31-
super().__init__(import_name, FlaskApi, server=server, **kwargs)
32-
self.extra_files = extra_files or []
33-
34-
def create_app(self):
35-
app = flask.Flask(self.import_name, **self.server_args)
36-
app.json_encoder = FlaskJSONEncoder
37-
app.url_map.converters['float'] = NumberConverter
38-
app.url_map.converters['int'] = IntegerConverter
39-
return app
40-
41-
def get_root_path(self):
42-
return pathlib.Path(self.app.root_path)
43-
44-
def set_errors_handlers(self):
45-
for error_code in werkzeug.exceptions.default_exceptions:
46-
self.add_error_handler(error_code, self.common_error_handler)
47-
48-
self.add_error_handler(ProblemException, self.common_error_handler)
49-
50-
def common_error_handler(self, exception):
51-
"""
52-
:type exception: Exception
53-
"""
54-
signals.got_request_exception.send(self.app, exception=exception)
55-
if isinstance(exception, ProblemException):
56-
response = problem(
57-
status=exception.status, title=exception.title, detail=exception.detail,
58-
type=exception.type, instance=exception.instance, headers=exception.headers,
59-
ext=exception.ext)
60-
else:
61-
if not isinstance(exception, werkzeug.exceptions.HTTPException):
62-
exception = werkzeug.exceptions.InternalServerError()
63-
64-
response = problem(title=exception.name,
65-
detail=exception.description,
66-
status=exception.code,
67-
headers=exception.get_headers())
68-
69-
return FlaskApi.get_response(response)
70-
71-
def add_api(self, specification, **kwargs):
72-
api = super().add_api(specification, **kwargs)
73-
self.app.register_blueprint(api.blueprint)
74-
if isinstance(specification, (str, pathlib.Path)):
75-
self.extra_files.append(self.specification_dir / specification)
76-
return api
77-
78-
def add_error_handler(self, error_code, function):
79-
# type: (int, FunctionType) -> None
80-
self.app.register_error_handler(error_code, function)
81-
82-
def run(self,
83-
port=None,
84-
server=None,
85-
debug=None,
86-
host=None,
87-
extra_files=None,
88-
**options): # pragma: no cover
89-
"""
90-
Runs the application on a local development server.
91-
92-
:param host: the host interface to bind on.
93-
:type host: str
94-
:param port: port to listen to
95-
:type port: int
96-
:param server: which wsgi server to use
97-
:type server: str | None
98-
:param debug: include debugging information
99-
:type debug: bool
100-
:param extra_files: additional files to be watched by the reloader.
101-
:type extra_files: Iterable[str | pathlib.Path]
102-
:param options: options to be forwarded to the underlying server
103-
"""
104-
# this functions is not covered in unit tests because we would effectively testing the mocks
105-
106-
# overwrite constructor parameter
107-
if port is not None:
108-
self.port = port
109-
elif self.port is None:
110-
self.port = 5000
111-
112-
self.host = host or self.host or '0.0.0.0'
113-
114-
if server is not None:
115-
self.server = server
116-
117-
if debug is not None:
118-
self.debug = debug
119-
120-
if extra_files is not None:
121-
self.extra_files.extend(extra_files)
122-
123-
logger.debug('Starting %s HTTP server..', self.server, extra=vars(self))
124-
if self.server == 'flask':
125-
self.app.run(self.host, port=self.port, debug=self.debug,
126-
extra_files=self.extra_files, **options)
127-
elif self.server == 'tornado':
128-
try:
129-
import tornado.httpserver
130-
import tornado.ioloop
131-
import tornado.wsgi
132-
except ImportError:
133-
raise Exception('tornado library not installed')
134-
wsgi_container = tornado.wsgi.WSGIContainer(self.app)
135-
http_server = tornado.httpserver.HTTPServer(wsgi_container, **options)
136-
http_server.listen(self.port, address=self.host)
137-
logger.info('Listening on %s:%s..', self.host, self.port)
138-
tornado.ioloop.IOLoop.instance().start()
139-
elif self.server == 'gevent':
140-
try:
141-
import gevent.pywsgi
142-
except ImportError:
143-
raise Exception('gevent library not installed')
144-
http_server = gevent.pywsgi.WSGIServer((self.host, self.port), self.app, **options)
145-
logger.info('Listening on %s:%s..', self.host, self.port)
146-
http_server.serve_forever()
147-
else:
148-
raise Exception(f'Server {self.server} not recognized')
149-
150-
151-
class FlaskJSONEncoder(json.JSONEncoder):
152-
def default(self, o):
153-
if isinstance(o, datetime.datetime):
154-
if o.tzinfo:
155-
# eg: '2015-09-25T23:14:42.588601+00:00'
156-
return o.isoformat('T')
157-
else:
158-
# No timezone present - assume UTC.
159-
# eg: '2015-09-25T23:14:42.588601Z'
160-
return o.isoformat('T') + 'Z'
161-
162-
if isinstance(o, datetime.date):
163-
return o.isoformat()
164-
165-
if isinstance(o, Decimal):
166-
return float(o)
167-
168-
return json.JSONEncoder.default(self, o)
169-
170-
171-
class NumberConverter(werkzeug.routing.BaseConverter):
172-
""" Flask converter for OpenAPI number type """
173-
regex = r"[+-]?[0-9]*(\.[0-9]*)?"
174-
175-
def to_python(self, value):
176-
return float(value)
177-
178-
179-
class IntegerConverter(werkzeug.routing.BaseConverter):
180-
""" Flask converter for OpenAPI integer type """
181-
regex = r"[+-]?[0-9]+"
182-
183-
def to_python(self, value):
184-
return int(value)
1+
"""
2+
This module defines a FlaskApp, a Firetail application to wrap a Flask application.
3+
"""
4+
5+
import datetime
6+
import logging
7+
import pathlib
8+
from decimal import Decimal
9+
from types import FunctionType # NOQA
10+
11+
import flask
12+
import werkzeug.exceptions
13+
from flask import json, signals
14+
15+
from ..apis.flask_api import FlaskApi
16+
from ..exceptions import ProblemException
17+
from ..problem import problem
18+
from .abstract import AbstractApp
19+
20+
logger = logging.getLogger('firetail.app')
21+
22+
23+
class FlaskApp(AbstractApp):
24+
def __init__(self, import_name, server='flask', extra_files=None, **kwargs):
25+
"""
26+
:param extra_files: additional files to be watched by the reloader, defaults to the swagger specs of added apis
27+
:type extra_files: list[str | pathlib.Path], optional
28+
29+
See :class:`~firetail.AbstractApp` for additional parameters.
30+
"""
31+
super().__init__(import_name, FlaskApi, server=server, **kwargs)
32+
self.extra_files = extra_files or []
33+
34+
def create_app(self):
35+
app = flask.Flask(self.import_name, **self.server_args)
36+
app.json_encoder = FlaskJSONEncoder
37+
app.url_map.converters['float'] = NumberConverter
38+
app.url_map.converters['int'] = IntegerConverter
39+
return app
40+
41+
def get_root_path(self):
42+
return pathlib.Path(self.app.root_path)
43+
44+
def set_errors_handlers(self):
45+
for error_code in werkzeug.exceptions.default_exceptions:
46+
self.add_error_handler(error_code, self.common_error_handler)
47+
48+
self.add_error_handler(ProblemException, self.common_error_handler)
49+
50+
def common_error_handler(self, exception):
51+
"""
52+
:type exception: Exception
53+
"""
54+
signals.got_request_exception.send(self.app, exception=exception)
55+
if isinstance(exception, ProblemException):
56+
response = problem(
57+
status=exception.status, title=exception.title, detail=exception.detail,
58+
type=exception.type, instance=exception.instance, headers=exception.headers,
59+
ext=exception.ext)
60+
else:
61+
if not isinstance(exception, werkzeug.exceptions.HTTPException):
62+
exception = werkzeug.exceptions.InternalServerError()
63+
64+
response = problem(title=exception.name,
65+
detail=exception.description,
66+
status=exception.code,
67+
headers=exception.get_headers())
68+
69+
return FlaskApi.get_response(response)
70+
71+
def add_api(self, specification, **kwargs):
72+
api = super().add_api(specification, **kwargs)
73+
self.app.register_blueprint(api.blueprint)
74+
if isinstance(specification, (str, pathlib.Path)):
75+
self.extra_files.append(self.specification_dir / specification)
76+
return api
77+
78+
def add_error_handler(self, error_code, function):
79+
# type: (int, FunctionType) -> None
80+
self.app.register_error_handler(error_code, function)
81+
82+
def run(self,
83+
port=None,
84+
server=None,
85+
debug=None,
86+
host=None,
87+
extra_files=None,
88+
**options): # pragma: no cover
89+
"""
90+
Runs the application on a local development server.
91+
92+
:param host: the host interface to bind on.
93+
:type host: str
94+
:param port: port to listen to
95+
:type port: int
96+
:param server: which wsgi server to use
97+
:type server: str | None
98+
:param debug: include debugging information
99+
:type debug: bool
100+
:param extra_files: additional files to be watched by the reloader.
101+
:type extra_files: Iterable[str | pathlib.Path]
102+
:param options: options to be forwarded to the underlying server
103+
"""
104+
# this functions is not covered in unit tests because we would effectively testing the mocks
105+
106+
# overwrite constructor parameter
107+
if port is not None:
108+
self.port = port
109+
elif self.port is None:
110+
self.port = 5000
111+
112+
self.host = host or self.host or '0.0.0.0'
113+
114+
if server is not None:
115+
self.server = server
116+
117+
if debug is not None:
118+
self.debug = debug
119+
120+
if extra_files is not None:
121+
self.extra_files.extend(extra_files)
122+
123+
logger.debug('Starting %s HTTP server..', self.server, extra=vars(self))
124+
if self.server == 'flask':
125+
self.app.run(self.host, port=self.port, debug=self.debug,
126+
extra_files=self.extra_files, **options)
127+
elif self.server == 'tornado':
128+
try:
129+
import tornado.httpserver
130+
import tornado.ioloop
131+
import tornado.wsgi
132+
except ImportError:
133+
raise Exception('tornado library not installed')
134+
wsgi_container = tornado.wsgi.WSGIContainer(self.app)
135+
http_server = tornado.httpserver.HTTPServer(wsgi_container, **options)
136+
http_server.listen(self.port, address=self.host)
137+
logger.info('Listening on %s:%s..', self.host, self.port)
138+
tornado.ioloop.IOLoop.instance().start()
139+
elif self.server == 'gevent':
140+
try:
141+
import gevent.pywsgi
142+
except ImportError:
143+
raise Exception('gevent library not installed')
144+
http_server = gevent.pywsgi.WSGIServer((self.host, self.port), self.app, **options)
145+
logger.info('Listening on %s:%s..', self.host, self.port)
146+
http_server.serve_forever()
147+
else:
148+
raise Exception(f'Server {self.server} not recognized')
149+
150+
151+
class FlaskJSONEncoder(json.JSONEncoder):
152+
def default(self, o):
153+
if isinstance(o, datetime.datetime):
154+
if o.tzinfo:
155+
# eg: '2015-09-25T23:14:42.588601+00:00'
156+
return o.isoformat('T')
157+
else:
158+
# No timezone present - assume UTC.
159+
# eg: '2015-09-25T23:14:42.588601Z'
160+
return o.isoformat('T') + 'Z'
161+
162+
if isinstance(o, datetime.date):
163+
return o.isoformat()
164+
165+
if isinstance(o, Decimal):
166+
return float(o)
167+
168+
return json.JSONEncoder.default(self, o)
169+
170+
171+
class NumberConverter(werkzeug.routing.BaseConverter):
172+
""" Flask converter for OpenAPI number type """
173+
regex = r"[+-]?[0-9]*(\.[0-9]*)?"
174+
175+
def to_python(self, value):
176+
return float(value)
177+
178+
179+
class IntegerConverter(werkzeug.routing.BaseConverter):
180+
""" Flask converter for OpenAPI integer type """
181+
regex = r"[+-]?[0-9]+"
182+
183+
def to_python(self, value):
184+
return int(value)

0 commit comments

Comments
 (0)