Esempio n. 1
0
    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
Esempio n. 2
0
    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"
Esempio n. 3
0
    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"
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
    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)
Esempio n. 7
0
    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
Esempio n. 8
0
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])
Esempio n. 9
0
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)