async def results(request: Request, db: Session = Depends(get_db)): """Renders web page showing a list of all the available simulation results. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. db : Session Database Session, needed to interact with database. This is handled internally. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying all the available simulation results. """ # Pull all simulations from database simulations = crud._get_all_simulations(db) simulations = [simulation.__dict__ for simulation in simulations] sim_status_url = app.url_path_for("fronted_simulation_status", sim_id="0") return templates.TemplateResponse( "results.html", { "request": request, "simulations": simulations, "sim_status_url": sim_status_url, } )
async def simulate(request: Request): """Simulate web page. Here the clients can select between the available systems to simulate their preferred one. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying the available systems to be simulated. """ # Available simulations sys_values = [ [sys.value, sys.value.replace("-", " ")] for sys in SimSystem ] # Render template return templates.TemplateResponse( "simulate.html", {"request": request, "sys_values": sys_values} )
async def index(request: Request): """Index frontend web page. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Renders greeting template and hyperlinks to simulation rquests and results. """ route_simulate = app.url_path_for("frontend_simulate") route_results = app.url_path_for("frontend_results") return templates.TemplateResponse( "index.html", { "request": request, "route_simulate": route_simulate, "route_results": route_results } )
async def simulate_id_sim_id(request: Request, sim_id: str): """Shows simulation id after asking for simulation in frontend form. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. sim_id : str ID of the simulation. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying the simulation ID and a hyperlink to further information about the simulation. """ sim_status_url = app.url_path_for( "fronted_simulation_status", sim_id=sim_id ) # This same template is used to show simulation id info or simulation # status info, here we need simulation id info, so we set status=False. return templates.TemplateResponse( "simulation-id-or-status.html", { "request": request, "status": False, "sim_id": sim_id, "sim_status_url": sim_status_url, } )
async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException): """Handles 404 exceptions by rendering a template.""" return templates.TemplateResponse( "404.html", { "request": request, } )
async def results_sim_system_sim_id(request: Request, sim_system: SimSystem, sim_id: str): """Shows graphic results of a simulation in frontend for a given ``sim_id``. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. sim_system : SimSystem System to be simulated. sim_id : str ID of the simulation. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying all the generated plots for a given simulation. """ # Pickle download route route_pickle = app.url_path_for("api_download_pickle", sim_id=sim_id) # Plot download routes plot_paths = app.url_path_for("api_download_plots", sim_id=sim_id) if sim_system == SimSystem.HO.value: coord = PlotQueryValues_HO.coord.value phase = PlotQueryValues_HO.phase.value plot_routes = { coord: plot_paths + "?value=" + coord, phase: plot_paths + "?value=" + phase, } elif sim_system == SimSystem.ChenLee.value: threeD = PlotQueryValues_ChenLee.threeD.value project = PlotQueryValues_ChenLee.project.value plot_routes = { threeD: plot_paths + "?value=" + threeD, project: plot_paths + "?value=" + project } else: plot_routes = {} results_info = { "sim_sys": sim_system.value, "sim_id": sim_id, "route_pickle": route_pickle, } return templates.TemplateResponse( "results-show.html", { "request": request, "plot_routes": plot_routes, **results_info } )
async def simulate_sim_system(request: Request, sim_system: SimSystem, error_message: Optional[str] = ''): """Simulation's form web page. The clients input the desired parameters for the simulation of their choosing. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. sim_system : SimSystem System to be simulated. error_message : str Internally used by the backend to display error messages in forntend form. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying the simulation request form. """ # Get Simulation form schema depending on system and instantiate it. This # schema contains the default values for initial conditions and parameters SysSimForm = SimFormDict[sim_system.value] sim_form = SysSimForm() return templates.TemplateResponse( f"request-simulation.html", { "sim_system": str(sim_system.value), "request": request, "integration_methods": integration_methods, "error_message": error_message, **sim_form.dict(), } )
async def simulate_status_sim_id(request: Request, sim_id: str, db: Session = Depends(get_db)): """Shows simulation status for a given simulation via its ``sim_id``. \f Parameters ---------- request : Request HTTP request, used internally by FastAPI. sim_id : str ID of the simulation. db : Session Database Session, needed to interact with database. This is handled internally. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` Template displaying the simulation status and a hyperlinks to simulation results in several formats. If simulation id is not available (not yet in database), renders a message about the situation. """ try: sim_info = crud._get_simulation(db, sim_id) username = crud._get_username(db, sim_info.user_id) except: return templates.TemplateResponse( "simulation-id-or-status.html", { "request": request, "sim_id": sim_id, "status": True, "not_finished": True, } ) params = crud._get_parameters(db, sim_id, ParamType.param.value) plot_query_values = crud._get_plot_query_values(db, sim_id) plots_url = app.url_path_for( "api_download_plots", sim_id=sim_id ) path_plots = [plots_url + "?value=" + value for value in plot_query_values] ini_cndtn = crud._get_parameters(db, sim_id, ParamType.ini_cndtn.value) # This same template is used to show simulation id info or simulation # status info, here we need simulation status so we set status=True. return templates.TemplateResponse( "simulation-id-or-status.html", { "request": request, "username": username[0], "status": True, "ini_cndtn": ini_cndtn, "params": params, "path_plots": path_plots, **sim_info.__dict__ } )
async def simulate_sim_system_post(request: Request, sim_system: SimSystem, background_tasks: BackgroundTasks, db: Session = Depends(get_db), sim_sys: SimSystem = Form(...), username: str = Form(...), t0: float = Form(...), tf: float = Form(...), dt: float = Form(...), t_steps: int = Form(...), method: IntegrationMethods = Form(...)): """Receives the simulation request information from the frontend form and requests the simulation to the backend. \f This route receives the form requesting a simulation (filled in frontend via GET in route ``/simulate/{sim_system}``). The simulation is internally requested using the function :func:`simulation_api.controller.tasks._api_simulation_request`. Finally the client is redirected to the "Simulation ID" frontend web page, where further information about the simulation is displayed. Parameters ---------- request : Request HTTP request, used internally by FastAPI. sim_system : SimSystem System to be simulated. background_tasks : BackgroundTasks Needed to request simulation to the backend (in background). Handled internally by the API. db : Session Database Session, needed to interact with database. This is handled internally. sim_sys : SimSystem Form entry: system to be simulated. Must be one of the members of SimSystem. username : str Form entry: name of the user requesting the simulation. t0 : float Form entry: initial time of simulation. tf : float Form entry: final time of simulation. dt : float Form entry: timestep of simulation. t_steps: Form entry: number of time steps. If different from 0 or ``None``, ``dt`` is ignored. method : IntegrationMethods Form entry: method of integration. Must be a member of IntegrationMethods. Returns ------- ``fastapi.templating.Jinja2Templates.TemplateResponse`` or ``starlette.responses.RedirectResponse`` If the client made a mistake filling the form renders the simulation request form again. Otherwise, redirects the client to "Simulation ID" frontend web page. Note ---- The values of the form accessed by ``fastapi.Form`` are only declared as parameters so that pydantic checks the types, but they are not used directly to request the simulation. Here, we access the form directly by using the method :meth:`fastapi.Request.form()` as can be seen in the first lines of this function. This allows us a better control over the data and also to handle different type of forms –which depend on the simulation because parameters and initial conditions are intrinsically different for different systems. """ # Here we get form directly from request. # NOTE The other method is using FastAPI's Form function, but here it is # easier to access the form directly from request (as below). # NOTE This method does NOT provide pydantic type checking. However, type # checking is more or less provided in the frontend by the form itself. form = await request.form() form = form.__dict__['_dict'] ########################## Check for some errors ########################## error_message = "" if not t_steps: if not t0 < tf: error_message = "in Time Stamp, t0 must be less than tf" elif not tf - t0 > dt or dt <= 0: error_message = "in Time Stamp, dt must be within 0 and tf - t0" # Maximum number of steps: 2e5 maxsteps = 2e5 maxsteps_reached_message = "maximum number of time steps is 2e5. " \ "Increase dt or decrease t_steps." if t_steps and t_steps > maxsteps: error_message = maxsteps_reached_message elif (tf - t0) / dt > maxsteps: error_message = maxsteps_reached_message # Check Chen-Lee parameters if sim_sys.value == SimSystem.ChenLee.value: a, b, c = [float(form[f"param{i}"]) for i in range(3)] if not _check_chen_lee_params(a, b, c): error_message = "Chen-Lee parameters must satisfy a > 0, and " \ "b < 0, and c < 0 and a < -(b + c)" if error_message: SysSimForm = SimFormDict[sim_system.value] sim_form = SysSimForm() return templates.TemplateResponse( "request-simulation.html", { "sim_system": str(sim_system.value), "request": request, "integration_methods": integration_methods, "error_message": error_message, **sim_form.dict(), }, status_code=400 ) ############################## End of check ############################### # Change format from form data to SimRequest schema. sim_request = _sim_form_to_sim_request(form) # BUG? Is the simulation request as done in the next lines of code OK? # # FIXME # # In the first place I was thinking of asking the API to somehow internally # go to route "/api/simulate/{sim_system}" BUT I could not do that. # I tried using the methods shown below: # # NOTE Method 1) using request.post() # request_simulation_url = app.url_path_for( # "api_request_sim", # sim_system=sim_request.system.value # ) # req = requests.post("http://0.0.0.0:5700" + request_simulation_url, data=sim_request.json()) # # NOTE Method 2) using RedirectResponse # req = RedirectResponse(request_simulation_url) # # Neither of the methods worked so I solved it by just doing the same as in # route "/api/simulate/{sim_system}" (calling _api_simulation_request as # done below) # Request simulation from backend and get sim_id_response sim_id_response = _api_simulation_request(sim_sys, sim_request, background_tasks, db) # Redirect client to 'success' page # POST/REDIRECT/GET Strategy with 303 status code # https://en.wikipedia.org/wiki/Post/Redirect/Get simulation_id_url = app.url_path_for( "frontend_simulation_id", sim_id=sim_id_response.sim_id ) req = RedirectResponse(simulation_id_url, status_code=HTTP_303_SEE_OTHER) return req