Wauplin HF staff commited on
Commit
871b360
1 Parent(s): 3a59d0b

on the road to 0.0.5

Browse files
README.md CHANGED
@@ -14,4 +14,76 @@ license: apache-2.0
14
 
15
  Description: Visualize logs in your Gradio app
16
 
17
- Install with: pip install gradio_logsview
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  Description: Visualize logs in your Gradio app
16
 
17
+ ## Installation
18
+
19
+ ```
20
+ pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
21
+ ```
22
+
23
+ or add this line to your `requirements.txt`:
24
+
25
+ ```
26
+ gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
27
+ ```
28
+
29
+ ## How to run Python code?
30
+
31
+ With `LogsView.run_python`, you can run Python code in a separate process and capture logs in real-time.
32
+ You can configure which logs to capture (log level and logger name).
33
+
34
+ ```py
35
+ from gradio_logsview import LogsView
36
+
37
+ def fn_python():
38
+ # Run `my_function` in a separate process
39
+ # All logs above `INFO` level will be captured and displayed in real-time.
40
+ runner = LogsViewRunner() # Initialize the runner
41
+ yield from runner.run_python(my_function, log_level=logging.INFO, arg1="value1")
42
+ yield runner.log(f"Runner: {runner}") # Log any message
43
+ if runner.exit_code != 0:
44
+ # Handle error
45
+ ...
46
+
47
+ with gr.Blocks() as demo:
48
+ logs = LogsView()
49
+ btn = gr.Button("Run Python code")
50
+ btn.click(fn_python, outputs=logs)
51
+ ```
52
+
53
+ ## How to run a command?
54
+
55
+ With `LogsView.run_command`, you can run a command on the system and capture logs from the process in real-time.
56
+
57
+ ```py
58
+ from gradio_logsview import LogsView
59
+
60
+ def fn_command():
61
+ # Run a command and capture all logs from the subprocess
62
+ runner = LogsViewRunner() # Initialize the runner
63
+ yield from runner.run_command(
64
+ cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
65
+ )
66
+ yield runner.log(f"Runner: {runner}") # Log any message
67
+ if runner.exit_code != 0:
68
+ # Handle error
69
+ ...
70
+
71
+ with gr.Blocks() as demo:
72
+ logs = LogsView()
73
+ btn = gr.Button("Run command")
74
+ btn.click(fn_command, outputs=logs)
75
+ ```
76
+
77
+ ## TODO
78
+
79
+ - [ ] display logs with colors (front-end)
80
+ - [ ] format logs client-side (front-end)
81
+ - [ ] scrollable logs if more than N lines (front-end)
82
+ - [ ] format each log only once (front-end)
83
+ - [x] stop process if `run_command` gets cancelled (back-end)
84
+ - [x] correctly pass error stacktrace in `run_python` (back-end)
85
+ - [x] correctly catch print statements in `run_python` (back-end)
86
+ - [ ] disable interactivity + remove all code editing logic (both?)
87
+ - [x] how to handle progress bars? (i.e when logs are overwritten in terminal)
88
+ - [ ] Have 3 tabs in UI for stdout, stderr and logging (front-end + back-end)
89
+ - [ ] Write logger name in the logs (back-end)
app.py CHANGED
@@ -3,13 +3,15 @@ import random
3
  import time
4
 
5
  import gradio as gr
 
6
 
7
- from gradio_logsview import LogsView
8
 
 
9
 
10
  def random_values(failing: bool = False):
