Skip to content

Commit

Permalink
Split redash/__init__.py to prevent import time side-effects. (getred…
Browse files Browse the repository at this point in the history
…ash#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.
  • Loading branch information
jezdez authored and harveyrendell committed Nov 14, 2019
1 parent f437081 commit 12a824b
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 97 deletions.
87 changes: 12 additions & 75 deletions redash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import sys
import logging
import urlparse
import os
import sys
import urllib
import urlparse

import redis
from flask import Flask, current_app
from werkzeug.contrib.fixers import ProxyFix
from werkzeug.routing import BaseConverter
from statsd import StatsClient
from flask_mail import Mail
from flask_limiter import Limiter
from flask_limiter.util import get_ipaddr
from flask_migrate import Migrate
from statsd import StatsClient

from redash import settings
from redash.query_runner import import_query_runners
from redash.destinations import import_destinations

from . import settings
from .app import create_app # noqa

__version__ = '7.0.0'


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

# Make noisy libraries less noisy
if settings.LOG_LEVEL != "DEBUG":
logging.getLogger("passlib").setLevel("ERROR")
logging.getLogger("requests.packages.urllib3").setLevel("ERROR")
logging.getLogger("snowflake.connector").setLevel("ERROR")
logging.getLogger('apiclient').setLevel("ERROR")
for name in ["passlib", "requests.packages.urllib3", "snowflake.connector", "apiclient"]:
logging.getLogger(name).setLevel("ERROR")


def create_redis_connection():
Expand Down Expand Up @@ -67,69 +60,13 @@ def create_redis_connection():


setup_logging()

redis_connection = create_redis_connection()

mail = Mail()
migrate = Migrate()
mail.init_mail(settings.all_settings())
statsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)
limiter = Limiter(key_func=get_ipaddr, storage_uri=settings.LIMITER_STORAGE)

import_query_runners(settings.QUERY_RUNNERS)
import_destinations(settings.DESTINATIONS)

from redash.version_check import reset_new_version_status
reset_new_version_status()


class SlugConverter(BaseConverter):
def to_python(self, value):
# This is ay workaround for when we enable multi-org and some files are being called by the index rule:
# for path in settings.STATIC_ASSETS_PATHS:
# full_path = safe_join(path, value)
# if os.path.isfile(full_path):
# raise ValidationError()

return value

def to_url(self, value):
return value


def create_app():
from redash import authentication, extensions, handlers, security
from redash.handlers.webpack import configure_webpack
from redash.handlers import chrome_logger
from redash.models import db, users
from redash.metrics import request as request_metrics
from redash.utils import sentry

sentry.init()

app = Flask(__name__,
template_folder=settings.STATIC_ASSETS_PATH,
static_folder=settings.STATIC_ASSETS_PATH,
static_url_path='/static')

# Make sure we get the right referral address even behind proxies like nginx.
app.wsgi_app = ProxyFix(app.wsgi_app, settings.PROXIES_COUNT)
app.url_map.converters['org_slug'] = SlugConverter

# configure our database
app.config['SQLALCHEMY_DATABASE_URI'] = settings.SQLALCHEMY_DATABASE_URI
app.config.update(settings.all_settings())
migrate = Migrate()

security.init_app(app)
request_metrics.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
mail.init_app(app)
authentication.init_app(app)
limiter.init_app(app)
handlers.init_app(app)
configure_webpack(app)
extensions.init_app(app)
chrome_logger.init_app(app)
users.init_app(app)
statsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)

return app
limiter = Limiter(key_func=get_ipaddr, storage_uri=settings.LIMITER_STORAGE)
56 changes: 56 additions & 0 deletions redash/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from flask import Flask
from werkzeug.contrib.fixers import ProxyFix

from . import settings


