Skip to content

Commit ad53501

Browse files
committed
public: use a factory to create the app object
1 parent 58b292f commit ad53501

File tree

7 files changed

+173
-194
lines changed

7 files changed

+173
-194
lines changed

src/auslib/web/public/base.py

Lines changed: 98 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515

1616
log = logging.getLogger(__name__)
1717

18-
connexion_app = connexion.App(__name__, specification_dir=".", options={"swagger_ui": False})
19-
flask_app = connexion_app.app
20-
2118
current_dir = path.dirname(__file__)
2219
web_dir = path.dirname(auslib.web.__file__)
2320
spec = (
@@ -28,105 +25,104 @@
2825
.add_spec(path.join(web_dir, "common/swagger/parameters.yml"))
2926
.add_spec(path.join(web_dir, "common/swagger/responses.yml"))
3027
)
31-
# Response validation should be enabled when it actually works
32-
connexion_app.add_api(spec, strict_validation=True)
33-
34-
35-
@flask_app.after_request
36-
def apply_security_headers(response):
37-
# There's no use cases for content served by Balrog to load additional content
38-
# nor be embedded elsewhere, so we apply a strict Content Security Policy.
39-
# We also need to set X-Content-Type-Options to nosniff for Firefox to obey this.
40-
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1332829#c4 for background.
41-
response.headers["Strict-Transport-Security"] = flask_app.config.get("STRICT_TRANSPORT_SECURITY", "max-age=31536000;")
42-
response.headers["X-Content-Type-Options"] = flask_app.config.get("CONTENT_TYPE_OPTIONS", "nosniff")
43-
if re.match("^/ui/", request.path):
44-
# This enables swagger-ui to dynamically fetch and
45-
# load the swagger specification JSON file containing API definition and examples.
46-
response.headers["X-Frame-Options"] = "SAMEORIGIN"
47-
else:
48-
response.headers["Content-Security-Policy"] = flask_app.config.get("CONTENT_SECURITY_POLICY", "default-src 'none'; frame-ancestors 'none'")
49-
return response
50-
51-
52-
@flask_app.errorhandler(404)
53-
def fourohfour(error):
54-
if re.match("^/update", request.path):
55-
"""We don't return 404s for AUS /update endpoints. Instead, we return empty XML files"""
56-
response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>')
57-
response.mimetype = "text/xml"
58-
return response
59-
return Response(status=404, mimetype="text/plain", response=error.description)
60-
61-
62-
# Connexion's error handling sometimes breaks when parameters contain
63-
# unicode characters (https://github.com/zalando/connexion/issues/604).
64-
# To work around, we catch them and return a 400 (which is what Connexion
65-
# would do if it didn't hit this error).
66-
@flask_app.errorhandler(UnicodeEncodeError)
67-
def unicode(error):
68-
return problem(400, "Unicode Error", "Connexion was unable to parse some unicode data correctly.")
69-
70-
71-
@flask_app.errorhandler(BadDataError)
72-
def baddata(error):
73-
"""Deals with BadDataError exceptions by returning a 400,
74-
because BadDataErrors are considered to be the client's fault.
75-
"""
76-
# Escape exception messages before replying with them, because they may
77-
# contain user input.
78-
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1332829 for background.
79-
# We used to look at error.message here, but that disappeared from many
80-
# Exception classes in Python 3, so args is the safer bet.
81-
# We may want to stop returning messages like this to the client altogether
82-
# both because it's ugly and potentially can leak things, but it's also
83-
# extremely helpful for debugging BadDataErrors, because we don't send
84-
# information about them to Sentry.
85-
if hasattr(error, "args"):
86-
message = " ".join(str(a) for a in error.args)
87-
else:
88-
message = repr(error)
89-
90-
return Response(status=400, mimetype="text/plain", response=html.escape(message, quote=False))
91-
92-
93-
@flask_app.errorhandler(Exception)
94-
def generic(error):
95-
"""Deals with any unhandled exceptions. It will be sent to Sentry, and re-raised (which causes a 500)."""
9628

97-
# Sentry doesn't handle exceptions for `@flask_app.errorhandler(Exception)`
98-
# implicitly. If Sentry is not configured, the following call returns None.
99-
capture_exception(error)
10029

