From a2ee56eb8b0e217d9fc8dfc179f8ff07476e9cfb Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 21:59:30 -0400 Subject: [PATCH 01/14] added UUID session cookie value and route wrapper --- src/wsgi.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/wsgi.py b/src/wsgi.py index 13734e3..c6e0fb3 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -1,12 +1,19 @@ -import flask +# import flask import logging +from flask import Flask, render_template, redirect, session, flash from werkzeug.debug import DebuggedApplication from datetime import datetime +from os import urandom +from uuid import uuid4 +from functools import wraps from tesla_ver.bubble_chart.bubble_chart import generate_bubble_chart from tesla_ver.data_uploading.data_uploading import generate_data_uploading -server = flask.Flask(__name__, static_folder="homepage/build/static", template_folder="homepage/build/") +server = Flask(__name__, static_folder="homepage/build/static", template_folder="homepage/build/") + +# set a new random secret key for sessions on each app launch +server.secret_key = urandom(24) # If application is being run through gunicorn, pass logging through to gunicorn if __name__ != "__main__": @@ -29,25 +36,41 @@ server.logger.debug("✅ Bubble Chart Screen created and connected") +def check_uuid_initialized(redirect_func): + @wraps(redirect_func) + def wrapped(*args, **kwargs): + uuid = session.get('uuid') + if uuid: + return redirect_func(*args, **kwargs) + else: + # session['uuid'] = str(uuid4().hex) + session['uuid'] = None + return redirect_func(*args, **kwargs) + return wrapped + + @server.route("/") +@check_uuid_initialized def index(): """Renders the landing page.""" server.logger.debug("rendering homepage") - return flask.render_template("index.html") + return render_template("index.html") @server.route("/bubblechart.html") +@check_uuid_initialized def render_bubble_chart(): """Redirects to the Dash Bubble chart.""" server.logger.debug("redirecting to bubblechart") - return flask.redirect("/bubblechart.html") + return redirect("/bubblechart.html") @server.route("/datauploading.html") +@check_uuid_initialized def render_data_uploading(): server.logger.debug("redirecting to data uploader") - return flask.redirect("/datauploading.html") + return redirect("/datauploading.html") if __name__ == "__main__": From 684412d1ec3771f097ab50258fe1fd5faff07ada Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:03:03 -0400 Subject: [PATCH 02/14] added UUID logging and refactored main flask app --- src/tesla_ver/bubble_chart/bubble_chart.py | 2 +- src/wsgi.py | 24 ++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/tesla_ver/bubble_chart/bubble_chart.py b/src/tesla_ver/bubble_chart/bubble_chart.py index 4c032a1..e1c5d25 100644 --- a/src/tesla_ver/bubble_chart/bubble_chart.py +++ b/src/tesla_ver/bubble_chart/bubble_chart.py @@ -13,7 +13,7 @@ from tesla_ver.redis_manager import redis_manager -def generate_bubble_chart(server): +def generate_charting(server): app = dash.Dash(__name__, server=server, url_base_pathname="/bubblechart.html/") app.layout = LAYOUT diff --git a/src/wsgi.py b/src/wsgi.py index c6e0fb3..8fd7487 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -1,12 +1,12 @@ # import flask import logging -from flask import Flask, render_template, redirect, session, flash +from flask import Flask, render_template, redirect, session from werkzeug.debug import DebuggedApplication from datetime import datetime from os import urandom from uuid import uuid4 from functools import wraps -from tesla_ver.bubble_chart.bubble_chart import generate_bubble_chart +from tesla_ver.bubble_chart.bubble_chart import generate_charting from tesla_ver.data_uploading.data_uploading import generate_data_uploading @@ -32,7 +32,7 @@ server.logger.debug("✅ Data Uploading Screen created and connected") # Creates the bubble chart and connects it to the flask server -generate_bubble_chart(server=server) +generate_charting(server=server) server.logger.debug("✅ Bubble Chart Screen created and connected") @@ -55,24 +55,26 @@ def wrapped(*args, **kwargs): def index(): """Renders the landing page.""" server.logger.debug("rendering homepage") + server.logger.debug("User with UUID:" + session.get('uuid') + "connected to homepage") return render_template("index.html") +@server.route("/datauploading.html") +@check_uuid_initialized +def render_data_uploading(): + server.logger.debug("redirecting to data uploader") + server.logger.debug("User with UUID:" + session.get('uuid') + "connected to data uploading") + return redirect("/datauploading.html") + @server.route("/bubblechart.html") @check_uuid_initialized -def render_bubble_chart(): +def render_charting_page(): """Redirects to the Dash Bubble chart.""" server.logger.debug("redirecting to bubblechart") + server.logger.debug("User with UUID:" + session.get('uuid') + "connected to charting") return redirect("/bubblechart.html") -@server.route("/datauploading.html") -@check_uuid_initialized -def render_data_uploading(): - server.logger.debug("redirecting to data uploader") - return redirect("/datauploading.html") - - if __name__ == "__main__": server.logger.debug("Server starting") server.run(debug=True) From ffc8dd3bd49eb7b48432c3dad2a2c43698d6a21f Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:04:46 -0400 Subject: [PATCH 03/14] changed logging messages --- src/wsgi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wsgi.py b/src/wsgi.py index 8fd7487..f8ade46 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -55,14 +55,14 @@ def wrapped(*args, **kwargs): def index(): """Renders the landing page.""" server.logger.debug("rendering homepage") - server.logger.debug("User with UUID:" + session.get('uuid') + "connected to homepage") + server.logger.debug("User with UUID:" + session.get('uuid') + "connecting to homepage") return render_template("index.html") @server.route("/datauploading.html") @check_uuid_initialized def render_data_uploading(): server.logger.debug("redirecting to data uploader") - server.logger.debug("User with UUID:" + session.get('uuid') + "connected to data uploading") + server.logger.debug("User with UUID:" + session.get('uuid') + "connecting to data uploading") return redirect("/datauploading.html") @@ -71,7 +71,7 @@ def render_data_uploading(): def render_charting_page(): """Redirects to the Dash Bubble chart.""" server.logger.debug("redirecting to bubblechart") - server.logger.debug("User with UUID:" + session.get('uuid') + "connected to charting") + server.logger.debug("User with UUID:" + session.get('uuid') + "connecting to charting") return redirect("/bubblechart.html") From 1c231f8fa002b3a02c026378d222d424b1f90d8c Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:23:55 -0400 Subject: [PATCH 04/14] documented check_uuid_initialized wrapper --- src/wsgi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wsgi.py b/src/wsgi.py index f8ade46..c5cee45 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -36,6 +36,9 @@ server.logger.debug("✅ Bubble Chart Screen created and connected") + + +# Wrapper to check if UUID is initialized, and initialize it if not def check_uuid_initialized(redirect_func): @wraps(redirect_func) def wrapped(*args, **kwargs): @@ -43,8 +46,7 @@ def wrapped(*args, **kwargs): if uuid: return redirect_func(*args, **kwargs) else: - # session['uuid'] = str(uuid4().hex) - session['uuid'] = None + session['uuid'] = str(uuid4().hex) return redirect_func(*args, **kwargs) return wrapped From 7539140a3ce59e941fb3bff71d695b1990ed6776 Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:24:56 -0400 Subject: [PATCH 05/14] added user session uuid based redis data writing --- src/tesla_ver/data_uploading/data_uploading.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tesla_ver/data_uploading/data_uploading.py b/src/tesla_ver/data_uploading/data_uploading.py index 39e8538..4c3d7dc 100644 --- a/src/tesla_ver/data_uploading/data_uploading.py +++ b/src/tesla_ver/data_uploading/data_uploading.py @@ -4,6 +4,7 @@ import numpy as np import pyarrow as pa +from flask import session from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate @@ -126,20 +127,24 @@ def push_to_redis(button_clicks, data_dict, selected_columns, selected_rows): serialization_context = pa.default_serialization_context() + session_uuid = session.get('uuid') + redis_manager.redis.set( - "data_numeric", + session_uuid + "_numeric_data", serialization_context.serialize(df[sorted(set(["Year", "Subject", *selected_columns]))]) .to_buffer() .to_pybytes(), ) - server.logger.debug("redis numeric data set") + server.logger.debug("redis numeric data set at key: " + session_uuid + "_numeric_data") # This may be an empty dataframe (checking is needed once the mdata starts getting used) redis_manager.redis.set( - "data_mdata", + session.get('uuid') + "_metadata", serialization_context.serialize(df[["Year", "Subject", *mdata_cols]]).to_buffer().to_pybytes(), ) - server.logger.debug("redis mdata set") + + server.logger.debug("redis metadata set at key: " + session_uuid + "_metadata") + return {"visibility": "visible"} return app From 1356ff8649e9e7159341b1d65de8df627b4d53ff Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:25:24 -0400 Subject: [PATCH 06/14] added user session uuid based graph data loading --- src/tesla_ver/bubble_chart/bubble_chart.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tesla_ver/bubble_chart/bubble_chart.py b/src/tesla_ver/bubble_chart/bubble_chart.py index e1c5d25..2afcadd 100644 --- a/src/tesla_ver/bubble_chart/bubble_chart.py +++ b/src/tesla_ver/bubble_chart/bubble_chart.py @@ -4,6 +4,8 @@ import pandas as pd import pyarrow as pa +from flask import session + from ast import literal_eval from plotly.graph_objects import Scatter from dash.dependencies import Input, Output, State @@ -64,9 +66,12 @@ def extract_mdata(df, x_column_name): context = pa.default_serialization_context() - if redis_manager.redis.exists("data_numeric"): - df = context.deserialize(redis_manager.redis.get("data_numeric")) - redis_manager.redis.flushdb() + session_uuid = session.get('uuid') + + if redis_manager.redis.exists(session_uuid + "_numeric_data"): + server.logger.debug("reading data from redis key: " + session_uuid + "_numeric_data") + df = context.deserialize(redis_manager.redis.get(session_uuid + "_numeric_data")) + # redis_manager.redis.flushdb() else: # Because of the need to return data matching all the different areas, displaying an error message # to the end user would require either another callback to chain with, which would complicate the code and From 52866f00251089ff567f025633808128c98f133f Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:27:56 -0400 Subject: [PATCH 07/14] documented session_uuid reads --- src/tesla_ver/bubble_chart/bubble_chart.py | 1 + src/tesla_ver/data_uploading/data_uploading.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/tesla_ver/bubble_chart/bubble_chart.py b/src/tesla_ver/bubble_chart/bubble_chart.py index 2afcadd..3c27f05 100644 --- a/src/tesla_ver/bubble_chart/bubble_chart.py +++ b/src/tesla_ver/bubble_chart/bubble_chart.py @@ -66,6 +66,7 @@ def extract_mdata(df, x_column_name): context = pa.default_serialization_context() + # Gets session UUID to get user specific data session_uuid = session.get('uuid') if redis_manager.redis.exists(session_uuid + "_numeric_data"): diff --git a/src/tesla_ver/data_uploading/data_uploading.py b/src/tesla_ver/data_uploading/data_uploading.py index 4c3d7dc..3e2a825 100644 --- a/src/tesla_ver/data_uploading/data_uploading.py +++ b/src/tesla_ver/data_uploading/data_uploading.py @@ -127,6 +127,8 @@ def push_to_redis(button_clicks, data_dict, selected_columns, selected_rows): serialization_context = pa.default_serialization_context() + + # Gets session UUID to write user specific data session_uuid = session.get('uuid') redis_manager.redis.set( From b967ab6ceb5a5e993d7f5f73f2f46314fe43daed Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:30:56 -0400 Subject: [PATCH 08/14] refactored and improved data reading documentation and redis cleanup --- src/tesla_ver/bubble_chart/bubble_chart.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tesla_ver/bubble_chart/bubble_chart.py b/src/tesla_ver/bubble_chart/bubble_chart.py index 3c27f05..6265f85 100644 --- a/src/tesla_ver/bubble_chart/bubble_chart.py +++ b/src/tesla_ver/bubble_chart/bubble_chart.py @@ -70,9 +70,16 @@ def extract_mdata(df, x_column_name): session_uuid = session.get('uuid') if redis_manager.redis.exists(session_uuid + "_numeric_data"): - server.logger.debug("reading data from redis key: " + session_uuid + "_numeric_data") + + # User specific key for data stored in redis + numeric_data_key = session_uuid + "_numeric_data" + + server.logger.debug("reading data from redis key: " + numeric_data_key) + df = context.deserialize(redis_manager.redis.get(session_uuid + "_numeric_data")) - # redis_manager.redis.flushdb() + + # Clear user specific data after read + redis_manager.redis.delete(numeric_data_key) else: # Because of the need to return data matching all the different areas, displaying an error message # to the end user would require either another callback to chain with, which would complicate the code and From 807a8d13d6e24f91184ae60bec4f422e1b895225 Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:35:09 -0400 Subject: [PATCH 09/14] added feature flag for mdata storage in redis --- src/tesla_ver/data_uploading/data_uploading.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/tesla_ver/data_uploading/data_uploading.py b/src/tesla_ver/data_uploading/data_uploading.py index 3e2a825..7a50801 100644 --- a/src/tesla_ver/data_uploading/data_uploading.py +++ b/src/tesla_ver/data_uploading/data_uploading.py @@ -139,13 +139,18 @@ def push_to_redis(button_clicks, data_dict, selected_columns, selected_rows): ) server.logger.debug("redis numeric data set at key: " + session_uuid + "_numeric_data") - # This may be an empty dataframe (checking is needed once the mdata starts getting used) - redis_manager.redis.set( - session.get('uuid') + "_metadata", - serialization_context.serialize(df[["Year", "Subject", *mdata_cols]]).to_buffer().to_pybytes(), - ) + # Metadata storage feature flag + + store_mdata = False + + if store_mdata: + # This may be an empty dataframe (checking is needed once the mdata starts getting used) + redis_manager.redis.set( + session.get('uuid') + "_metadata", + serialization_context.serialize(df[["Year", "Subject", *mdata_cols]]).to_buffer().to_pybytes(), + ) - server.logger.debug("redis metadata set at key: " + session_uuid + "_metadata") + server.logger.debug("redis metadata set at key: " + session_uuid + "_metadata") return {"visibility": "visible"} From 943333182832c012f5799ce32813a1361f96a192 Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Sat, 22 May 2021 22:47:41 -0400 Subject: [PATCH 10/14] renamed bubble_chart to charting --- src/tesla_ver/{bubble_chart => charting}/__init__.py | 0 src/tesla_ver/{bubble_chart => charting}/assets/bWLwgP.css | 0 src/tesla_ver/{bubble_chart => charting}/assets/layout.css | 0 .../{bubble_chart => charting}/assets/materialize.css | 0 .../{bubble_chart/bubble_chart.py => charting/charting.py} | 4 ++-- .../bubble_chart_layout.py => charting/charting_layout.py} | 0 src/wsgi.py | 2 +- 7 files changed, 3 insertions(+), 3 deletions(-) rename src/tesla_ver/{bubble_chart => charting}/__init__.py (100%) rename src/tesla_ver/{bubble_chart => charting}/assets/bWLwgP.css (100%) rename src/tesla_ver/{bubble_chart => charting}/assets/layout.css (100%) rename src/tesla_ver/{bubble_chart => charting}/assets/materialize.css (100%) rename src/tesla_ver/{bubble_chart/bubble_chart.py => charting/charting.py} (98%) rename src/tesla_ver/{bubble_chart/bubble_chart_layout.py => charting/charting_layout.py} (100%) diff --git a/src/tesla_ver/bubble_chart/__init__.py b/src/tesla_ver/charting/__init__.py similarity index 100% rename from src/tesla_ver/bubble_chart/__init__.py rename to src/tesla_ver/charting/__init__.py diff --git a/src/tesla_ver/bubble_chart/assets/bWLwgP.css b/src/tesla_ver/charting/assets/bWLwgP.css similarity index 100% rename from src/tesla_ver/bubble_chart/assets/bWLwgP.css rename to src/tesla_ver/charting/assets/bWLwgP.css diff --git a/src/tesla_ver/bubble_chart/assets/layout.css b/src/tesla_ver/charting/assets/layout.css similarity index 100% rename from src/tesla_ver/bubble_chart/assets/layout.css rename to src/tesla_ver/charting/assets/layout.css diff --git a/src/tesla_ver/bubble_chart/assets/materialize.css b/src/tesla_ver/charting/assets/materialize.css similarity index 100% rename from src/tesla_ver/bubble_chart/assets/materialize.css rename to src/tesla_ver/charting/assets/materialize.css diff --git a/src/tesla_ver/bubble_chart/bubble_chart.py b/src/tesla_ver/charting/charting.py similarity index 98% rename from src/tesla_ver/bubble_chart/bubble_chart.py rename to src/tesla_ver/charting/charting.py index 6265f85..b27f06d 100644 --- a/src/tesla_ver/bubble_chart/bubble_chart.py +++ b/src/tesla_ver/charting/charting.py @@ -11,7 +11,7 @@ from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -from tesla_ver.bubble_chart.bubble_chart_layout import LAYOUT +from tesla_ver.charting.charting_layout import LAYOUT from tesla_ver.redis_manager import redis_manager @@ -86,7 +86,7 @@ def extract_mdata(df, x_column_name): # likely add a small bit of latency, which is this is left as a console-based error message. raise PreventUpdate("Data could not be loaded from redis") - server.logger.debug("redis db flushed") + server.logger.debug("redis data for UUID " + session_uuid + " flushed") mdata = extract_mdata(df, "Year") df.rename(columns={"Year": "X"}, inplace=True) diff --git a/src/tesla_ver/bubble_chart/bubble_chart_layout.py b/src/tesla_ver/charting/charting_layout.py similarity index 100% rename from src/tesla_ver/bubble_chart/bubble_chart_layout.py rename to src/tesla_ver/charting/charting_layout.py diff --git a/src/wsgi.py b/src/wsgi.py index c5cee45..de2e439 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -6,7 +6,7 @@ from os import urandom from uuid import uuid4 from functools import wraps -from tesla_ver.bubble_chart.bubble_chart import generate_charting +from tesla_ver.charting.charting import generate_charting from tesla_ver.data_uploading.data_uploading import generate_data_uploading From d7a57936ec99c6449a8ce5db6919667a7f3a6c7a Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Thu, 27 May 2021 14:24:40 -0400 Subject: [PATCH 11/14] added TODO comments about using metadata in charts --- src/tesla_ver/charting/charting.py | 3 +++ src/tesla_ver/data_uploading/data_uploading.py | 1 + 2 files changed, 4 insertions(+) diff --git a/src/tesla_ver/charting/charting.py b/src/tesla_ver/charting/charting.py index b27f06d..80499b8 100644 --- a/src/tesla_ver/charting/charting.py +++ b/src/tesla_ver/charting/charting.py @@ -16,6 +16,9 @@ def generate_charting(server): + + # TODO Use metadata in charting/graphing system, reenable feature flag when metadata is used for charting/graphing + app = dash.Dash(__name__, server=server, url_base_pathname="/bubblechart.html/") app.layout = LAYOUT diff --git a/src/tesla_ver/data_uploading/data_uploading.py b/src/tesla_ver/data_uploading/data_uploading.py index 7a50801..2f55381 100644 --- a/src/tesla_ver/data_uploading/data_uploading.py +++ b/src/tesla_ver/data_uploading/data_uploading.py @@ -140,6 +140,7 @@ def push_to_redis(button_clicks, data_dict, selected_columns, selected_rows): server.logger.debug("redis numeric data set at key: " + session_uuid + "_numeric_data") # Metadata storage feature flag + # TODO Use metadata in charting/graphing system, reenable feature flag when metadata is used for charting/graphing store_mdata = False From be2baa6bb99b6120f8ede914bb2d5180011cfe85 Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Thu, 27 May 2021 14:56:26 -0400 Subject: [PATCH 12/14] removed unused commented import --- src/wsgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wsgi.py b/src/wsgi.py index de2e439..24f82e9 100644 --- a/src/wsgi.py +++ b/src/wsgi.py @@ -1,4 +1,3 @@ -# import flask import logging from flask import Flask, render_template, redirect, session from werkzeug.debug import DebuggedApplication From a77bf59b955ca473089f16df9d5da5eed6cbe79f Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Wed, 23 Jun 2021 19:13:51 -0400 Subject: [PATCH 13/14] updated readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 93e93e3..cfe6b10 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ Make sure that no conda environments containing `tesla-ver` in the name exist, a --- +## Notes + +The values for time must be integers. + ## Users Currently, the Tesla-ver server is not running, but may be available as a web app in future iterations From 6e408553f1aa474e0c9f7214619a79476eb2a07a Mon Sep 17 00:00:00 2001 From: Alexander Kyim Date: Wed, 23 Jun 2021 19:14:24 -0400 Subject: [PATCH 14/14] added max time value checks --- src/tesla_ver/charting/charting.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tesla_ver/charting/charting.py b/src/tesla_ver/charting/charting.py index 80499b8..66227e7 100644 --- a/src/tesla_ver/charting/charting.py +++ b/src/tesla_ver/charting/charting.py @@ -185,11 +185,15 @@ def update_figure(time_value, y_column_name, x_column_name, json_data, marks, md if time_value == None: time_value = int(mdata.get("time_min")) + if time_value == int(mdata.get("time_max")): + time_value + # Loads dataframe at specific time value by getting the time as a key from a dictionary, # then evaluates it to turn it into a python dictionary, and then loads it as a dataframe try: df_by_time = pd.DataFrame.from_dict(literal_eval(json.loads(json_data).get(str(time_value)))) except ValueError: + server.logger.debug(f"❌ unable to load dataframe at time value: {str(time_value)}") pass server.logger.debug("✅ dataframe filtered by time") @@ -244,12 +248,15 @@ def play_pause_switch(n_clicks): return [play_status, play_bool] @app.callback( - Output("time-slider", "value"), [Input("play-interval", "n_intervals")], [State("time-slider", "value")] + [Output("time-slider", "value"), Output("play-interval", "disabled")], [Input("play-interval", "n_intervals")], [State("time-slider", "value"), State("df-mdata", "data")] ) - def play_increment(n_intervals, time_value): + def play_increment(n_intervals, time_value, mdata): if time_value is None: raise PreventUpdate - return str(int(time_value) + 1) + if int(time_value) == int(mdata.get("time_max")): + server.logger.debug(f'Max time value reached, returning max value') + return [str(int(time_value)), True] + return [str(int(time_value) + 1), False] @app.callback( [Output("left-line-plot-graph", "figure"), Output("right-line-plot-graph", "figure")],