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

Flask App factory PR #1 #8418

Merged
merged 48 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
56e6c1a
First cut at app factory
craig-rueda Oct 15, 2019
21e3494
Setting things back to master
craig-rueda Oct 18, 2019
be37d24
Working with new FLASK_APP
craig-rueda Oct 18, 2019
9d31fff
Merge branch 'master' into app_factory
craig-rueda Oct 18, 2019
259a307
Still need to refactor Celery
craig-rueda Oct 21, 2019
9f8fe89
Merge branch 'master' into app_factory
craig-rueda Oct 25, 2019
89bba87
CLI mostly working
craig-rueda Oct 28, 2019
fb9cf33
Working on unit tests
craig-rueda Oct 28, 2019
01aafb5
Moving cli stuff around a bit
craig-rueda Oct 28, 2019
9bf6333
Merge branch 'master' into app_factory
craig-rueda Nov 6, 2019
db25d4d
Removing get in config
craig-rueda Nov 6, 2019
0ab9f0a
Defaulting test config
craig-rueda Nov 6, 2019
921c285
Adding flask-testing
craig-rueda Nov 7, 2019
095529f
flask-testing casing
craig-rueda Nov 8, 2019
428655b
resultsbackend property bug
craig-rueda Nov 8, 2019
97e4cda
Fixing up cli
craig-rueda Nov 8, 2019
9f686cd
Quick fix for KV api
craig-rueda Nov 8, 2019
f1318d5
Working on save slice
craig-rueda Nov 8, 2019
947622b
Fixed core_tests
craig-rueda Nov 9, 2019
91a39f5
Fixed utils_tests
craig-rueda Nov 9, 2019
f675ad4
Most tests working - still need to dig into remaining app_context iss…
craig-rueda Nov 13, 2019
78990d1
All tests passing locally - need to update code comments
craig-rueda Nov 13, 2019
7e37cef
Fixing dashboard tests again
craig-rueda Nov 13, 2019
3b95e96
Blacking
craig-rueda Nov 13, 2019
7f1dadc
Sorting imports
craig-rueda Nov 13, 2019
01607c4
linting
craig-rueda Nov 13, 2019
3a66b07
removing envvar mangling
craig-rueda Nov 13, 2019
5764936
Merge remote-tracking branch 'remotes/origin/master' into app_factory
craig-rueda Nov 14, 2019
74cd1bf
blacking
craig-rueda Nov 14, 2019
8d53bd7
Fixing unit tests
craig-rueda Nov 14, 2019
037cb2c
isorting
craig-rueda Nov 14, 2019
3c0d7da
licensing
craig-rueda Nov 14, 2019
c3c480d
fixing mysql tests
craig-rueda Nov 15, 2019
90a25f2
fixing cypress?
craig-rueda Nov 15, 2019
d8e74a2
fixing .flaskenv
craig-rueda Nov 15, 2019
55d5c27
fixing test app_ctx
craig-rueda Nov 15, 2019
92e7922
fixing cypress
craig-rueda Nov 15, 2019
798d723
Merge remote-tracking branch 'remotes/upstream/master' into app_factory
craig-rueda Nov 18, 2019
b943d68
moving manifest processor around
craig-rueda Nov 18, 2019
ab7a69c
moving results backend manager around
craig-rueda Nov 18, 2019
f72438a
Cleaning up __init__ a bit more
craig-rueda Nov 19, 2019
b30ff82
Addressing PR comments
craig-rueda Nov 19, 2019
b333044
Addressing PR comments
craig-rueda Nov 19, 2019
a9ecc7a
Blacking
craig-rueda Nov 19, 2019
ccd0338
Fixes for running celery worker
craig-rueda Nov 19, 2019
03a3e4a
Tuning isort
craig-rueda Nov 19, 2019
cf0dac2
Merge remote-tracking branch 'remotes/origin/master' into app_factory
craig-rueda Nov 19, 2019
d2f4ada
Blacking
craig-rueda Nov 19, 2019
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
250 changes: 28 additions & 222 deletions superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,231 +14,37 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=C,R,W
"""Package's main module!"""
import json
import logging
import os
from copy import deepcopy
from typing import Any, Dict
from flask import current_app as flask_current_app
from werkzeug.local import LocalProxy