101-
raise
102-
103-
104-
# Keeping static files endpoints here due to an issue when returning response for static files.
105-
# Similar issue: https://github.com/zalando/connexion/issues/401
106-
@flask_app.route("/robots.txt")
107-
def robots():
108-
return send_from_directory(flask_app.static_folder, "robots.txt")
109-
110-
111-
@flask_app.route("/contribute.json")
112-
def contributejson():
113-
return send_from_directory(flask_app.static_folder, "contribute.json")
114-
115-
116-
@flask_app.before_request
117-
def set_cache_control():
118-
# By default, we want a cache that can be shared across requests from
119-
# different users ("public").
120-
# and a maximum age of 90 seconds, to keep our TTL low.
121-
# We bumped this from 60s -> 90s in November, 2016.
122-
setattr(flask_app, "cacheControl", flask_app.config.get("CACHE_CONTROL", "public, max-age=90"))
123-
124-
125-
@flask_app.route("/debug/api.yml")
126-
def get_yaml():
127-
if flask_app.config.get("SWAGGER_DEBUG", False):
128-
import yaml
30+
def create_app():
31+
connexion_app = connexion.App(__name__, specification_dir=".", options={"swagger_ui": False})
32+
flask_app = connexion_app.app
33+
34+
# Response validation should be enabled when it actually works
35+
connexion_app.add_api(spec, strict_validation=True)
36+
37+
@flask_app.after_request
38+
def apply_security_headers(response):
39+
# There's no use cases for content served by Balrog to load additional content
40+
# nor be embedded elsewhere, so we apply a strict Content Security Policy.
41+
# We also need to set X-Content-Type-Options to nosniff for Firefox to obey this.
42+
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1332829#c4 for background.
43+
response.headers["Strict-Transport-Security"] = flask_app.config.get("STRICT_TRANSPORT_SECURITY", "max-age=31536000;")
44+
response.headers["X-Content-Type-Options"] = flask_app.config.get("CONTENT_TYPE_OPTIONS", "nosniff")
45+
if re.match("^/ui/", request.path):
46+
# This enables swagger-ui to dynamically fetch and
47+
# load the swagger specification JSON file containing API definition and examples.
48+
response.headers["X-Frame-Options"] = "SAMEORIGIN"
49+
else:
50+
response.headers["Content-Security-Policy"] = flask_app.config.get("CONTENT_SECURITY_POLICY", "default-src 'none'; frame-ancestors 'none'")
51+
return response
12952

