Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split redash/__init__.py to prevent import time side-effects. #3601

Merged
merged 9 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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():
jezdez marked this conversation as resolved.
Show resolved Hide resolved
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)
jezdez marked this conversation as resolved.
Show resolved Hide resolved

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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I basically tried to separate Flask app specific code in the flask.Flask subclass and all Redash specific code in the create_app function.

"""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)
jezdez marked this conversation as resolved.
Show resolved Hide resolved

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,
jezdez marked this conversation as resolved.
Show resolved Hide resolved
}
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():
arikfr marked this conversation as resolved.
Show resolved Hide resolved
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():
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed since Flask has app.config.from_object

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
arikfr marked this conversation as resolved.
Show resolved Hide resolved

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