import wtforms_json
from flask import Flask, redirect
from flask_appbuilder import AppBuilder, IndexView, SQLA
from flask_appbuilder.baseviews import expose
from flask_compress import Compress
from flask_migrate import Migrate
from flask_talisman import Talisman
from flask_wtf.csrf import CSRFProtect

from superset import config
from superset.app import create_app
from superset.connectors.connector_registry import ConnectorRegistry
from superset.extensions import (
craig-rueda marked this conversation as resolved.
Show resolved Hide resolved
appbuilder as ab,
cache_manager as ext_cache_manager,
db as ext_db,
event_logger as ext_event_logger,
feature_flag_manager as ext_feature_flag_manager,
manifest_processor as ext_manifest_processor,
results_backend_manager as ext_results_backend_manager,
security_manager as ext_security_manager,
talisman as ext_talisman
)
from superset.security import SupersetSecurityManager
from superset.utils.core import pessimistic_connection_handling, setup_cache
from superset.utils.log import DBEventLogger, get_event_logger_from_cfg_value

wtforms_json.init()

APP_DIR = os.path.dirname(__file__)
CONFIG_MODULE = os.environ.get("SUPERSET_CONFIG", "superset.config")

if not os.path.exists(config.DATA_DIR):
os.makedirs(config.DATA_DIR)

app = Flask(__name__)
app.config.from_object(CONFIG_MODULE) # type: ignore
conf = app.config

#################################################################
# Handling manifest file logic at app start
#################################################################
MANIFEST_FILE = APP_DIR + "/static/assets/dist/manifest.json"
manifest: Dict[Any, Any] = {}


def parse_manifest_json():
global manifest
try:
with open(MANIFEST_FILE, "r") as f:
# the manifest inclues non-entry files
# we only need entries in templates
full_manifest = json.load(f)
manifest = full_manifest.get("entrypoints", {})
except Exception:
pass


def get_js_manifest_files(filename):
if app.debug:
parse_manifest_json()
entry_files = manifest.get(filename, {})
return entry_files.get("js", [])


def get_css_manifest_files(filename):
if app.debug:
parse_manifest_json()
entry_files = manifest.get(filename, {})
return entry_files.get("css", [])


def get_unloaded_chunks(files, loaded_chunks):
filtered_files = [f for f in files if f not in loaded_chunks]
for f in filtered_files:
loaded_chunks.add(f)
return filtered_files


parse_manifest_json()


@app.context_processor
def get_manifest():
return dict(
loaded_chunks=set(),
get_unloaded_chunks=get_unloaded_chunks,
js_manifest=get_js_manifest_files,
css_manifest=get_css_manifest_files,
)


#################################################################

for bp in conf["BLUEPRINTS"]:
try:
print("Registering blueprint: '{}'".format(bp.name))
app.register_blueprint(bp)
except Exception as e:
print("blueprint registration failed")
logging.exception(e)

if conf.get("SILENCE_FAB"):
logging.getLogger("flask_appbuilder").setLevel(logging.ERROR)

db = SQLA(app)

if conf.get("WTF_CSRF_ENABLED"):
csrf = CSRFProtect(app)
csrf_exempt_list = conf.get("WTF_CSRF_EXEMPT_LIST", [])
for ex in csrf_exempt_list:
csrf.exempt(ex)

pessimistic_connection_handling(db.engine)

cache = setup_cache(app, conf.get("CACHE_CONFIG"))
tables_cache = setup_cache(app, conf.get("TABLE_NAMES_CACHE_CONFIG"))

migrate = Migrate(app, db, directory=APP_DIR + "/migrations")

app.config["LOGGING_CONFIGURATOR"].configure_logging(app.config, app.debug)

if app.config.get("ENABLE_CORS"):
from flask_cors import CORS

CORS(app, **app.config.get("CORS_OPTIONS"))

if app.config.get("ENABLE_PROXY_FIX"):
from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix( # type: ignore
app.wsgi_app, **app.config.get("PROXY_FIX_CONFIG")
)

if app.config.get("ENABLE_CHUNK_ENCODING"):

class ChunkedEncodingFix(object):
def __init__(self, app):
self.app = app

