--- last_review: "2025-01-01" last_reviewer: "-" documented_code: [ ] --- ```{tags} tutorial, administrator, developer, server-side-scripting ``` # Asynchronous server-side scripting :::{note} This page has been migrated from the old documentation, and has not yet been fully revised. There might be inconsistencies or errors when using with current LinkAhead versions. ::: % TODO: Issue: https://gitlab.indiscale.com/caosdb/src/linkahead-docs/-/issues/86 % TODO: Link to and ensure common thread with serverside scripting, Split Tutorial + Explanation Sometimes, server-side scripts can take a long time for execution (e.g., crawling many files, writing many entities, communicating with external services) so that naively executing them from the web interface may result in connection timeouts. In such cases, we might want to execute these processes asynchronously so that the script simply runs in the background for as long as it takes. While LinkAhead doesn't support asynchronous server-side scripts natively (yet), in this tutorial, we will use LinkAhead's server-side scripting API to effectively achieve the same thing. It is meant for users who are already familiar with the server-side scripting infrastructure and know how to write and execute (Python) server-side scripts. For simplicity, let's consider the following: We have a very simple script that takes a `name` and a `description` and inserts a new RecordType if it doesn't exist already. To emulate long execution times, we may make the script sleep for a few seconds. In total, this script may look like the following. ```python #!/usr/bin/env python3 from time import sleep import linkahead as db from caosadvancedtools.serverside import helper def main(): parser = helper.get_argument_parser() parser.add_argument("name", help="Name of the new RT") parser.add_argument("description", help="Description of the new RT") sleep(30) args = parser.parse_args() db.configure_connection(auth_token=args.auth_token) num_rt = db.execute_query(f"COUNT RECORDTYPE WITH name='{args.name}'") if num_rt > 0: return db.RecordType(name=args.name, description=args.description).insert() if __name__ == "__main__": main() ``` This script can be called from the web interface as explained in the server-side scripting documentation, will run for at least 30 seconds, and then insert a new RecordType if we don't have one with the same name already. Note that this is more of a sketch rather than production code, as it is missing any kind of user feedback regarding successful execution or possible errors. While 30 seconds execution time usually don't cause any timeouts, it is already a rather long time to wait in the browser without any feedback, so we want to call this script in an asynchronous way and then inform the user that it was called asynchronously and is running in the background. To achieve this, we create a second Python script that calls the above one. Assuming the above script was called `create_rt.py`, the calling script could look as follows. ```python #!/usr/bin/env python3 import os import subprocess import sys from pathlib import Path import linkahead as db from caosadvancedtools.serverside import helper def main(): parser = helper.get_argument_parser() parser.add_argument("name", help="Name of the new RT") parser.add_argument("description", help="Description of the new RT") args = parser.parse_args() # log-in with the provided credentials and save auth token for further log-in db.configure_connection(auth_token=args.auth_token) db.Info() conn = db.get_connection() auth_token = conn._authenticator.auth_token # Provide the absolute path to the other script exec_path = Path(__file__).parent / "create_rt.py" cmds = [ str(exec_path), "--auth-token", auth_token, args.filename, args.name, args.description ] # We can use the environment of the SSS setup, but we need to reset the HOME # so that the called script can find e.g., the pylinkahead.ini config file. myenv = os.environ.copy() myenv["HOME"] = str(Path(__file__).parent.parent / "home") # We also need to provide a cwd, otherwise Popen will default to cwd=None subprocess.Popen(cmds, start_new_session=True, env=myenv, cwd=str(exec_path.parent)) print( f"Your job to create RT '{args.name}' asynchronously was started successfully " "and is running in the background." ) if __name__ == "__main__": main() ``` There are a few things to mention about the calling script: - We use the authentication token provided by the server-side scripting environment to authenticate, then we copy the resulting login token and provide it to the called script which can then use this token to authenticate with the same permissions as the calling script. - We reproduce the general environment of the server-side scripting API, but we reset the `HOME` variable since the SSS API uses a temporary directory. - We need to additionally specify a current working directory since `Popen`'s default to `None` will result in errors when importing LinkAhead. - There is no error handling in the called script. For a production use case, we will have to add error handling, logging, and reporting to the called script, but it makes sense to also save `stderr` and `stdout` to a file in case the called script crashes in an unexpected way, e.g., by replacing the `subprocess.Popen(...)` line in the above code by ```python # Find a reasonable path where you can retrieve the log file later logfile_path = "/tmp/async_log.txt" with open(logfile_path, "w") as logfile: p = subprocess.Popen( cmds, start_new_session=True, env=myenv, cwd=str(exec_path.parent), stderr=logfile, # Write stderr and stdout to the file defined above. stdout=logfile ) ```