def __init__(self, path: str = None): self.expdir = self.find_path(path) self.config = ExperimentConfig(self.expdir) self.secrets = ExperimentSecrets(self.expdir) self.app = None self.expurl = None self.test_mode = None self.debug_mode = None
def test_exp_secrets(self, tmp_path): """Assert that an arbitrary option from a secrets.conf in the experiment directory is parsed.""" d = tmp_path secrets_file = d / "secrets.conf" secrets_file.write_text("[config_test]\nfile = testconfig.conf") secrets = ExperimentSecrets(expdir=str(d)) assert secrets.get("config_test", "file") == "testconfig.conf"
def test_secrets_environ(self, tmp_path): fp = str(tmp_path) d = tmp_path / "test" d.mkdir() secrets_file = d / "secrets.conf" os.environ["ALFRED_SECRETS_FILE"] = str(secrets_file) secrets_file.write_text("[config_test]\nfile = environ.conf") secrets = ExperimentSecrets(expdir=fp) assert secrets.get("config_test", "file") == "environ.conf"
def test_admin_missing_password(self, tmp_path): exp = al.Experiment() exp.admin += al.Page(name="admin_test") config = ExperimentConfig(tmp_path) secrets = ExperimentSecrets(tmp_path) urlargs = {"admin": "true"} secrets.read_dict({"general": {"adminpass_lvl2": "test"}}) with pytest.raises(AlfredError) as excinfo: exp.create_session("sid1", config, secrets, **urlargs) msg = str(excinfo.value) assert "lvl1" in msg and "lvl3" in msg and not "lvl2" in msg
def test_admin(self, tmp_path): exp = al.Experiment() exp.admin += al.Page(name="admin_test") config = ExperimentConfig(tmp_path) secrets = ExperimentSecrets(tmp_path) secrets.read_dict({ "general": { "adminpass_lvl1": "test1", "adminpass_lvl2": "test2", "adminpass_lvl3": "test3" } }) urlargs = {"admin": "true"} session = exp.create_session("sid1", config, secrets, **urlargs) assert session.admin_mode
def test_admin_without_password(self, tmp_path): exp = al.Experiment() exp.admin += al.Page(name="admin_test") config = ExperimentConfig(tmp_path) secrets = ExperimentSecrets(tmp_path) urlargs = {"admin": "true"} with pytest.raises(AlfredError): exp.create_session("sid1", config, secrets, **urlargs)
def test_admin_equal_passwords(self, tmp_path): exp = al.Experiment() exp.admin += al.Page(name="admin_test") config = ExperimentConfig(tmp_path) secrets = ExperimentSecrets(tmp_path) urlargs = {"admin": "true"} secrets.read_dict({ "general": { "adminpass_lvl1": "test", "adminpass_lvl2": "test", "adminpass_lvl3": "test1" } }) with pytest.raises(AlfredError) as excinfo: exp.create_session("sid1", config, secrets, **urlargs) msg = str(excinfo.value) assert "Passwords must be unique to a level" in msg
def prepare_secrets(tmp_path, secrets_path: str) -> str: """ Writes a secrets.conf for a mongo saving agent into the *tmp_path*. This is intended for use with the tmp_path fixture. """ if not secrets_path: return SECRETS = Path(secrets_path).read_text(encoding="utf-8") secrets = SECRETS.format( host=os.getenv("MONGODB_HOST"), port=os.getenv("MONGODB_PORT"), db=os.getenv("MONGODB_DATABASE"), usr=os.getenv("MONGODB_USERNAME"), pw=os.getenv("MONGODB_PASSWORD"), col=os.getenv("MONGODB_COLLECTION"), misc_collection=os.getenv("MONGODB_MISC_COLLECTION"), ) tmp_secrets = Path(tmp_path) / "secrets.conf" tmp_secrets.write_text(secrets) return ExperimentSecrets(expdir=tmp_path, config_objects=[secrets])
class ExperimentRunner: def __init__(self, path: str = None): self.expdir = self.find_path(path) self.config = ExperimentConfig(self.expdir) self.secrets = ExperimentSecrets(self.expdir) self.app = None self.expurl = None self.test_mode = None self.debug_mode = None def find_path(self, path): if path: p = Path(path).resolve() script0 = p / "script.py" if script0.is_file(): sys.stderr.writelines([f" * Using script '{str(script0)}'\n"]) return p fp = Path(sys.argv[0]).resolve().parent script2 = fp / "script.py" if script2.is_file(): sys.stderr.writelines([f" * Using script '{str(script2)}'\n"]) return fp wd = Path.cwd() script1 = wd / "script.py" if script1.is_file(): sys.stderr.writelines([f" * Using script '{str(script1)}'\n"]) return wd raise FileNotFoundError("No script.py found.") def generate_session_id(self): session_id = uuid4().hex self.config.read_dict( {"metadata": { "session_id": "sid-" + session_id }}) def configure_logging(self): """Sets some sensible logging configuration for local experiments. * Base logger gets turned off to avoid doubled logging messages (we don't want to turn the queue_logger off, because that way usage is completely the same between local and web exp.) * Queue logger gets configured using settings from config.conf """ config = self.config exp_id = config.get("metadata", "exp_id") loggername = f"exp.{exp_id}" logger = logging.getLogger(loggername) formatter = alfredlog.prepare_alfred_formatter(exp_id) if config.getboolean("general", "debug"): logfile = "alfred_debug.log" else: logfile = "alfred.log" logpath = Path(config.get("log", "path")).resolve() / logfile if not logpath.is_absolute(): logpath = self.expdir / logpath file_handler = alfredlog.prepare_file_handler(logpath) file_handler.setFormatter(formatter) stream_handler = logging.StreamHandler(sys.stderr) stream_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(stream_handler) lvl = config.get("log", "level") if config.getboolean("general", "debug"): if config.getboolean("debug", "log_level_override"): lvl = config.get("debug", "log_level") logger.setLevel(alfredlog.parse_level(lvl)) base_logger = logging.getLogger("alfred3") base_logger.addHandler(logging.NullHandler()) def create_experiment_app(self): script = smuggle(str(self.expdir / "script.py")) localserver.Script.expdir = self.expdir localserver.Script.config = self.config localserver.Script.secrets = self.secrets localserver.Script.exp = script.exp self.app = localserver.app secret_key = self.secrets.get("flask", "secret_key", fallback=None) if not secret_key: import secrets secret_key = secrets.token_urlsafe(16) self.app.secret_key = secret_key return self.app def set_port(self): port = 5000 while not socket_checker(port): port += 1 self.port = port def print_startup_message(self): msg_startup = f" * Start local experiment using http://127.0.0.1:{self.port}/start\n" msg_admin = f" * Start admin mode using http://127.0.0.1:{self.port}/start?admin=true\n" msg_test = f" * Start test mode using http://127.0.0.1:{self.port}/start?test=true\n" sys.stderr.writelines([msg_startup, msg_admin, msg_test]) def _open_browser(self): # generate url expurl = "http://127.0.0.1:{port}/start".format(port=self.port) expurl = expurl + "?" if self.debug_mode or self.test_mode else expurl expurl = expurl + "test=true" if self.test_mode else expurl expurl = expurl + "debug=true" if self.debug_mode else expurl if self.config.getboolean("general", "fullscreen"): ChromeKiosk.open(url=expurl) else: webbrowser.open(url=expurl) def start_browser_thread(self): # start browser in a thread (needed for windows) browser = threading.Thread(target=self._open_browser, name="browser") browser.start() def auto_run(self, open_browser: bool = None, debug=False, test: bool = False): """ Automatically runs an alfred experiment. Args: open_browser: Indicates, whether alfred should try to open a new browser window automatically. debug: Indicates, whether the underlying flask app should be run in debug mode. Defaults to None, which leads to taking the value from option 'open_browser' in section 'general' of config.conf. test: If true, the experiment is started in test mode. """ self.test_mode = test self.debug_mode = debug self.generate_session_id() self.configure_logging() self.create_experiment_app() self.set_port() open_browser = self.config.getboolean( "general", "open_browser") if open_browser is None else open_browser if open_browser: self.start_browser_thread() self.print_startup_message() self.app.run(port=self.port, threaded=False, use_reloader=False, debug=debug)