Hello, I’m relatively new to Python (~1 year) and this is my first post, so apologies for any terminology/content/formatting issues. Preemptively, I’ll note that I’ve seen other threads demonstrating dash-auth and flask login, but I’m not sure if/how those authentication flows can be adapted to database authentication.
I’m trying to incorporate user interactive database authentication into a multipage app. I generally structure my apps consistent with the best practices outlined here, and also include a module (e.g., utils.conn.py) with SQLAlchemy engines pointing to my org’s “on prem” servers. Each page module for my dashboard imports the relevant engine (from utils.conn) and then loads the data prior to definition of the page layout, which often times depends in part on the underlying data (e.g., the options for a dropdown component would depend on certain values in the data).
However, for reasons that are out of my control, in order to deploy my dash app, I need to point the database engines to my org’s Azure SQL managed instance, which requires user interactive authentication (i.e., getting device credentials , getting an access token, then establishing the database connection with token-based authentication). Although I’ve written scripts that can, in isolation, acquire the access token, store it in browser, and establish the database connection, I’m having issues tying everything together within the app.
The critical issue is that the app can’t run until the database connection is established (because the page layouts depend in part on the imported data), but the database connection can’t be established until the app runs and presents the user with the authentication instructions and code. (Note that when I run this locally I can print the authentication instructions to the console log, but that won’t be possible when the app is deployed.) What is the optimal way to resolve this apparent catch-22? Can the file structure outlined in the best practices post still be used? Would I need to modify each page layout (wrap page layouts in functions or something), or is there some sort of way to delay reading the page layouts until database authentication has been established? I’m currently researching flask blueprints, but not sure whether I’m on the right track.
Although this is more of a conceptual question, below is some sample code that works when running locally but fails when I try to restructure the authentication process into the app components (using dcc.Store to check for/acquire token and dbc.Modal to relay messages), resulting in circular imports (ImportError: cannot import name ‘engine’ from partially initialized module ‘src.app’ (most likely due to a circular import)).
Thank you for any advice/assistance.
file utils.conn.py
from msal import PublicClientApplication
from sqlalchemy import URL, create_engine
# public client app
public_client_app = PublicClientApplication(
client_id=CLIENT,
authority="https://login.microsoftonline.com/{}".format(TENANT)
)
# get device creds - this prompts the user to go to a website and enter a code
device_flow = public_client_app.initiate_device_flow(scopes=SCOPES)
# this returns, among other things, the access token
auth_result = public_client_app.acquire_token_by_device_flow(flow=device_flow)
# once the token is converted into bytes, it can be used to establish the database connection
conn_string = "DRIVER={};SERVER={};ENCRYPT=yes;".format(DRIVER, SERVER)
conn_url = URL.create("mssql+pyodbc", query={"odbc_connect": conn_string})
engine = create_engine(
conn_url,
connect_args={"attrs_before": {SQL_COPT_SS_ACCESS_TOKEN: token}},
fast_executemany=True
)
file pages.somepage.py
from utils.conn import engine
# data imports
with engine.connect() as connection:
query_stuff = text(r"""
SELECT *
FROM [DATABASE].[DBO].[TABLE_NAME]
""")
df_stuff = pd.read_sql_query(query_stuff, connection)
register_page(__name__, name="somepage")
layout = dbc.Container([...dcc/dbc Components based on df_stuff], fluid=True, className="dbc")
app.py
# imports
...
app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG], use_pages=True)
app.layout = dbc.Container(
[
navbar,
page_container
],
fluid=True,
style={"padding": 0},
className="dbc"
)
if __name__ == "__main__":
app.run(debug=True)