Skip to content

Commit 12a824b

Browse files
jezdezharveyrendell
authored andcommitted
Split redash/__init__.py to prevent import time side-effects. (getredash#3601)
## What type of PR is this? (check all applicable) <!-- Please leave only what's applicable --> - [x] Refactor - [x] Bug Fix ## Description This basically makes sure that when import the redash package we don't accidentally trigger import-time side-effects such as requiring Redis. Refs getredash#3569 and getredash#3466.
1 parent f437081 commit 12a824b

File tree

8 files changed

+82
-97
lines changed

8 files changed

+82
-97
lines changed

redash/__init__.py

+12-75
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
1-
import sys
21
import logging
3-
import urlparse
2+
import os
3+
import sys
44
import urllib
5+
import urlparse
56

67
import redis
7-
from flask import Flask, current_app
8-
from werkzeug.contrib.fixers import ProxyFix
9-
from werkzeug.routing import BaseConverter
10-
from statsd import StatsClient
118
from flask_mail import Mail
129
from flask_limiter import Limiter
1310
from flask_limiter.util import get_ipaddr
1411
from flask_migrate import Migrate
12+
from statsd import StatsClient
1513

16-
from redash import settings
17-
from redash.query_runner import import_query_runners
18-
from redash.destinations import import_destinations
19-
14+
from . import settings
15+
from .app import create_app # noqa
2016

2117
__version__ = '7.0.0'
2218

2319

24-
import os
2520
if os.environ.get("REMOTE_DEBUG"):
2621
import ptvsd
2722
ptvsd.enable_attach(address=('0.0.0.0', 5678))
@@ -36,10 +31,8 @@ def setup_logging():
3631

3732
# Make noisy libraries less noisy
3833
if settings.LOG_LEVEL != "DEBUG":
39-
logging.getLogger("passlib").setLevel("ERROR")
40-
logging.getLogger("requests.packages.urllib3").setLevel("ERROR")
41-
logging.getLogger("snowflake.connector").setLevel("ERROR")
42-
logging.getLogger('apiclient').setLevel("ERROR")
34+
for name in ["passlib", "requests.packages.urllib3", "snowflake.connector", "apiclient"]:
35+
logging.getLogger(name).setLevel("ERROR")
4336

4437

4538
def create_redis_connection():
@@ -67,69 +60,13 @@ def create_redis_connection():
6760

6861

6962
setup_logging()
63+
7064
redis_connection = create_redis_connection()
7165

7266
mail = Mail()
73-
migrate = Migrate()
74-
mail.init_mail(settings.all_settings())
75-
statsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)
76-
limiter = Limiter(key_func=get_ipaddr, storage_uri=settings.LIMITER_STORAGE)
77-
78-
import_query_runners(settings.QUERY_RUNNERS)
79-
import_destinations(settings.DESTINATIONS)
8067

81-
from redash.version_check import reset_new_version_status
82-
reset_new_version_status()
83-
84-
85-
class SlugConverter(BaseConverter):
86-
def to_python(self, value):
87-
# This is ay workaround for when we enable multi-org and some files are being called by the index rule:
88-
# for path in settings.STATIC_ASSETS_PATHS:
89-
# full_path = safe_join(path, value)
90-
# if os.path.isfile(full_path):
91-
# raise ValidationError()
92-
93-
return value
94-
95-
def to_url(self, value):
96-
return value
97-
98-
99-
def create_app():
100-
from redash import authentication, extensions, handlers, security
101-
from redash.handlers.webpack import configure_webpack
102-
from redash.handlers import chrome_logger
103-
from redash.models import db, users
104-
from redash.metrics import request as request_metrics
105-
from redash.utils import sentry
106-
107-
sentry.init()
108-
109-
app = Flask(__name__,
110-
template_folder=settings.STATIC_ASSETS_PATH,
111-
static_folder=settings.STATIC_ASSETS_PATH,
112-
static_url_path='/static')
113-
114-
# Make sure we get the right referral address even behind proxies like nginx.
115-
app.wsgi_app = ProxyFix(app.wsgi_app, settings.PROXIES_COUNT)
116-
app.url_map.converters['org_slug'] = SlugConverter
117-
118-
# configure our database
119-
app.config['SQLALCHEMY_DATABASE_URI'] = settings.SQLALCHEMY_DATABASE_URI
120-
app.config.update(settings.all_settings())
68+
migrate = Migrate()
12169

122-
security.init_app(app)
123-
request_metrics.init_app(app)
124-
db.init_app(app)
125-
migrate.init_app(app, db)
126-
mail.init_app(app)
127-
authentication.init_app(app)
128-
limiter.init_app(app)
129-
handlers.init_app(app)
130-
configure_webpack(app)
131-
extensions.init_app(app)
132-
chrome_logger.init_app(app)
133-
users.init_app(app)
70+
statsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)
13471

135-
return app
72+
limiter = Limiter(key_func=get_ipaddr, storage_uri=settings.LIMITER_STORAGE)