class Redash(Flask):
"""A custom Flask app for Redash"""
def __init__(self, *args, **kwargs):
kwargs.update({
'template_folder': settings.STATIC_ASSETS_PATH,
'static_folder': settings.STATIC_ASSETS_PATH,
'static_path': '/static',
})
super(Redash, self).__init__(__name__, *args, **kwargs)
# Make sure we get the right referral address even behind proxies like nginx.
self.wsgi_app = ProxyFix(self.wsgi_app, settings.PROXIES_COUNT)
# Configure Redash using our settings
self.config.from_object('redash.settings')


def create_app():
from . import authentication, extensions, handlers, limiter, mail, migrate, security
from .destinations import import_destinations
from .handlers import chrome_logger
from .handlers.webpack import configure_webpack
from .metrics import request as request_metrics
from .models import db, users
from .query_runner import import_query_runners
from .utils import sentry
from .version_check import reset_new_version_status

sentry.init()
app = Redash()

# Check and update the cached version for use by the client
app.before_first_request(reset_new_version_status)

# Load query runners and destinations
import_query_runners(settings.QUERY_RUNNERS)
import_destinations(settings.DESTINATIONS)

security.init_app(app)
request_metrics.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
mail.init_app(app)
authentication.init_app(app)
limiter.init_app(app)
handlers.init_app(app)
configure_webpack(app)
extensions.init_app(app)
chrome_logger.init_app(app)
users.init_app(app)

return app
10 changes: 6 additions & 4 deletions redash/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ def create(group):

@app.shell_context_processor
def shell_context():
from redash import models
return dict(models=models)

from redash import models, settings
return {
'models': models,
'settings': settings,
}
return app


Expand Down Expand Up @@ -48,7 +50,7 @@ def status():
@manager.command()
def check_settings():
"""Show the settings as Redash sees them (useful for debugging)."""
for name, item in settings.all_settings().iteritems():
for name, item in current_app.config.iteritems():
print("{} = {}".format(name, item))


Expand Down
2 changes: 1 addition & 1 deletion redash/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def paginate(query_set, page, page_size, serializer, **kwargs):

def org_scoped_rule(rule):
if settings.MULTI_ORG:
return "/<org_slug:org_slug>{}".format(rule)
return "/<org_slug>{}".format(rule)

return rule

Expand Down
14 changes: 1 addition & 13 deletions redash/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,7 @@
from flask_talisman import talisman

from .helpers import fix_assets_path, array_from_string, parse_boolean, int_or_none, set_from_string
from .organization import DATE_FORMAT


def all_settings():
from types import ModuleType

settings = {}
for name, item in globals().iteritems():
if not callable(item) and not name.startswith("__") and not isinstance(item, ModuleType):
settings[name] = item

return settings

from .organization import DATE_FORMAT # noqa

REDIS_URL = os.environ.get('REDASH_REDIS_URL', os.environ.get('REDIS_URL', "redis://localhost:6379/0"))
PROXIES_COUNT = int(os.environ.get('REDASH_PROXIES_COUNT', "1"))
Expand Down
2 changes: 1 addition & 1 deletion redash/tasks/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from redash import models, redis_connection, settings, statsd_client
from redash.query_runner import InterruptException
from redash.tasks.alerts import check_alerts_for_query
from redash.utils import gen_query_hash, json_dumps, json_loads, utcnow, mustache_render
from redash.utils import gen_query_hash, json_dumps, utcnow, mustache_render
from redash.worker import celery

logger = get_task_logger(__name__)
Expand Down
4 changes: 3 additions & 1 deletion redash/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
from celery import Celery
from celery.schedules import crontab
from celery.signals import worker_process_init

from redash import create_app, settings
from redash.metrics import celery as celery_metrics
from redash.metrics import celery as celery_metrics # noqa


celery = Celery('redash',
broker=settings.CELERY_BROKER,
Expand Down
4 changes: 2 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
# Make sure rate limit is enabled
os.environ['REDASH_RATELIMIT_ENABLED'] = "true"

from redash import create_app, limiter
from redash import redis_connection
from redash import limiter, redis_connection
from redash.app import create_app
from redash.models import db
from redash.utils import json_dumps, json_loads
from tests.factories import Factory, user_factory
Expand Down

0 comments on commit 12a824b

Please sign in to comment.