def __call__(self, environ, start_response):
# Setting wsgi.input_terminated tells werkzeug.wsgi to ignore
# content-length and read the stream till the end.
if environ.get("HTTP_TRANSFER_ENCODING", "").lower() == u"chunked":
environ["wsgi.input_terminated"] = True
return self.app(environ, start_response)

app.wsgi_app = ChunkedEncodingFix(app.wsgi_app) # type: ignore

if app.config["UPLOAD_FOLDER"]:
try:
os.makedirs(app.config["UPLOAD_FOLDER"])
except OSError:
pass

for middleware in app.config["ADDITIONAL_MIDDLEWARE"]:
app.wsgi_app = middleware(app.wsgi_app) # type: ignore


class MyIndexView(IndexView):
@expose("/")
def index(self):
return redirect("/superset/welcome")


custom_sm = app.config.get("CUSTOM_SECURITY_MANAGER") or SupersetSecurityManager
if not issubclass(custom_sm, SupersetSecurityManager):
raise Exception(
"""Your CUSTOM_SECURITY_MANAGER must now extend SupersetSecurityManager,
not FAB's security manager.
See [4565] in UPDATING.md"""
)

with app.app_context():
appbuilder = AppBuilder(
app,
db.session,
base_template="superset/base.html",
indexview=MyIndexView,
security_manager_class=custom_sm,
update_perms=False, # Run `superset init` to update FAB's perms
)

security_manager = appbuilder.sm

results_backend = app.config.get("RESULTS_BACKEND")
results_backend_use_msgpack = app.config.get("RESULTS_BACKEND_USE_MSGPACK")

# Merge user defined feature flags with default feature flags
_feature_flags = app.config.get("DEFAULT_FEATURE_FLAGS") or {}
_feature_flags.update(app.config.get("FEATURE_FLAGS") or {})

# Event Logger
event_logger = get_event_logger_from_cfg_value(
app.config.get("EVENT_LOGGER", DBEventLogger())
)


def get_feature_flags():
GET_FEATURE_FLAGS_FUNC = app.config.get("GET_FEATURE_FLAGS_FUNC")
if GET_FEATURE_FLAGS_FUNC:
return GET_FEATURE_FLAGS_FUNC(deepcopy(_feature_flags))
return _feature_flags


def is_feature_enabled(feature):
"""Utility function for checking whether a feature is turned on"""
return get_feature_flags().get(feature)


# Flask-Compress
if conf.get("ENABLE_FLASK_COMPRESS"):
Compress(app)


talisman = Talisman()

if app.config["TALISMAN_ENABLED"]:
talisman.init_app(app, **app.config["TALISMAN_CONFIG"])

# Hook that provides administrators a handle on the Flask APP
# after initialization
flask_app_mutator = app.config.get("FLASK_APP_MUTATOR")
if flask_app_mutator:
flask_app_mutator(app)

from superset import views # noqa isort:skip

# Registering sources
module_datasource_map = app.config["DEFAULT_MODULE_DS_MAP"]
module_datasource_map.update(app.config["ADDITIONAL_MODULE_DS_MAP"])
ConnectorRegistry.register_sources(module_datasource_map)
app = create_app()
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need this file? Having logic in __init__.py can be problematic for cyclical dependencies.

Copy link
Member Author

Choose a reason for hiding this comment

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

We do and we don't. My goal is to clear it out completely... However, the changes required in order to facilitate this will end up being quite a large PR. If you think it's reasonable to "just do it", then I'd be happy to pull that thread.

One thing of note here is that removing app from here will REQUIRE people to register their FLASK_APP env var differently superset.app -> superset.app:create_app()

appbuilder = ab
cache = LocalProxy(lambda: ext_cache_manager.cache)
conf = LocalProxy(lambda: flask_current_app.config)
db = ext_db
event_logger = ext_event_logger
get_feature_flags = ext_feature_flag_manager.get_feature_flags
get_css_manifest_files = ext_manifest_processor.get_css_manifest_files
is_feature_enabled = ext_feature_flag_manager.is_feature_enabled
results_backend = LocalProxy(lambda: ext_results_backend_manager.results_backend)
results_backend_use_msgpack = LocalProxy(lambda: ext_results_backend_manager.should_use_msgpack)
security_manager = ext_security_manager
tables_cache = LocalProxy(lambda: ext_cache_manager.tables_cache)
talisman = ext_talisman
Loading