Skip to content

Commit

Permalink
#786: updates to shutdown
Browse files Browse the repository at this point in the history
  • Loading branch information
aschonfeld committed Aug 22, 2023
1 parent 57f3721 commit d1c3dd6
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 59 deletions.
117 changes: 69 additions & 48 deletions dtale/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@
)
from dtale.views import DtaleData, head_endpoint, is_up, kill, startup

if PY3:
import _thread
else:
import thread as _thread

logger = logging.getLogger(__name__)

USE_NGROK = False
Expand Down Expand Up @@ -423,8 +418,12 @@ def shutdown_server():
logger.info("Executing shutdown...")
func = request.environ.get("werkzeug.server.shutdown")
if func is None:
raise RuntimeError("Not running with the Werkzeug Server")
func()
logger.info("Not running with the Werkzeug Server, exiting using os._exit")
import os

os._exit(os.EX_OK)
else:
func()
global_state.cleanup()
ACTIVE_PORT = None
ACTIVE_HOST = None
Expand Down Expand Up @@ -633,6 +632,38 @@ def use_colab(port):
return None


def start(host, port, use_ngrok, app_url, final_options, final_app_root, instance):
app = build_app(
app_url,
reaper_on=final_options["reaper_on"],
host=host,
app_root=final_app_root,
)
if final_options["debug"] and not use_ngrok:
app.jinja_env.auto_reload = True
app.config["TEMPLATES_AUTO_RELOAD"] = True
else:
logging.getLogger("werkzeug").setLevel(LOG_ERROR)

if final_options["open_browser"]:
instance.open_browser()

# hide banner message in production environments
cli = sys.modules.get("flask.cli")
if cli is not None:
cli.show_server_banner = lambda *x: None

if use_ngrok:
app.run(threaded=True)
else:
app.run(
host="0.0.0.0",
port=port,
debug=final_options["debug"],
threaded=True,
)


def show(data=None, data_loader=None, name=None, context_vars=None, **options):
"""
Entry point for kicking off D-Tale :class:`flask:flask.Flask` process from python process
Expand Down Expand Up @@ -783,61 +814,51 @@ def show(data=None, data_loader=None, name=None, context_vars=None, **options):
)
instance.started_with_open_browser = final_options["open_browser"]
is_active = not running_with_flask_debug() and is_up(app_url)
if is_active:

def _start():
if final_options["open_browser"]:
instance.open_browser()

else:
if not is_active:
if USE_NGROK:
thread = Timer(1, _run_ngrok)
thread.setDaemon(True)
thread.start()

def _start():
app = build_app(
app_url,
reaper_on=final_options["reaper_on"],
host=ACTIVE_HOST,
app_root=final_app_root,
)
if final_options["debug"] and not USE_NGROK:
app.jinja_env.auto_reload = True
app.config["TEMPLATES_AUTO_RELOAD"] = True
else:
logging.getLogger("werkzeug").setLevel(LOG_ERROR)

if final_options["open_browser"]:
instance.open_browser()

# hide banner message in production environments
cli = sys.modules.get("flask.cli")
if cli is not None:
cli.show_server_banner = lambda *x: None

if USE_NGROK:
app.run(threaded=True)
else:
app.run(
host="0.0.0.0",
port=ACTIVE_PORT,
debug=final_options["debug"],
threaded=True,
)

if final_options["subprocess"]:
if is_active:
_start()
if final_options["open_browser"]:
instance.open_browser()
else:
_thread.start_new_thread(_start, ())
import multiprocessing

p = multiprocessing.Process(
target=start,
args=(
ACTIVE_HOST,
ACTIVE_PORT,
USE_NGROK,
app_url,
final_options,
final_app_root,
instance,
),
)
p.start()

if final_options["notebook"]:
instance.notebook()
else:
# Need to use logging.info() because for some reason other libraries like arctic seem to break logging
logging.info("D-Tale started at: {}".format(app_url))
_start()
if is_active:
if final_options["open_browser"]:
instance.open_browser()
else:
start(
ACTIVE_HOST,
ACTIVE_PORT,
USE_NGROK,
app_url,
final_options,
final_app_root,
instance,
)

return instance
except DuplicateDataError as ex:
Expand Down
5 changes: 4 additions & 1 deletion dtale/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ def kill(base):
This function fires a request to this instance's 'shutdown' route to kill it
"""
requests.get(build_shutdown_url(base))
try:
requests.get(build_shutdown_url(base))
except BaseException:
logger.info("Shutdown complete")


def is_up(base):
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Flask<2.3; python_version >= '3.7'
Flask-Compress
flask-ngrok; python_version > '3.0'
future>=0.14.0
immutables<=0.19; python_version == '3.6'
itsdangerous<=1.1.0; python_version < '3.7'
itsdangerous; python_version >= '3.7'
# required for loading scikit-learn
Expand All @@ -45,7 +46,8 @@ matplotlib<=3.3.4; python_version == '3.4'
matplotlib<=3.3.4; python_version == '3.5'
matplotlib<=3.3.4; python_version == '3.6'
matplotlib<=3.5.3; python_version == '3.7'
matplotlib; python_version >= '3.8'
matplotlib<=3.7.2; python_version == '3.8'
matplotlib; python_version >= '3.9'
missingno
networkx<=2.2; python_version <= '3.4'
networkx<=2.4; python_version == '3.5'
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def run_tests(self):
install_requires=read_file("requirements.txt"),
extras_require={
"arctic": ["arctic <= 1.79.4"],
"arcticdb": ["arcticdb"],
"arcticdb": ["arcticdb != 1.6.1"],
"dash-bio": [
"ParmEd==3.4.3; python_version == '3.6'",
"dash-bio; python_version > '3.0'",
Expand Down
2 changes: 1 addition & 1 deletion tests/dtale/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def run(self, *args, **kwargs):
ignore_duplicate=True,
)

with mock.patch("dtale.app._thread.start_new_thread", mock.Mock()) as mock_thread:
with mock.patch("multiprocessing.Process", mock.Mock()) as mock_thread:
show(data=test_data, subprocess=True, ignore_duplicate=True)
mock_thread.assert_called()

Expand Down
21 changes: 14 additions & 7 deletions tests/dtale/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,18 +435,25 @@ def import_mock(name, *args, **kwargs):

@pytest.mark.unit
def test_shutdown(unittest):
import werkzeug

with app.test_client() as c:
try:
c.get("/shutdown")
with ExitStack() as stack:
mock_os = stack.enter_context(mock.patch("os._exit", mock.Mock()))
resp = c.get("/shutdown").data
assert "Server shutting down..." in str(resp)
mock_os.assert_called()
unittest.fail()
except: # noqa
pass
mock_shutdown = mock.Mock()
resp = c.get(
"/shutdown", environ_base={"werkzeug.server.shutdown": mock_shutdown}
).data
assert "Server shutting down..." in str(resp)
mock_shutdown.assert_called()
if parse_version(werkzeug.__version__) < parse_version("2.1.0"):
mock_shutdown = mock.Mock()
resp = c.get(
"/shutdown", environ_base={"werkzeug.server.shutdown": mock_shutdown}
).data
assert "Server shutting down..." in str(resp)
mock_shutdown.assert_called()


@pytest.mark.unit
Expand Down

0 comments on commit d1c3dd6

Please sign in to comment.