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 all 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
4 changes: 2 additions & 2 deletions .flaskenv
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FLASK_APP=superset:app
FLASK_ENV=development
FLASK_APP="superset.app:create_app()"
FLASK_ENV="development"
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
black==19.3b0
coverage==4.5.3
flask-cors==3.0.7
flask-testing==0.7.1
ipdb==0.12
isort==4.3.21
mypy==0.670
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,5 @@ def get_git_sha():
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
tests_require=["flask-testing==0.7.1"],
)
253 changes: 31 additions & 222 deletions superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,229 +14,38 @@
# 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, Flask
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,
cache_manager,
db,
event_logger,
feature_flag_manager,
manifest_processor,
results_backend_manager,
security_manager,
talisman,
)
from superset.security import SupersetSecurityManager
from superset.utils.core import pessimistic_connection_handling, setup_cache
from superset.utils.log import 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["ENABLE_CORS"]:
from flask_cors import CORS

CORS(app, **app.config["CORS_OPTIONS"])

if app.config["ENABLE_PROXY_FIX"]:
from werkzeug.middleware.proxy_fix import ProxyFix

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

if app.config["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["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["RESULTS_BACKEND"]
results_backend_use_msgpack = app.config["RESULTS_BACKEND_USE_MSGPACK"]

# Merge user defined feature flags with default feature flags
_feature_flags = app.config["DEFAULT_FEATURE_FLAGS"]
_feature_flags.update(app.config["FEATURE_FLAGS"])

# Event Logger
event_logger = get_event_logger_from_cfg_value(app.config["EVENT_LOGGER"])


def get_feature_flags():
GET_FEATURE_FLAGS_FUNC = app.config["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["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)
from superset.utils.log import DBEventLogger, get_event_logger_from_cfg_value

# All of the fields located here should be considered legacy. The correct way
# to declare "global" dependencies is to define it in extensions.py,
# then initialize it in app.create_app(). These fields will be removed
# in subsequent PRs as things are migrated towards the factory pattern
app: Flask = current_app
cache = LocalProxy(lambda: cache_manager.cache)
conf = LocalProxy(lambda: current_app.config)
get_feature_flags = feature_flag_manager.get_feature_flags
get_css_manifest_files = manifest_processor.get_css_manifest_files
is_feature_enabled = feature_flag_manager.is_feature_enabled
results_backend = LocalProxy(lambda: results_backend_manager.results_backend)
results_backend_use_msgpack = LocalProxy(
lambda: results_backend_manager.should_use_msgpack
)
tables_cache = LocalProxy(lambda: cache_manager.tables_cache)
Loading