11
- for i in range(10):
12
- logging.log(
13
  random.choice(
14
  [ # Random levels
15
  logging.INFO,
@@ -26,90 +28,134 @@ def random_values(failing: bool = False):
26
  raise ValueError("Failing!!")
27
 
28
 
29
- def fn_process_success():
30
- yield from LogsView.run_process(["python", "-u", "demo/script.py"])
 
 
31
 
32
 
33
- def fn_process_failing():
34
- yield from LogsView.run_process(["python", "-u", "demo/script.py", "--failing"])
 
 
35
 
36
 
37
- def fn_thread_success():
38
- yield from LogsView.run_thread(random_values, log_level=logging.INFO, failing=False)
 
 
39
 
40
 
41
- def fn_thread_failing():
42
- yield from LogsView.run_thread(random_values, log_level=logging.INFO, failing=True)
 
 
43
 
44
 
45
  markdown_top = """
46
  # LogsView Demo
47
 
48
- This demo shows how to use the `LogsView` component to display logs from a process or a thread in real-time.
49
 
50
- Click on any button to launch a process or a thread and see the logs displayed in real-time.
51
- In the thread example, logs are generated randomly with different log levels.
52
- In the process example, logs are generated by a Python script but any command can be executed.
53
  """
54
 
55
 
56
  markdown_bottom = """
57
- ## How to run in a thread?
 
 
 
 
 
 
58
 
59
- With `LogsView.run_thread`, you can run a function in a separate thread and capture logs in real-time.
 
 
 
 
 
 
60
  You can configure which logs to capture (log level and logger name).
61
 
62
  ```py
63
  from gradio_logsview import LogsView
64
 
65
- def fn_thread():
66
- # Run `my_function` in a separate thread
67
  # All logs above `INFO` level will be captured and displayed in real-time.
68
- yield from LogsView.run_thread(my_function, log_level=logging.INFO, arg1="value1")
 
 
 
 
 
69
 
70
  with gr.Blocks() as demo:
71
  logs = LogsView()
72
- btn = gr.Button("Run thread")
73
- btn.click(fn_thread, outputs=logs)
74
  ```
75
 
76
- ## How to run in a process?
77
 
78
- With `LogsView.run_process`, you can run a command in a separate process and capture logs from the process in real-time.
79
 
80
  ```py
81
  from gradio_logsview import LogsView
82
 
83
- def fn_process():
84
- # Run a process and capture all logs from the process
85
- yield from LogsView.run_process(
 
86
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
87
  )
 
 
 
 
88
 
89
  with gr.Blocks() as demo:
90
  logs = LogsView()
91
- btn = gr.Button("Run process")
92
- btn.click(fn_process, outputs=logs)
93
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  """
95
 
96
  with gr.Blocks() as demo:
97
  gr.Markdown(markdown_top)
98
 
99
  with gr.Row():
100
- btn_thread_success = gr.Button("Run thread (success)")
101
- btn_thread_failing = gr.Button("Run thread (failing)")
102
  with gr.Row():
103
- btn_process_success = gr.Button("Run process (success)")
104
- btn_process_failing = gr.Button("Run process (failing)")
105
  logs = LogsView()
106
 
107
  gr.Markdown(markdown_bottom)
108
 
109
- btn_thread_failing.click(fn_thread_failing, outputs=logs)
110
- btn_thread_success.click(fn_thread_success, outputs=logs)
111
- btn_process_failing.click(fn_process_failing, outputs=logs)
112
- btn_process_success.click(fn_process_success, outputs=logs)
113
 
114
 
115
  if __name__ == "__main__":
 
3
  import time
4
 
5
  import gradio as gr
6
+ from tqdm import tqdm
7
 
8
+ from gradio_logsview import LogsView, LogsViewRunner
9
 
10
+ logger = logging.getLogger("custom.foo")
11
 
12
  def random_values(failing: bool = False):
13
+ for i in tqdm(range(10)):
14
+ logger.log(
15
  random.choice(
16
  [ # Random levels
17
  logging.INFO,
 
28
  raise ValueError("Failing!!")
29
 
30
 
31
+ def fn_command_success():
32
+ runner = LogsViewRunner()
33
+ yield from runner.run_command(["python", "-u", "demo/script.py"])
34
+ yield runner.log(f"Runner: {runner}")
35
 
36
 
37
+ def fn_command_failing():
38
+ runner = LogsViewRunner()
39
+ yield from runner.run_command(["python", "-u", "demo/script.py", "--failing"])
40
+ yield runner.log(f"Runner: {runner}")
41
 
42
 
43
+ def fn_python_success():
44
+ runner = LogsViewRunner()
45
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=False)
46
+ yield runner.log(f"Runner: {runner}")
47
 
48
 
49
+ def fn_python_failing():
50
+ runner = LogsViewRunner()
51
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=True)
52
+ yield runner.log(f"Runner: {runner}")
53
 
54
 
55
  markdown_top = """
56
  # LogsView Demo
57
 
58
+ This demo shows how to use the `LogsView` component to run some Python code or execute a command and display logs in real-time.
59
 
60
+ Click on any button to launch a process and see the logs displayed in real-time.
61
+ In the Python examples, code is executed in a process. You can see the logs (generated randomly with different log levels).
62
+ In the command examples, the command line is executed on the system directly. Any command can be executed.
63
  """
64
 
65
 
66
  markdown_bottom = """
67
+ ## Installation
68
+
69
+ ```
70
+ pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
71
+ ```
72
+
73
+ or add this line to your `requirements.txt`:
74
 
75
+ ```
76
+ gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
77
+ ```
78
+
79
+ ## How to run Python code?
80
+
81
+ With `LogsView.run_python`, you can run Python code in a separate process and capture logs in real-time.
82
  You can configure which logs to capture (log level and logger name).
83
 
84
  ```py
85
  from gradio_logsview import LogsView
86
 
87
+ def fn_python():
88
+ # Run `my_function` in a separate process
89
  # All logs above `INFO` level will be captured and displayed in real-time.
90
+ runner = LogsViewRunner() # Initialize the runner
91
+ yield from runner.run_python(my_function, log_level=logging.INFO, arg1="value1")
92
+ yield runner.log(f"Runner: {runner}") # Log any message
93
+ if runner.exit_code != 0:
94
+ # Handle error
95
+ ...
96
 
97
  with gr.Blocks() as demo:
98
  logs = LogsView()
99
+ btn = gr.Button("Run Python code")
100
+ btn.click(fn_python, outputs=logs)
101
  ```
102
 
103
+ ## How to run a command?
104
 
105
+ With `LogsView.run_command`, you can run a command on the system and capture logs from the process in real-time.
106
 
107
  ```py
108
  from gradio_logsview import LogsView
109
 
110
+ def fn_command():
111
+ # Run a command and capture all logs from the subprocess
112
+ runner = LogsViewRunner() # Initialize the runner
113
+ yield from runner.run_command(
114
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
115
  )
116
+ yield runner.log(f"Runner: {runner}") # Log any message
117
+ if runner.exit_code != 0:
118
+ # Handle error
119
+ ...
120
 
121
  with gr.Blocks() as demo:
122
  logs = LogsView()
123
+ btn = gr.Button("Run command")
124
+ btn.click(fn_command, outputs=logs)
125
  ```
126
+
127
+ ## TODO
128
+
129
+ - [ ] display logs with colors (front-end)
130
+ - [ ] format logs client-side (front-end)
131
+ - [ ] scrollable logs if more than N lines (front-end)
132
+ - [ ] format each log only once (front-end)
133
+ - [x] stop process if `run_command` gets cancelled (back-end)
134
+ - [x] correctly pass error stacktrace in `run_python` (back-end)
135
+ - [x] correctly catch print statements in `run_python` (back-end)
136
+ - [ ] disable interactivity + remove all code editing logic (both?)
137
+ - [x] how to handle progress bars? (i.e when logs are overwritten in terminal)
138
+ - [ ] Have 3 tabs in UI for stdout, stderr and logging (front-end + back-end)
139
+ - [ ] Write logger name in the logs (back-end)
140
  """
141
 
142
  with gr.Blocks() as demo:
143
  gr.Markdown(markdown_top)
144
 
145
  with gr.Row():
146
+ btn_python_success = gr.Button("Run Python code (success)")
147
+ btn_python_failing = gr.Button("Run Python code (failing)")
148
  with gr.Row():
149
+ btn_command_success = gr.Button("Run command (success)")
150
+ btn_command_failing = gr.Button("Run command (failing)")
151
  logs = LogsView()
152
 
153
  gr.Markdown(markdown_bottom)
154
 
155
+ btn_python_failing.click(fn_python_failing, outputs=logs)
156
+ btn_python_success.click(fn_python_success, outputs=logs)
157
+ btn_command_failing.click(fn_command_failing, outputs=logs)
158
+ btn_command_success.click(fn_command_success, outputs=logs)
159
 
160
 
161
  if __name__ == "__main__":
gradio_logsview-0.0.5-py3-none-any.whl ADDED
Binary file (324 kB). View file
 
requirements.txt CHANGED
@@ -1 +1 @@
1
- gradio_logsview-0.0.4-py3-none-any.whl
 
1
+ gradio_logsview-0.0.5-py3-none-any.whl
space.py CHANGED
@@ -3,13 +3,15 @@ import random
3
  import time
4
 
5
  import gradio as gr
 
6
 
7
  from gradio_logsview import LogsView, LogsViewRunner
8
 
 
9
 
10
  def random_values(failing: bool = False):
11
- for i in range(10):
12
- logging.log(
13
  random.choice(
14
  [ # Random levels
15
  logging.INFO,
@@ -26,38 +28,38 @@ def random_values(failing: bool = False):
26
  raise ValueError("Failing!!")
27
 
28
 
29
- def fn_process_success():
30
  runner = LogsViewRunner()
31
- yield from runner.run_process(["python", "-u", "demo/script.py"])
32
  yield runner.log(f"Runner: {runner}")
33
 
34
 
35
- def fn_process_failing():
36
  runner = LogsViewRunner()
37
- yield from runner.run_process(["python", "-u", "demo/script.py", "--failing"])
38
  yield runner.log(f"Runner: {runner}")
39
 
40
 
41
- def fn_thread_success():
42
  runner = LogsViewRunner()
43
- yield from runner.run_thread(random_values, log_level=logging.INFO, failing=False)
44
  yield runner.log(f"Runner: {runner}")
45
 
46
 
47
- def fn_thread_failing():
48
  runner = LogsViewRunner()
49
- yield from runner.run_thread(random_values, log_level=logging.INFO, failing=True)
50
  yield runner.log(f"Runner: {runner}")
51
 
52
 
53
  markdown_top = """
54
  # LogsView Demo
55
 
56
- This demo shows how to use the `LogsView` component to display logs from a process or a thread in real-time.
57
 
58
- Click on any button to launch a process or a thread and see the logs displayed in real-time.
59
- In the thread example, logs are generated randomly with different log levels.
60
- In the process example, logs are generated by a Python script but any command can be executed.
61
  """
62
 
63
 
@@ -65,55 +67,61 @@ markdown_bottom = """
65
  ## Installation
66
 
67
  ```
68
- pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.3-py3-none-any.whl
69
  ```
70
 
71
  or add this line to your `requirements.txt`:
72
 
73
  ```
74
- gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.3-py3-none-any.whl
75
  ```
76
 
77
- ## How to run in a thread?
78
 
79
- With `LogsView.run_thread`, you can run a function in a separate thread and capture logs in real-time.
80
  You can configure which logs to capture (log level and logger name).
81
 
82
  ```py
83
  from gradio_logsview import LogsView
84
 
85
- def fn_thread():
86
- # Run `my_function` in a separate thread
87
  # All logs above `INFO` level will be captured and displayed in real-time.
88
  runner = LogsViewRunner() # Initialize the runner
89
- yield from runner.run_thread(my_function, log_level=logging.INFO, arg1="value1")
90
  yield runner.log(f"Runner: {runner}") # Log any message
 
 
 
91
 
92
  with gr.Blocks() as demo:
93
  logs = LogsView()
94
- btn = gr.Button("Run thread")
95
- btn.click(fn_thread, outputs=logs)
96
  ```
97
 
98
- ## How to run in a process?
99
 
100
- With `LogsView.run_process`, you can run a command in a separate process and capture logs from the process in real-time.
101
 
102
  ```py
103
  from gradio_logsview import LogsView
104
 
105
- def fn_process():
106
- # Run a process and capture all logs from the process
107
  runner = LogsViewRunner() # Initialize the runner
108
- yield from runner.run_process(
109
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
110
  )
111
  yield runner.log(f"Runner: {runner}") # Log any message
 
 
 
112
 
113
  with gr.Blocks() as demo:
114
  logs = LogsView()
115
- btn = gr.Button("Run process")
116
- btn.click(fn_process, outputs=logs)
117
  ```
118
 
119
  ## TODO
@@ -122,30 +130,32 @@ with gr.Blocks() as demo:
122
  - [ ] format logs client-side (front-end)
123
  - [ ] scrollable logs if more than N lines (front-end)
124
  - [ ] format each log only once (front-end)
125
- - [x] stop process if `run_process` gets cancelled (back-end)
126
- - [x] correctly pass error stacktrace in `run_thread` (back-end)
127
- - [ ] correctly catch print statements in `run_thread` (back-end)
128
  - [ ] disable interactivity + remove all code editing logic (both?)
129
- - [ ] how to handle progress bars? (i.e when logs are overwritten in terminal)
 
 
130
  """
131
 
132
  with gr.Blocks() as demo:
133
  gr.Markdown(markdown_top)
134
 
135
  with gr.Row():
136
- btn_thread_success = gr.Button("Run thread (success)")
137
- btn_thread_failing = gr.Button("Run thread (failing)")
138
  with gr.Row():
139
- btn_process_success = gr.Button("Run process (success)")
140
- btn_process_failing = gr.Button("Run process (failing)")
141
  logs = LogsView()
142
 
143
  gr.Markdown(markdown_bottom)
144
 
145
- btn_thread_failing.click(fn_thread_failing, outputs=logs)
146
- btn_thread_success.click(fn_thread_success, outputs=logs)
147
- btn_process_failing.click(fn_process_failing, outputs=logs)
148
- btn_process_success.click(fn_process_success, outputs=logs)
149
 
150
 
151
  if __name__ == "__main__":
 
3
  import time
4
 
5
  import gradio as gr
6
+ from tqdm import tqdm
7
 
8
  from gradio_logsview import LogsView, LogsViewRunner
9
 
10
+ logger = logging.getLogger("custom.foo")
11
 
12
  def random_values(failing: bool = False):
13
+ for i in tqdm(range(10)):
14
+ logger.log(
15
  random.choice(
16
  [ # Random levels
17
  logging.INFO,
 
28
  raise ValueError("Failing!!")
29
 
30
 
31
+ def fn_command_success():
32
  runner = LogsViewRunner()
33
+ yield from runner.run_command(["python", "-u", "script.py"])
34
  yield runner.log(f"Runner: {runner}")
35
 
36
 
37
+ def fn_command_failing():
38
  runner = LogsViewRunner()
39
+ yield from runner.run_command(["python", "-u", "script.py", "--failing"])
40
  yield runner.log(f"Runner: {runner}")
41
 
42
 
43
+ def fn_python_success():
44
  runner = LogsViewRunner()
45
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=False)
46
  yield runner.log(f"Runner: {runner}")
47
 
48
 
49
+ def fn_python_failing():
50
  runner = LogsViewRunner()
51
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=True)
52
  yield runner.log(f"Runner: {runner}")
53
 
54
 
55
  markdown_top = """
56
  # LogsView Demo
57
 
58
+ This demo shows how to use the `LogsView` component to run some Python code or execute a command and display logs in real-time.
59
 
60
+ Click on any button to launch a process and see the logs displayed in real-time.
61
+ In the Python examples, code is executed in a process. You can see the logs (generated randomly with different log levels).
62
+ In the command examples, the command line is executed on the system directly. Any command can be executed.
63
  """
64
 
65
 
 
67
  ## Installation
68
 
69
  ```
70
+ pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
71
  ```
72
 
73
  or add this line to your `requirements.txt`:
74
 
75
  ```
76
+ gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
77
  ```
78
 
79
+ ## How to run Python code?
80
 
81
+ With `LogsView.run_python`, you can run Python code in a separate process and capture logs in real-time.
82
  You can configure which logs to capture (log level and logger name).
83
 
84
  ```py
85
  from gradio_logsview import LogsView
86
 
87
+ def fn_python():
88
+ # Run `my_function` in a separate process
89
  # All logs above `INFO` level will be captured and displayed in real-time.
90
  runner = LogsViewRunner() # Initialize the runner
91
+ yield from runner.run_python(my_function, log_level=logging.INFO, arg1="value1")
92
  yield runner.log(f"Runner: {runner}") # Log any message
93
+ if runner.exit_code != 0:
94
+ # Handle error
95
+ ...
96
 
97
  with gr.Blocks() as demo:
98
  logs = LogsView()
99
+ btn = gr.Button("Run Python code")
100
+ btn.click(fn_python, outputs=logs)
101
  ```
102
 
103
+ ## How to run a command?
104
 
105
+ With `LogsView.run_command`, you can run a command on the system and capture logs from the process in real-time.
106
 
107
  ```py
108
  from gradio_logsview import LogsView
109
 
110
+ def fn_command():
111
+ # Run a command and capture all logs from the subprocess
112
  runner = LogsViewRunner() # Initialize the runner
113
+ yield from runner.run_command(
114
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
115
  )
116
  yield runner.log(f"Runner: {runner}") # Log any message
117
+ if runner.exit_code != 0:
118
+ # Handle error
119
+ ...
120
 
121
  with gr.Blocks() as demo:
122
  logs = LogsView()
123
+ btn = gr.Button("Run command")
124
+ btn.click(fn_command, outputs=logs)
125
  ```
126
 
127
  ## TODO
 
130
  - [ ] format logs client-side (front-end)
131
  - [ ] scrollable logs if more than N lines (front-end)
132
  - [ ] format each log only once (front-end)
133
+ - [x] stop process if `run_command` gets cancelled (back-end)
134
+ - [x] correctly pass error stacktrace in `run_python` (back-end)
135
+ - [x] correctly catch print statements in `run_python` (back-end)
136
  - [ ] disable interactivity + remove all code editing logic (both?)
137
+ - [x] how to handle progress bars? (i.e when logs are overwritten in terminal)
138
+ - [ ] Have 3 tabs in UI for stdout, stderr and logging (front-end + back-end)
139
+ - [ ] Write logger name in the logs (back-end)
140
  """
141
 
142
  with gr.Blocks() as demo:
143
  gr.Markdown(markdown_top)
144
 
145
  with gr.Row():
146
+ btn_python_success = gr.Button("Run Python code (success)")
147
+ btn_python_failing = gr.Button("Run Python code (failing)")
148
  with gr.Row():
149
+ btn_command_success = gr.Button("Run command (success)")
150
+ btn_command_failing = gr.Button("Run command (failing)")
151
  logs = LogsView()
152
 
153
  gr.Markdown(markdown_bottom)
154
 
155
+ btn_python_failing.click(fn_python_failing, outputs=logs)
156
+ btn_python_success.click(fn_python_success, outputs=logs)
157
+ btn_command_failing.click(fn_command_failing, outputs=logs)
158
+ btn_command_success.click(fn_command_success, outputs=logs)
159
 
160
 
161
  if __name__ == "__main__":
src/backend/gradio_logsview/logsview.py CHANGED
@@ -7,13 +7,13 @@ import queue
7
  import subprocess
8
  import time
9
  import traceback
10
- from contextlib import contextmanager, redirect_stdout
11
  from dataclasses import dataclass
12
  from datetime import datetime
13
  from functools import wraps
14
  from logging.handlers import QueueHandler
15
- from queue import Queue
16
- from threading import Thread
17
  from typing import Any, Callable, Generator, List, Literal, NoReturn
18
 
19
  from gradio.components.base import Component
@@ -30,15 +30,33 @@ class Log:
30
 
31
 
32
  class LogsViewRunner:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def __init__(self, date_format: str = "%Y-%m-%d %H:%M:%S"):
34
  self.date_format = date_format
35
  self.logs: List[Log] = []
36
 
37
  # Runner state
38
- self.completed = False
39
- self.error = False
40
- self.process: subprocess.Popen | None = None
41
- self.thread: Thread | None = None
42
 
43
  def log(
44
  self,
@@ -55,7 +73,25 @@ class LogsViewRunner:
55
  self.logs.append(Log(level=level, message=message, timestamp=timestamp))
56
  return self.logs
57
 
58
- def run_process(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  self, command: List[str], **kwargs
60
  ) -> Generator[List[Log], None, None]:
61
  """Run a command in a subprocess and yield logs in real-time."""
@@ -75,116 +111,61 @@ class LogsViewRunner:
75
  yield self.log(line.strip())
76
 
77
  self.process.stdout.close()
78
- return_code = self.process.wait()
79
- if return_code:
80
- yield self.log(f"Process exited with code {return_code}", level="ERROR")
81
- self.completed = True
82
- self.error = True
83
  else:
84
- yield self.log("Process exited successfully", level="INFO")
85
- self.completed = True
86
- self.error = False
87
 
88
- def run_thread(
89
  self,
90
  fn: Callable,
91
  log_level: int = logging.INFO,
92
  logger_name: str | None = None,
93
  **kwargs,
94
  ) -> Generator[List[Log], None, None]:
95
- """Run a function in a thread and capture logs in real-time to yield them."""
96
  yield self.log(
97
  f"Running {fn.__name__}({', '.join(f'{k}={v}' for k, v in kwargs.items())})"
98
  )
99
-
100
- error_queue, wrapped_fn = non_failing_fn(fn)
101
- self.thread = Thread(target=wrapped_fn, kwargs=kwargs)
102
-
103
- def _log(record: logging.LogRecord) -> bool:
104
- """Handle log record and return True if log should be yielded."""
105
- # if record.thread != self.thread.ident:
106
- # return False # Skip if not from the thread
107
- if logger_name and not record.name.startswith(logger_name):
108
- return False # Skip if not from the logger
109
- if record.levelno < log_level:
110
- return False # Skip if too verbose
111
- self.log(
112
- message=record.getMessage(),
113
- level=record.levelname,
114
- timestamp=record.created,
115
- )
116
- return True
117
-
118
- stdout_queue = queue.Queue()
119
- stream = LineQueueStream(stdout_queue)
120
- with capture_logging(log_level) as log_queue:
121
- with redirect_stdout(stream):
122
- # Start thread and loop to capture and yield logs from the thread
123
- self.thread.start()
124
- while self.thread.is_alive():
125
- while True:
126
- try:
127
- if _log(log_queue.get_nowait()):
128
- yield self.logs
129
- except queue.Empty:
130
- break
131
- while True:
132
- try:
133
- line = stdout_queue.get_nowait()
134
- yield self.log(line)
135
- except queue.Empty:
136
- break
137
- self.thread.join(timeout=0.1) # adjust the timeout as needed
138
-
139
- # After the thread completes, yield any remaining logs
140
- while True:
141
- try:
142
- if _log(log_queue.get_nowait()):
143
- yield self.logs
144
- except queue.Empty:
145
- break
146
- while True:
147
- try:
148
- line = stdout_queue.get_nowait()
149
- yield self.log(line)
150
- except queue.Empty:
151
- break
152
-
153
  try:
154
- error = error_queue.get_nowait()
 
 
155
  except queue.Empty:
156
- error = None
157
- if error is not None:
158
- msg = (
159
- f"Error in '{fn.__name__}':"
160
- + "\n"
161
- + "\n".join(
162
- line.strip("\n")
163
- for line in traceback.format_tb(error.__traceback__)
164
- if line.strip()
165
- )
166
- + "\n\n"
167
- + str(error)
168
- )
169
- yield self.log(msg, level="ERROR")
170
- self.completed = True
171
- self.error = True
172
- else:
173
- yield self.log("Thread completed successfully")
174
- self.completed = True
175
- self.error = False
176
 
177
  def __repr__(self) -> str:
178
- return f"<LogsViewRunner nb_logs={len(self.logs)} completed={self.completed} error={self.error}>"
179
 
180
  def __del__(self):
181
- if self.process and self.process.poll() is None:
182
- print(f"Killing process: {self.process}")
183
- self.process.kill()
184
- if self.thread and self.thread.is_alive():
185
- print(f"Joining thread: {self.thread}")
186
- self.thread.join()
187
-
 
 
188
 
189
  class LogsView(Component):
190
  """
@@ -278,40 +259,57 @@ class LogsView(Component):
278
 
279
 
280
  @contextmanager
281
- def capture_logging(log_level: int) -> Generator[Queue, None, None]:
282
  # Create a queue to capture log messages
283
- log_queue = queue.Queue()
284
- logger = logging.getLogger()
285
  logger.setLevel(log_level)
286
  handler = QueueHandler(log_queue)
287
  logger.addHandler(handler)
288
 
289
- # Yield the queue
290
- yield log_queue
291
 
292
  # Clean up
293
  logger.removeHandler(handler)
294
 
295
 
296
- def non_failing_fn(fn: Callable, *args, **kwargs) -> Callable:
297
- error_queue = queue.Queue()
 
 
 
298
 
299
  @wraps(fn)
300
  def _inner(*args, **kwargs):
301
- try:
302
- fn(*args, **kwargs)
303
- except Exception as e:
304
- error_queue.put(e)
305
-
306
- return error_queue, _inner
307
-
308
- class LineQueueStream(io.StringIO):
309
- def __init__(self, queue: queue.Queue, *args, **kwargs):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  super().__init__(*args, **kwargs)
311
  self._queue = queue
312
  self._current_line = ""
313
 
314
  def write(self, s: str) -> int:
 
315
  if "\n" in s:
316
  lines = s.split("\n")
317
  for line in lines[:-1]:
 
7
  import subprocess
8
  import time
9
  import traceback
10
+ from contextlib import contextmanager, redirect_stderr, redirect_stdout
11
  from dataclasses import dataclass
12
  from datetime import datetime
13
  from functools import wraps
14
  from logging.handlers import QueueHandler
15
+ from multiprocessing import Process
16
+ from multiprocessing import Queue as mpQueue
17
  from typing import Any, Callable, Generator, List, Literal, NoReturn
18
 
19
  from gradio.components.base import Component
 
30
 
31
 
32
  class LogsViewRunner:
33
+ """
34
+ Runner to execute a command or a function and capture logs in real-time.
35
+
36
+ Parameters:
37
+ date_format: Date format to use for logs timestamps. Default is "%Y-%m-%d %H:%M:%S".
38
+
39
+ Attributes:
40
+ date_format: Date format to use for logs timestamps.
41
+ logs: List of `Log` logs.
42
+ exit_code: Exit code of the process or function. None if not run yet.
43
+ process: Process or subprocess.Popen object. None if not run yet.
44
+
45
+ Usage:
46
+ ```python
47
+ runner = LogsViewRunner()
48
+ for logs in runner.run_command(["echo", "Hello World"]):
49
+ for log in logs:
50
+ print(log.timestamp, log.level, log.message)
51
+ """
52
+
53
  def __init__(self, date_format: str = "%Y-%m-%d %H:%M:%S"):
54
  self.date_format = date_format
55
  self.logs: List[Log] = []
56
 
57
  # Runner state
58
+ self.exit_code: int | None = None
59
+ self.process: Process | subprocess.Popen | None = None
 
 
60
 
61
  def log(
62
  self,
 
73
  self.logs.append(Log(level=level, message=message, timestamp=timestamp))
74
  return self.logs
75
 
76
+ def log_from_record(self, record: logging.LogRecord) -> List[Log]:
77
+ return self.log(
78
+ message=record.getMessage(),
79
+ level=record.levelname,
80
+ timestamp=record.created,
81
+ )
82
+
83
+ def _log_from_queue(self, mp_queue: mpQueue) -> Generator[List[Log], None, None]:
84
+ while True:
85
+ try:
86
+ line = mp_queue.get_nowait()
87
+ if isinstance(line, logging.LogRecord):
88
+ yield self.log_from_record(line)
89
+ else:
90
+ yield self.log(line)
91
+ except queue.Empty:
92
+ break
93
+
94
+ def run_command(
95
  self, command: List[str], **kwargs
96
  ) -> Generator[List[Log], None, None]:
97
  """Run a command in a subprocess and yield logs in real-time."""
 
111
  yield self.log(line.strip())
112
 
113
  self.process.stdout.close()
114
+ self.exit_code = self.process.wait()
115
+ if self.exit_code:
116
+ yield self.log(f"Command exited with code {self.exit_code}", level="ERROR")
 
 
117
  else:
118
+ yield self.log("Command exited successfully", level="INFO")
 
 
119
 
120
+ def run_python(
121
  self,
122
  fn: Callable,
123
  log_level: int = logging.INFO,
124
  logger_name: str | None = None,
125
  **kwargs,
126
  ) -> Generator[List[Log], None, None]:
127
+ """Run Python code in a process and capture logs in real-time to yield them."""
128
  yield self.log(
129
  f"Running {fn.__name__}({', '.join(f'{k}={v}' for k, v in kwargs.items())})"
130
  )
131
+ logs_queue, stdout_queue, stderr_queue, error_queue, wrapped_fn = wrap_for_process(fn, log_level=log_level, logger_name=logger_name)
132
+ self.process = Process(target=wrapped_fn, kwargs=kwargs)
133
+
134
+ # Start process and pull logs while it runs
135
+ self.process.start()
136
+ while self.process.is_alive():
137
+ yield from self._log_from_queue(logs_queue)
138
+ yield from self._log_from_queue(stdout_queue)
139
+ yield from self._log_from_queue(stderr_queue)
140
+ self.process.join(timeout=0.1)
141
+
142
+ # After the process completes, yield any remaining logs
143
+ yield from self._log_from_queue(logs_queue)
144
+ yield from self._log_from_queue(stdout_queue)
145
+ yield from self._log_from_queue(stderr_queue)
146
+
147
+ # Check for error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  try:
149
+ error_msg = error_queue.get_nowait()
150
+ self.exit_code = 1
151
+ yield self.log(error_msg, level="ERROR")
152
  except queue.Empty:
153
+ yield self.log("Process completed successfully")
154
+ self.exit_code = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  def __repr__(self) -> str:
157
+ return f"<LogsViewRunner nb_logs={len(self.logs)} exit_code={self.exit_code}>"
158
 
159
  def __del__(self):
160
+ if (isinstance(self.process, Process) and self.process.is_alive()) or (isinstance(self.process, subprocess.Popen) and self.process.poll() is None):
161
+ try:
162
+ print(f"Terminating process {self.process}")
163
+ self.process.terminate()
164
+ except Exception as e:
165
+ print(f"Failed to terminate process: {e}")
166
+ finally:
167
+ print(f"Killing process: {self.process}")
168
+ self.process.kill()
169
 
170
  class LogsView(Component):
171
  """
 
259
 
260
 
261
  @contextmanager
262
+ def capture_logging(log_queue: mpQueue, log_level: int, logger_name: str | None = None) -> Generator:
263
  # Create a queue to capture log messages
264
+ logger = logging.getLogger(logger_name)
 
265
  logger.setLevel(log_level)
266
  handler = QueueHandler(log_queue)
267
  logger.addHandler(handler)
268
 
269
+ yield
 
270
 
271
  # Clean up
272
  logger.removeHandler(handler)
273
 
274
 
275
+ def wrap_for_process(fn: Callable, log_level: int, logger_name: str | None) -> Callable:
276
+ logs_queue = mpQueue()
277
+ stdout_queue = mpQueue()
278
+ stderr_queue = mpQueue()
279
+ error_queue = mpQueue()
280
 
281
  @wraps(fn)
282
  def _inner(*args, **kwargs):
283
+ with capture_logging(logs_queue, log_level, logger_name):
284
+ with redirect_stdout(CapturingStream(stdout_queue)):
285
+ with redirect_stderr(CapturingStream(stderr_queue)):
286
+ try:
287
+ fn(*args, **kwargs)
288
+ except Exception as error:
289
+ msg = (
290
+ f"Error in '{fn.__name__}':"
291
+ + "\n"
292
+ + "\n".join(
293
+ line.strip("\n")
294
+ for line in traceback.format_tb(error.__traceback__)
295
+ if line.strip()
296
+ )
297
+ + "\n\n"
298
+ + str(error)
299
+ )
300
+ error_queue.put(msg)
301
+
302
+ return logs_queue, stdout_queue, stderr_queue, error_queue, _inner
303
+
304
+ class CapturingStream(io.StringIO):
305
+ """Stream to capture stdout/stderr line by line and put them in a queue."""
306
+ def __init__(self, queue: mpQueue, *args, **kwargs):
307
  super().__init__(*args, **kwargs)
308
  self._queue = queue
309
  self._current_line = ""
310
 
311
  def write(self, s: str) -> int:
312
+ s = s.replace("\r", "\n") # Remove carriage return (from tqdm)
313
  if "\n" in s:
314
  lines = s.split("\n")
315
  for line in lines[:-1]:
src/demo/app.py CHANGED
@@ -4,11 +4,13 @@ import time
4
 
5
  import gradio as gr
6
  from gradio_logsview import LogsView, LogsViewRunner
 
7
 
 
8
 
9
  def random_values(failing: bool = False):
10
- for i in range(10):
11
- logging.log(
12
  random.choice(
13
  [ # Random levels
14
  logging.INFO,
@@ -25,38 +27,38 @@ def random_values(failing: bool = False):
25
  raise ValueError("Failing!!")
26
 
27
 
28
- def fn_process_success():
29
  runner = LogsViewRunner()
30
- yield from runner.run_process(["python", "-u", "demo/script.py"])
31
  yield runner.log(f"Runner: {runner}")
32
 
33
 
34
- def fn_process_failing():
35
  runner = LogsViewRunner()
36
- yield from runner.run_process(["python", "-u", "demo/script.py", "--failing"])
37
  yield runner.log(f"Runner: {runner}")
38
 
39
 
40
- def fn_thread_success():
41
  runner = LogsViewRunner()
42
- yield from runner.run_thread(random_values, log_level=logging.INFO, failing=False)
43
  yield runner.log(f"Runner: {runner}")
44
 
45
 
46
- def fn_thread_failing():
47
  runner = LogsViewRunner()
48
- yield from runner.run_thread(random_values, log_level=logging.INFO, failing=True)
49
  yield runner.log(f"Runner: {runner}")
50
 
51
 
52
  markdown_top = """
53
  # LogsView Demo
54
 
55
- This demo shows how to use the `LogsView` component to display logs from a process or a thread in real-time.
56
 
57
- Click on any button to launch a process or a thread and see the logs displayed in real-time.
58
- In the thread example, logs are generated randomly with different log levels.
59
- In the process example, logs are generated by a Python script but any command can be executed.
60
  """
61
 
62
 
@@ -64,55 +66,61 @@ markdown_bottom = """
64
  ## Installation
65
 
66
  ```
67
- pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.3-py3-none-any.whl
68
  ```
69
 
70
  or add this line to your `requirements.txt`:
71
 
72
  ```
73
- gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.3-py3-none-any.whl
74
  ```
75
 
76
- ## How to run in a thread?
77
 
78
- With `LogsView.run_thread`, you can run a function in a separate thread and capture logs in real-time.
79
  You can configure which logs to capture (log level and logger name).
80
 
81
  ```py
82
  from gradio_logsview import LogsView
83
 
84
- def fn_thread():
85
- # Run `my_function` in a separate thread
86
  # All logs above `INFO` level will be captured and displayed in real-time.
87
  runner = LogsViewRunner() # Initialize the runner
88
- yield from runner.run_thread(my_function, log_level=logging.INFO, arg1="value1")
89
  yield runner.log(f"Runner: {runner}") # Log any message
 
 
 
90
 
91
  with gr.Blocks() as demo:
92
  logs = LogsView()
93
- btn = gr.Button("Run thread")
94
- btn.click(fn_thread, outputs=logs)
95
  ```
96
 
97
- ## How to run in a process?
98
 
99
- With `LogsView.run_process`, you can run a command in a separate process and capture logs from the process in real-time.
100
 
101
  ```py
102
  from gradio_logsview import LogsView
103
 
104
- def fn_process():
105
- # Run a process and capture all logs from the process
106
  runner = LogsViewRunner() # Initialize the runner
107
- yield from runner.run_process(
108
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
109
  )
110
  yield runner.log(f"Runner: {runner}") # Log any message
 
 
 
111
 
112
  with gr.Blocks() as demo:
113
  logs = LogsView()
114
- btn = gr.Button("Run process")
115
- btn.click(fn_process, outputs=logs)
116
  ```
117
 
118
  ## TODO
@@ -121,30 +129,32 @@ with gr.Blocks() as demo:
121
  - [ ] format logs client-side (front-end)
122
  - [ ] scrollable logs if more than N lines (front-end)
123
  - [ ] format each log only once (front-end)
124
- - [x] stop process if `run_process` gets cancelled (back-end)
125
- - [x] correctly pass error stacktrace in `run_thread` (back-end)
126
- - [ ] correctly catch print statements in `run_thread` (back-end)
127
  - [ ] disable interactivity + remove all code editing logic (both?)
128
- - [ ] how to handle progress bars? (i.e when logs are overwritten in terminal)
 
 
129
  """
130
 
131
  with gr.Blocks() as demo:
132
  gr.Markdown(markdown_top)
133
 
134
  with gr.Row():
135
- btn_thread_success = gr.Button("Run thread (success)")
136
- btn_thread_failing = gr.Button("Run thread (failing)")
137
  with gr.Row():
138
- btn_process_success = gr.Button("Run process (success)")
139
- btn_process_failing = gr.Button("Run process (failing)")
140
  logs = LogsView()
141
 
142
  gr.Markdown(markdown_bottom)
143
 
144
- btn_thread_failing.click(fn_thread_failing, outputs=logs)
145
- btn_thread_success.click(fn_thread_success, outputs=logs)
146
- btn_process_failing.click(fn_process_failing, outputs=logs)
147
- btn_process_success.click(fn_process_success, outputs=logs)
148
 
149
 
150
  if __name__ == "__main__":
 
4
 
5
  import gradio as gr
6
  from gradio_logsview import LogsView, LogsViewRunner
7
+ from tqdm import tqdm
8
 
9
+ logger = logging.getLogger("custom.foo")
10
 
11
  def random_values(failing: bool = False):
12
+ for i in tqdm(range(10)):
13
+ logger.log(
14
  random.choice(
15
  [ # Random levels
16
  logging.INFO,
 
27
  raise ValueError("Failing!!")
28
 
29
 
30
+ def fn_command_success():
31
  runner = LogsViewRunner()
32
+ yield from runner.run_command(["python", "-u", "demo/script.py"])
33
  yield runner.log(f"Runner: {runner}")
34
 
35
 
36
+ def fn_command_failing():
37
  runner = LogsViewRunner()
38
+ yield from runner.run_command(["python", "-u", "demo/script.py", "--failing"])
39
  yield runner.log(f"Runner: {runner}")
40
 
41
 
42
+ def fn_python_success():
43
  runner = LogsViewRunner()
44
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=False)
45
  yield runner.log(f"Runner: {runner}")
46
 
47
 
48
+ def fn_python_failing():
49
  runner = LogsViewRunner()
50
+ yield from runner.run_python(random_values, log_level=logging.INFO, logger_name="custom.foo", failing=True)
51
  yield runner.log(f"Runner: {runner}")
52
 
53
 
54
  markdown_top = """
55
  # LogsView Demo
56
 
57
+ This demo shows how to use the `LogsView` component to run some Python code or execute a command and display logs in real-time.
58
 
59
+ Click on any button to launch a process and see the logs displayed in real-time.
60
+ In the Python examples, code is executed in a process. You can see the logs (generated randomly with different log levels).
61
+ In the command examples, the command line is executed on the system directly. Any command can be executed.
62
  """
63
 
64
 
 
66
  ## Installation
67
 
68
  ```
69
+ pip install https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
70
  ```
71
 
72
  or add this line to your `requirements.txt`:
73
 
74
  ```
75
+ gradio_logsview@https://huggingface.co/spaces/Wauplin/gradio_logsview/resolve/main/gradio_logsview-0.0.5-py3-none-any.whl
76
  ```
77
 
78
+ ## How to run Python code?
79
 
80
+ With `LogsView.run_python`, you can run Python code in a separate process and capture logs in real-time.
81
  You can configure which logs to capture (log level and logger name).
82
 
83
  ```py
84
  from gradio_logsview import LogsView
85
 
86
+ def fn_python():
87
+ # Run `my_function` in a separate process
88
  # All logs above `INFO` level will be captured and displayed in real-time.
89
  runner = LogsViewRunner() # Initialize the runner
90
+ yield from runner.run_python(my_function, log_level=logging.INFO, arg1="value1")
91
  yield runner.log(f"Runner: {runner}") # Log any message
92
+ if runner.exit_code != 0:
93
+ # Handle error
94
+ ...
95
 
96
  with gr.Blocks() as demo:
97
  logs = LogsView()
98
+ btn = gr.Button("Run Python code")
99
+ btn.click(fn_python, outputs=logs)
100
  ```
101
 
102
+ ## How to run a command?
103
 
104
+ With `LogsView.run_command`, you can run a command on the system and capture logs from the process in real-time.
105
 
106
  ```py
107
  from gradio_logsview import LogsView
108
 
109
+ def fn_command():
110
+ # Run a command and capture all logs from the subprocess
111
  runner = LogsViewRunner() # Initialize the runner
112
+ yield from runner.run_command(
113
  cmd=["mergekit-yaml", "config.yaml", "merge", "--copy-", "--cuda", "--low-cpu-memory"]
114
  )
115
  yield runner.log(f"Runner: {runner}") # Log any message
116
+ if runner.exit_code != 0:
117
+ # Handle error
118
+ ...
119
 
120
  with gr.Blocks() as demo:
121
  logs = LogsView()
122
+ btn = gr.Button("Run command")
123
+ btn.click(fn_command, outputs=logs)
124
  ```
125
 
126
  ## TODO
 
129
  - [ ] format logs client-side (front-end)
130
  - [ ] scrollable logs if more than N lines (front-end)
131
  - [ ] format each log only once (front-end)
132
+ - [x] stop process if `run_command` gets cancelled (back-end)
133
+ - [x] correctly pass error stacktrace in `run_python` (back-end)
134
+ - [x] correctly catch print statements in `run_python` (back-end)
135
  - [ ] disable interactivity + remove all code editing logic (both?)
136
+ - [x] how to handle progress bars? (i.e when logs are overwritten in terminal)
137
+ - [ ] Have 3 tabs in UI for stdout, stderr and logging (front-end + back-end)
138
+ - [ ] Write logger name in the logs (back-end)
139
  """
140
 
141
  with gr.Blocks() as demo:
142
  gr.Markdown(markdown_top)
143
 
144
  with gr.Row():
145
+ btn_python_success = gr.Button("Run Python code (success)")
146
+ btn_python_failing = gr.Button("Run Python code (failing)")
147
  with gr.Row():
148
+ btn_command_success = gr.Button("Run command (success)")
149
+ btn_command_failing = gr.Button("Run command (failing)")
150
  logs = LogsView()
151
 
152
  gr.Markdown(markdown_bottom)
153
 
154
+ btn_python_failing.click(fn_python_failing, outputs=logs)
155
+ btn_python_success.click(fn_python_success, outputs=logs)
156
+ btn_command_failing.click(fn_command_failing, outputs=logs)
157
+ btn_command_success.click(fn_command_success, outputs=logs)
158
 
159
 
160
  if __name__ == "__main__":