redash/app.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from flask import Flask
2+
from werkzeug.contrib.fixers import ProxyFix
3+
4+
from . import settings
5+
6+
7+
class Redash(Flask):
8+
"""A custom Flask app for Redash"""
9+
def __init__(self, *args, **kwargs):
10+
kwargs.update({
11+
'template_folder': settings.STATIC_ASSETS_PATH,
12+
'static_folder': settings.STATIC_ASSETS_PATH,
13+
'static_path': '/static',
14+
})
15+
super(Redash, self).__init__(__name__, *args, **kwargs)
16+
# Make sure we get the right referral address even behind proxies like nginx.
17+
self.wsgi_app = ProxyFix(self.wsgi_app, settings.PROXIES_COUNT)
18+
# Configure Redash using our settings
19+
self.config.from_object('redash.settings')
20+
21+
22+
def create_app():
23+
from . import authentication, extensions, handlers, limiter, mail, migrate, security
24+
from .destinations import import_destinations
25+
from .handlers import chrome_logger
26+
from .handlers.webpack import configure_webpack
27+
from .metrics import request as request_metrics
28+
from .models import db, users
29+
from .query_runner import import_query_runners
30+
from .utils import sentry
31+
from .version_check import reset_new_version_status
32+
33+
sentry.init()
34+
app = Redash()
35+
36+
# Check and update the cached version for use by the client
37+
app.before_first_request(reset_new_version_status)
38+
39+
# Load query runners and destinations
40+
import_query_runners(settings.QUERY_RUNNERS)
41+
import_destinations(settings.DESTINATIONS)
42+
43+
security.init_app(app)
44+
request_metrics.init_app(app)
45+
db.init_app(app)
46+
migrate.init_app(app, db)
47+
mail.init_app(app)
48+
authentication.init_app(app)
49+
limiter.init_app(app)
50+
handlers.init_app(app)
51+
configure_webpack(app)
52+
extensions.init_app(app)
53+
chrome_logger.init_app(app)
54+
users.init_app(app)
55+
56+
return app

redash/cli/__init__.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ def create(group):
1515

1616
@app.shell_context_processor
1717
def shell_context():
18-
from redash import models
19-
return dict(models=models)
20-
18+
from redash import models, settings
19+
return {
20+
'models': models,
21+
'settings': settings,
22+
}
2123
return app
2224

2325

@@ -48,7 +50,7 @@ def status():
4850
@manager.command()
4951
def check_settings():
5052
"""Show the settings as Redash sees them (useful for debugging)."""
51-
for name, item in settings.all_settings().iteritems():
53+
for name, item in current_app.config.iteritems():
5254
print("{} = {}".format(name, item))
5355

5456

redash/handlers/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def paginate(query_set, page, page_size, serializer, **kwargs):
117117

118118
def org_scoped_rule(rule):
119119
if settings.MULTI_ORG:
120-
return "/<org_slug:org_slug>{}".format(rule)
120+
return "/<org_slug>{}".format(rule)
121121

122122
return rule
123123

redash/settings/__init__.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,7 @@
44
from flask_talisman import talisman
55

66
from .helpers import fix_assets_path, array_from_string, parse_boolean, int_or_none, set_from_string
7-
from .organization import DATE_FORMAT
8-
9-
10-
def all_settings():
11-
from types import ModuleType
12-
13-
settings = {}
14-
for name, item in globals().iteritems():
15-
if not callable(item) and not name.startswith("__") and not isinstance(item, ModuleType):
16-
settings[name] = item
17-
18-
return settings
19-
7+
from .organization import DATE_FORMAT # noqa
208

219
REDIS_URL = os.environ.get('REDASH_REDIS_URL', os.environ.get('REDIS_URL', "redis://localhost:6379/0"))
2210
PROXIES_COUNT = int(os.environ.get('REDASH_PROXIES_COUNT', "1"))

redash/tasks/queries.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from redash import models, redis_connection, settings, statsd_client
1212
from redash.query_runner import InterruptException
1313
from redash.tasks.alerts import check_alerts_for_query
14-
from redash.utils import gen_query_hash, json_dumps, json_loads, utcnow, mustache_render
14+
from redash.utils import gen_query_hash, json_dumps, utcnow, mustache_render
1515
from redash.worker import celery
1616

1717
logger = get_task_logger(__name__)

redash/worker.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from celery import Celery
99
from celery.schedules import crontab
1010
from celery.signals import worker_process_init
11+
1112
from redash import create_app, settings
12-
from redash.metrics import celery as celery_metrics
13+
from redash.metrics import celery as celery_metrics # noqa
14+
1315

1416
celery = Celery('redash',
1517
broker=settings.CELERY_BROKER,

tests/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
# Make sure rate limit is enabled
1717
os.environ['REDASH_RATELIMIT_ENABLED'] = "true"
1818

19-
from redash import create_app, limiter
20-
from redash import redis_connection
19+
from redash import limiter, redis_connection
20+
from redash.app import create_app
2121
from redash.models import db
2222
from redash.utils import json_dumps, json_loads
2323
from tests.factories import Factory, user_factory

0 commit comments

Comments
 (0)