130-
app_spec = yaml.dump(spec)
131-
return Response(mimetype="text/plain", response=app_spec)
132-
return Response(status=404)
53+
@flask_app.errorhandler(404)
54+
def fourohfour(error):
55+
if re.match("^/update", request.path):
56+
"""We don't return 404s for AUS /update endpoints. Instead, we return empty XML files"""
57+
response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>')
58+
response.mimetype = "text/xml"
59+
return response
60+
return Response(status=404, mimetype="text/plain", response=error.description)
61+
62+
# Connexion's error handling sometimes breaks when parameters contain
63+
# unicode characters (https://github.com/zalando/connexion/issues/604).
64+
# To work around, we catch them and return a 400 (which is what Connexion
65+
# would do if it didn't hit this error).
66+
@flask_app.errorhandler(UnicodeEncodeError)
67+
def unicode(error):
68+
return problem(400, "Unicode Error", "Connexion was unable to parse some unicode data correctly.")
69+
70+
@flask_app.errorhandler(BadDataError)
71+
def baddata(error):
72+
"""Deals with BadDataError exceptions by returning a 400,
73+
because BadDataErrors are considered to be the client's fault.
74+
"""
75+
# Escape exception messages before replying with them, because they may
76+
# contain user input.
77+
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1332829 for background.
78+
# We used to look at error.message here, but that disappeared from many
79+
# Exception classes in Python 3, so args is the safer bet.
80+
# We may want to stop returning messages like this to the client altogether
81+
# both because it's ugly and potentially can leak things, but it's also
82+
# extremely helpful for debugging BadDataErrors, because we don't send
83+
# information about them to Sentry.
84+
if hasattr(error, "args"):
85+
message = " ".join(str(a) for a in error.args)
86+
else:
87+
message = repr(error)
88+
89+
return Response(status=400, mimetype="text/plain", response=html.escape(message, quote=False))
90+
91+
@flask_app.errorhandler(Exception)
92+
def generic(error):
93+
"""Deals with any unhandled exceptions. It will be sent to Sentry, and re-raised (which causes a 500)."""
94+
95+
# Sentry doesn't handle exceptions for `@flask_app.errorhandler(Exception)`
96+
# implicitly. If Sentry is not configured, the following call returns None.
97+
capture_exception(error)
98+
99+
raise
100+
101+
# Keeping static files endpoints here due to an issue when returning response for static files.
102+
# Similar issue: https://github.com/zalando/connexion/issues/401
103+
@flask_app.route("/robots.txt")
104+
def robots():
105+
return send_from_directory(flask_app.static_folder, "robots.txt")
106+
107+
@flask_app.route("/contribute.json")
108+
def contributejson():
109+
return send_from_directory(flask_app.static_folder, "contribute.json")
110+
111+
@flask_app.before_request
112+
def set_cache_control():
113+
# By default, we want a cache that can be shared across requests from
114+
# different users ("public").
115+
# and a maximum age of 90 seconds, to keep our TTL low.
116+
# We bumped this from 60s -> 90s in November, 2016.
117+
setattr(flask_app, "cacheControl", flask_app.config.get("CACHE_CONTROL", "public, max-age=90"))
118+
119+
@flask_app.route("/debug/api.yml")
120+
def get_yaml():
121+
if flask_app.config.get("SWAGGER_DEBUG", False):
122+
import yaml
123+
124+
app_spec = yaml.dump(spec)
125+
return Response(mimetype="text/plain", response=app_spec)
126+
return Response(status=404)
127+
128+
return connexion_app

tests/web/api/base.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from auslib.blobs.base import createBlob
88
from auslib.global_state import dbo
9-
from auslib.web.public.base import flask_app as app
9+
from auslib.web.public.base import create_app
1010

1111

1212
def setUpModule():
@@ -18,19 +18,16 @@ def setUpModule():
1818
class CommonTestBase(unittest.TestCase):
1919
@classmethod
2020
def setUpClass(cls):
21+
connexion_app = create_app()
22+
cls.app = connexion_app.app
2123
# Error handlers are removed in order to give us better debug messages
22-
cls.error_spec = app.error_handler_spec
2324
# Ripped from https://github.com/pallets/flask/blob/2.3.3/src/flask/scaffold.py#L131-L134
24-
app.error_handler_spec = defaultdict(lambda: defaultdict(dict))
25-
26-
@classmethod
27-
def tearDownClass(cls):
28-
app.error_handler_spec = cls.error_spec
25+
cls.app.error_handler_spec = defaultdict(lambda: defaultdict(dict))
2926

3027
@pytest.fixture(autouse=True)
3128
def setup(self, insert_release, firefox_54_0_1_build1, firefox_56_0_build1, superblob_e8f4a19, hotfix_bug_1548973_1_1_4, timecop_1_0):
32-
app.config["DEBUG"] = True
33-
self.public_client = app.test_client()
29+
self.app.config["DEBUG"] = True
30+
self.public_client = self.app.test_client()
3431

3532
dbo.setDb("sqlite:///:memory:")
3633
self.metadata.create_all(dbo.engine)

tests/web/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from collections import defaultdict
2+
3+
import pytest
4+
5+
from auslib.web.public.base import create_app
6+
7+
8+
@pytest.fixture(scope="class")
9+
def app(request):
10+
connexion_app = create_app()
11+
app = request.cls.app = connexion_app.app
12+
return app
13+
14+
15+
@pytest.fixture(scope="class")
16+
def propagate_exceptions(app):
17+
# Error handlers are removed in order to give us better debug messages
18+
# Ripped from https://github.com/pallets/flask/blob/2.3.3/src/flask/scaffold.py#L131-L134
19+
app.error_handler_spec = defaultdict(lambda: defaultdict(dict))

0 commit comments

Comments
 (0)