Skip to content

Commit 7b6864f

Browse files
irvingpopAaron Suarez
and
Aaron Suarez
authored
To fix the rate limiting for /healthz, reorganize the app/__init__ a bunch to support decorators (#280)
* To fix the rate limiting for /healthz, reorganize the app/__init__ a bunch to support decorators Signed-off-by: Irving Popovetsky <[email protected]> * Making the linter happy broke the app, so quiet the linter instead Signed-off-by: Irving Popovetsky <[email protected]> * nobody found my requirements.txt joke funny Signed-off-by: Irving Popovetsky <[email protected]> * Fix the /metrics endpoint Signed-off-by: Irving Popovetsky <[email protected]> * test the healthz endpoint Signed-off-by: Irving Popovetsky <[email protected]> * Revert "Fix the /metrics endpoint" This reverts commit ddfdcc5. * Comment out the /environment endpoint Co-authored-by: Aaron Suarez <[email protected]>
1 parent e2a345d commit 7b6864f

File tree

5 files changed

+71
-56
lines changed

5 files changed

+71
-56
lines changed

app/__init__.py

+45-20
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
from algoliasearch.search_client import SearchClient
22
from configs import Config
3+
34
from flask import Flask
45
from flask_migrate import Migrate
56
from flask_sqlalchemy import SQLAlchemy
67
from flask_limiter import Limiter
78
from flask_limiter.util import get_remote_address
8-
from app.healthcheck import add_health_check
9+
10+
from healthcheck import HealthCheck
11+
# from healthcheck import EnvironmentDump
12+
13+
from app.versioning import versioned
914

1015
db = SQLAlchemy()
1116
migrate = Migrate()
@@ -14,30 +19,50 @@
1419
search_client = SearchClient.create(Config.ALGOLIA_APP_ID, Config.ALGOLIA_API_KEY)
1520
index = search_client.init_index(Config.INDEX_NAME)
1621

22+
app = Flask(__name__, static_folder='app/static')
23+
app.config.from_object(Config)
24+
app.url_map.strict_slashes = False
1725

18-
def create_app(config_class=Config):
19-
app = Flask(__name__, static_folder=None)
20-
app.config.from_object(config_class)
21-
app.url_map.strict_slashes = False
26+
limiter = Limiter(
27+
app,
28+
key_func=get_remote_address,
29+
default_limits=["200 per day", "50 per hour"]
30+
)
2231

23-
Limiter(
24-
app,
25-
key_func=get_remote_address,
26-
default_limits=["200 per day", "50 per hour"]
27-
)
32+
db.init_app(app)
33+
migrate.init_app(app, db)
2834

29-
db.init_app(app)
30-
migrate.init_app(app, db)
35+
from app.api import bp as api_bp # noqa
36+
app.register_blueprint(api_bp, url_prefix='/api/v1')
3137

32-
from app.api import bp as api_bp
33-
app.register_blueprint(api_bp, url_prefix='/api/v1')
38+
from app.views import bp as view_bp # noqa
39+
app.register_blueprint(view_bp)
3440

35-
from app.views import bp as view_bp
36-
app.register_blueprint(view_bp)
41+
from app.errors import bp as error_bp # noqa
42+
app.register_blueprint(error_bp)
3743

38-
from app.errors import bp as error_bp
39-
app.register_blueprint(error_bp)
4044

41-
add_health_check(app)
45+
@app.route("/healthz")
46+
@limiter.exempt
47+
def healthz():
48+
health = HealthCheck()
49+
health.add_section("application", application_data)
50+
return health.run()
4251

43-
return app
52+
53+
# @app.route("/environment")
54+
# @limiter.limit("1 per hour")
55+
# def environment():
56+
# envdump = EnvironmentDump()
57+
# envdump.add_section("application", application_data)
58+
# return envdump.run()
59+
60+
61+
@versioned
62+
def application_data(version):
63+
return dict(
64+
apiVersion=version,
65+
status="ok",
66+
status_code=200,
67+
data=None
68+
)

app/healthcheck.py

-23
This file was deleted.

run.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
from app import cli, create_app
1+
from app import app, cli
22
from app.models import Category, Language, Resource, db
3-
from werkzeug.wsgi import DispatcherMiddleware
3+
from werkzeug.middleware.dispatcher import DispatcherMiddleware
44
from prometheus_client import make_wsgi_app
55

6-
app = create_app()
6+
7+
if __name__ == "__main__":
8+
app.run()
9+
710
cli.register(app, db)
811

912
# Add prometheus wsgi middleware to route /metrics requests

tests/conftest.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import pytest
22
from algoliasearch.exceptions import (AlgoliaException,
33
AlgoliaUnreachableHostException)
4-
from app import create_app
4+
from app import app
55
from app import db as _db
66
from app.utils import standardize_response
7-
from configs import Config
87

98
TEST_DATABASE_URI = 'sqlite:///:memory:'
109

@@ -16,19 +15,19 @@
1615
# away the need for a persistent DB and be able to use this method again.
1716
@pytest.fixture(scope='session')
1817
def session_app(request):
19-
Config.SQLALCHEMY_DATABASE_URI = TEST_DATABASE_URI
20-
Config.TESTING = True
21-
app = create_app(Config)
18+
flask_app = app
19+
flask_app.config['TESTING'] = True
20+
flask_app.config['SQLALCHEMY_DATABASE_URI'] = TEST_DATABASE_URI
2221

2322
# Establish an application context before running the tests.
24-
ctx = app.app_context()
23+
ctx = flask_app.app_context()
2524
ctx.push()
2625

2726
def teardown():
2827
ctx.pop()
2928

3029
request.addfinalizer(teardown)
31-
return app
30+
return flask_app
3231

3332

3433
# TODO: session_db is not currently used because we need a
@@ -72,9 +71,9 @@ def teardown():
7271

7372
@pytest.fixture(scope='module')
7473
def module_client():
75-
Config.SQLALCHEMY_DATABASE_URI = TEST_DATABASE_URI
76-
Config.TESTING = True
77-
flask_app = create_app(Config)
74+
flask_app = app
75+
flask_app.config['TESTING'] = True
76+
flask_app.config['SQLALCHEMY_DATABASE_URI'] = TEST_DATABASE_URI
7877

7978
# Flask provides a way to test your application by exposing the Werkzeug test Client
8079
# and handling the context locals for you.

tests/unit/test_routes/test_utils.py

+11
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,14 @@ def test_rate_limit(module_client, module_db):
113113
# Response should be a failure on request 51
114114
response = client.get('api/v1/resources')
115115
assert_correct_response(response, 429)
116+
117+
118+
# Ensure the healthz endpoint is never rate limited
119+
def test_rate_limit_healthz(module_client, module_db):
120+
client = module_client
121+
122+
for _ in range(50):
123+
client.get('/healthz')
124+
125+
response = client.get('/healthz')
126+
assert (response.status_code == 200)

0 commit comments

Comments
 (0)