# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
"""
Control the underlying Ryven logging system, and route logs to a widget.
"""
from __future__ import annotations
import sys
from io import TextIOBase
from typing import TYPE_CHECKING
import ipywidgets as widgets
from pyiron_base.interfaces.singleton import Singleton
from ironflow.gui.draws_widgets import DrawsWidgets
if TYPE_CHECKING:
from ironflow.model.model import HasSession
[docs]
class StdOutPut(TextIOBase):
"""
Helper class that can be assigned to stdout and/or stderr, passing string to a widget
"""
def __init__(self):
self.output = widgets.HTML()
self.messages = ""
[docs]
def write(self, s):
self.messages += s
self.output.value = (
"<style>p{"
"word-wrap: break-word; "
"margin: 0px; "
"font-family: monospace;"
"white-space: pre"
"}</style> <p>" + self.messages + " </p>"
)
[docs]
class LogController(metaclass=Singleton):
"""
Singleton pattern ensures that whatever `sys.stdout/err` was at the beginning of the session gets preserved.
"""
def __init__(self):
self.stdoutput = StdOutPut()
self._standard_stdout = sys.stdout
self._standard_stderr = sys.stderr
@property
def output(self):
return self.stdoutput.output
[docs]
def log_to_display(self):
sys.stdout = self.stdoutput
sys.stderr = self.stdoutput
[docs]
def log_to_stdout(self):
sys.stdout = self._standard_stdout
sys.stderr = self._standard_stderr
[docs]
def clear_log(self):
self.stdoutput.messages = ""
self.stdoutput.output.value = ""
self.stdoutput.flush()
[docs]
class LogGUI(DrawsWidgets):
"""
A class that can redirect stdout and stderr to a widget, and gives controls for both this and toggling the
Ryven logger.
"""
main_widget_class = widgets.VBox
def __new__(cls, model, enable_ryven_log, log_to_display, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
def __init__(
self,
model: HasSession,
enable_ryven_log: bool,
log_to_display: bool,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)
self.widget = self.main_widget_class([])
self.model = model
self._log_controller = LogController()
if log_to_display:
self.log_to_display()
self.ryven_log_button = widgets.Checkbox(
value=enable_ryven_log, description="Use Ryven's InfoMsgs system"
)
self.display_log_button = widgets.Checkbox(
value=log_to_display, description="Route stdout to ironflow"
)
self.clear_button = widgets.Button(
description="Clear", tooltip="Clear the log output and flush the stdout."
)
self.ryven_log_button.observe(self._toggle_ryven_log)
self.display_log_button.observe(self._toggle_display_log)
self.clear_button.on_click(self._click_clear)
self.widget.children = [
widgets.HBox(
[self.display_log_button, self.ryven_log_button, self.clear_button],
layout=widgets.Layout(min_height="35px"),
),
widgets.HBox([self.output], layout=widgets.Layout(height="435px")),
]
@property
def output(self):
return self._log_controller.output
[docs]
def log_to_display(self):
self._log_controller.log_to_display()
[docs]
def log_to_stdout(self):
self._log_controller.log_to_stdout()
def _toggle_ryven_log(self, change: dict):
if change["name"] == "value":
if change["new"]:
self.model.session.info_messenger().enable()
else:
self.model.session.info_messenger().disable()
def _toggle_display_log(self, change: dict):
if change["name"] == "value":
if change["new"]:
self.log_to_display()
else:
self.log_to_stdout()
def _click_clear(self, button: widgets.Button):
self._log_controller.clear_log()