Skip to content

Commit 7b944c7

Browse files
committed
Split redash/__init__.py to prevent import time side-effects.
Refs #3569 and #3466.
1 parent 97492d7 commit 7b944c7

File tree

10 files changed

+113
-102
lines changed

10 files changed

+113
-102
lines changed

redash/__init__.py

+16-72
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
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
8+
<<<<<<< HEAD
79
from flask import Flask, current_app
810
from werkzeug.contrib.fixers import ProxyFix
911
from werkzeug.routing import BaseConverter
1012
from statsd import StatsClient
13+
=======
14+
>>>>>>> Split redash/__init__.py to prevent import time side-effects.
1115
from flask_mail import Mail
1216
from flask_limiter import Limiter
1317
from flask_limiter.util import get_ipaddr
1418
from flask_migrate import Migrate
19+
from statsd import StatsClient
20+
from werkzeug.local import LocalProxy
1521

16-
from redash import settings
17-
from redash.query_runner import import_query_runners
18-
from redash.destinations import import_destinations
19-
22+
from . import settings
2023

2124
__version__ = '7.0.0'
2225

2326

24-
import os
2527
if os.environ.get("REMOTE_DEBUG"):
2628
import ptvsd
2729
ptvsd.enable_attach(address=('0.0.0.0', 5678))
@@ -36,10 +38,8 @@ def setup_logging():
3638

3739
# Make noisy libraries less noisy
3840
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")
41+
for name in ["passlib", "requests.packages.urllib3", "snowflake.connector", "apiclient"]:
42+
logging.getLogger(name).setLevel("ERROR")
4343

4444

4545
def create_redis_connection():
@@ -67,69 +67,13 @@ def create_redis_connection():
6767

6868

6969
setup_logging()
70-
redis_connection = create_redis_connection()
71-
72-
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)
7770

78-
import_query_runners(settings.QUERY_RUNNERS)
79-
import_destinations(settings.DESTINATIONS)
71+
redis_connection = LocalProxy(create_redis_connection)
8072

81-
from redash.version_check import reset_new_version_status
82-
reset_new_version_status()
73+
mail = LocalProxy(Mail)
8374

75+
migrate = LocalProxy(Migrate)
8476

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())
121-
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)
77+
statsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)
13478

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

redash/app.py

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

redash/cli/__init__.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from flask.cli import FlaskGroup, run_command
55
from flask import current_app
66

7-
from redash import create_app, settings, __version__
7+
from redash import settings, __version__
8+
from redash.app import create_app
89
from redash.cli import users, groups, database, data_sources, organization
910
from redash.monitor import get_status
1011

@@ -15,9 +16,11 @@ def create(group):
1516

1617
@app.shell_context_processor
1718
def shell_context():
18-
from redash import models
19-
return dict(models=models)
20-
19+
from redash import models, settings
20+
return {
21+
'models': models,
22+
'settings': settings,
23+
}
2124
return app
2225

2326

@@ -48,7 +51,7 @@ def status():
4851
@manager.command()
4952
def check_settings():
5053
"""Show the settings as Redash sees them (useful for debugging)."""
51-
for name, item in settings.all_settings().iteritems():
54+
for name, item in current_app.config.iteritems():
5255
print("{} = {}".format(name, item))
5356

5457

redash/metrics/celery.py

-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
from __future__ import absolute_import
2-
31
import logging
42
import socket
53
import time
64

7-
from celery.signals import task_postrun, task_prerun
85
from redash import settings, statsd_client
96
from redash.utils import json_dumps
107

118
tasks_start_time = {}
129

1310

14-
@task_prerun.connect
1511
def task_prerun_handler(signal, sender, task_id, task, args, kwargs, **kw):
1612
try:
1713
tasks_start_time[task_id] = time.time()
@@ -29,7 +25,6 @@ def metric_name(name, tags):
2925
return "{},{}".format(name, tags_string)
3026

3127

32-
@task_postrun.connect
3328
def task_postrun_handler(signal, sender, task_id, task, args, kwargs, retval, state, **kw):
3429
try:
3530
run_time = 1000 * (time.time() - tasks_start_time.pop(task_id))

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/utils/routes.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from werkzeug.routing import BaseConverter
2+
3+
4+
class SlugConverter(BaseConverter):
5+
def to_python(self, value):
6+
# This is ay workaround for when we enable multi-org and some files are being called by the index rule:
7+
# for path in settings.STATIC_ASSETS_PATHS:
8+
# full_path = safe_join(path, value)
9+
# if os.path.isfile(full_path):
10+
# raise ValidationError()
11+
12+
return value
13+
14+
def to_url(self, value):
15+
return value

redash/worker.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
from celery import Celery
99
from celery.schedules import crontab
10-
from celery.signals import worker_process_init
11-
from redash import create_app, settings
12-
from redash.metrics import celery as celery_metrics
10+
from celery.signals import task_postrun, task_prerun, worker_process_init
11+
12+
from redash import settings
13+
from redash.app import create_app
14+
from redash.metrics.celery import task_postrun_handler, task_prerun_handler
15+
1316

1417
celery = Celery('redash',
1518
broker=settings.CELERY_BROKER,
@@ -66,6 +69,10 @@ def __call__(self, *args, **kwargs):
6669

6770
celery.Task = ContextTask
6871

72+
# Connect the task signal handles for Redash metrics
73+
task_prerun.connect(task_prerun_handler)
74+
task_postrun.connect(task_postrun_handler)
75+
6976

7077
# Create Flask app after forking a new worker, to make sure no resources are shared between processes.
7178
@worker_process_init.connect

redash/wsgi.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from redash import create_app
1+
from redash.app import create_app
22

33
app = create_app()

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)