Skip to content

[Feature]: Enable Access to ASGI Session State (Django, FastAPI, etc.) in Shiny for Python #1981

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

Open
vnijs opened this issue Apr 28, 2025 · 1 comment
Labels
enhancement New feature or request needs-triage

Comments

@vnijs
Copy link

vnijs commented Apr 28, 2025

I'm building Django sites for several of my graduate MSBA classes. Being able to incorporate Shiny apps for GenAI chat, ML, etc. would be so much easier—at least for me—than having to use Vue or React. To make that work well, however, I need to maintain student state as they complete exercises and progress through the class materials.

I tried to use Django's session management system but ran into several challenges:

  • While I can access Django session state in the Shiny app_ui (via the request object), I cannot access it in the Shiny server function where the app logic and state updates happen.
  • There’s no built-in way for Shiny for Python to read or write Django session data from within the server function, making it hard to persist user progress or restore state.
  • I tried workarounds with custom middleware an HTTP calls but was not success. It seems likely that any such solution may insufficiently stable for production or teaching.

It would be incredibly helpful if Shiny for Python could natively support reading and writing session state from ASGI-compatible frameworks (like Django, FastAPI, or Starlette) inside the server function, so user state (e.g., student progress through the course) can be managed easily. This would benefit not just Django users, but anyone integrating Shiny with modern Python web frameworks.

I'd be happy to provide a cleaned up version of that I have been working on if that would be useful.

Thank you for considering this!

Solution

Allow Shiny for Python apps to access and modify the session state from the parent ASGI framework (e.g., FastAPI, Starlette, Django) directly within the Shiny server function.

How it should work:

  • When a Shiny app is mounted within an ASGI app (such as FastAPI or Starlette), the session or user state from the parent app should be accessible in both app_ui and server.
  • The Shiny server function should receive a session object that allows reading and writing to the parent ASGI session (e.g., for user progress, preferences, authentication, etc.).
  • State changes in Shiny should be persisted back to the parent ASGI session automatically.

Ideally, the session parameter in the Shiny server function would be an object (e.g., SessionASGI) that exposes the parent ASGI session dict, so we can read and write session data as needed.

Shiny for Python could achieve this by looking for a session object in the ASGI scope (such as scope["session"], which is set by common ASGI session middleware in FastAPI, Starlette, or Django). If found, Shiny would pass this object to the server function, allowing seamless integration with the parent app’s session management.

Example (with FastAPI):

from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from shiny import App, ui, render
import uvicorn
import webbrowser

# from shiny import SessionASGI  # (if implemented)

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="mysecret")


@app.get("/set-user")
async def set_user(request: Request):
    request.session["user_name"] = "Alice"
    return RedirectResponse("/shiny")


def app_ui(request: Request):
    # Access session in UI (already possible)
    user_name = request.session.get("user_name", "Guest")
    return ui.page_fluid(
        ui.h2(f"Welcome, {user_name}!"),
        ui.input_text("answer", "Your answer:"),
        ui.output_text_verbatim("feedback"),
    )


def server(input, output, session): # would be ..., session: SessionASGI
    # NEW: Access and update FastAPI/Starlette session directly
    user_name = session["user_name"]  # Read from ASGI session

    @output
    @render.text
    def feedback():
        # Save answer to session
        session["answer"] = input.answer()
        return f"Thanks, {user_name}! Your answer was saved."

    def save_progress():
        # Save the latest answer to the ASGI session
        session["last_answer"] = input.answer()
        # Optionally, do other cleanup or logging

    session.on_ended(save_progress)

shiny_app = App(app_ui, server)
app.mount("/shiny", shiny_app)


if __name__ == "__main__":
    host = "127.0.0.1"
    port = 8000
    # Open the /set-user URL in the default browser
    webbrowser.open(f"http://{host}:{port}/set-user")
    # Start the server
    uvicorn.run(app, host=host, port=port, log_level="info")

Contribution? (Optional)

Yes, I can review/test.

@vnijs vnijs added enhancement New feature or request needs-triage labels Apr 28, 2025
@schloerke
Copy link
Collaborator

Ideally, the session parameter in the Shiny server function would be an object (e.g., SessionASGI) that exposes the parent ASGI session dict, so we can read and write session data as needed.

Being session specific is the difficult part.


I'm answering under the guise of a non-expert of Starlette...

I don't see a way to have directly sharable session context between the different routes via https://www.starlette.io/routing/ . I see the ability to enhance the request and response, but nothing about the app as a whole.

One way out is through cookies. This could be utilized by an independent FastAPI as well as Shiny. But within each sub-app, you'd need to handle populating the session's info. Within py-shiny, I'd recommend not storing any cross-sub-app data and instead ask the database each time. (You could cache it within the session, but you'll need a cache-invalidation mechanism. 🤞 )


Looks like Starlette has SessionMiddleware: https://www.starlette.io/middleware/#sessionmiddleware

You'd be able to access the request.session in the app_ui's request object. Within the server, my best guess is to look within session.http_conn (untested).


Leaving the issue as need-triage until a better pair of eyes looks. (But I'm currently not hopeful for a clean solution outside of Starlette's SessionMiddleware middlware)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request needs-triage
Projects
None yet
Development

No branches or pull requests

2 participants