Wauplin HF staff commited on
Commit
9bdb607
1 Parent(s): c573da3

Update space.py

Browse files
Files changed (1) hide show
  1. space.py +4 -345
space.py CHANGED
@@ -1,43 +1,3 @@
1
-
2
- import gradio as gr
3
- from app import demo as app
4
- import os
5
-
6
- _docs = {'LogsView': {'description': 'Creates a component to visualize logs from a subprocess in real-time.', 'members': {'__init__': {'value': {'type': 'str | Callable | tuple[str] | None', 'default': 'None', 'description': 'Default value to show in the code editor. If callable, the function will be called whenever the app loads to set the initial value of the component.'}, 'every': {'type': 'float | None', 'default': 'None', 'description': "If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute."}, 'lines': {'type': 'int', 'default': '5', 'description': None}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}}, 'postprocess': {'value': {'type': 'list[Log]', 'description': 'Expects a list of `Log` logs.'}}, 'preprocess': {'return': {'type': 'LogsView', 'description': 'Passes the code entered as a `str`.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the LogsView changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the LogsView.'}, 'focus': {'type': None, 'default': None, 'description': 'This listener is triggered when the LogsView is focused.'}, 'blur': {'type': None, 'default': None, 'description': 'This listener is triggered when the LogsView is unfocused/blurred.'}}}, '__meta__': {'additional_interfaces': {'Log': {'source': '@dataclass\nclass Log:\n level: Literal[\n "INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"\n ]\n message: str\n timestamp: str'}, 'LogsView': {'source': 'class LogsView(Component):\n EVENTS = [\n Events.change,\n Events.input,\n Events.focus,\n Events.blur,\n ]\n\n def __init__(\n self,\n value: str | Callable | tuple[str] | None = None,\n *,\n every: float | None = None,\n lines: int = 5,\n label: str | None = None,\n show_label: bool | None = None,\n container: bool = True,\n scale: int | None = None,\n min_width: int = 160,\n visible: bool = True,\n elem_id: str | None = None,\n elem_classes: list[str] | str | None = None,\n render: bool = True,\n ):\n self.language = "shell"\n self.lines = lines\n self.interactive = False\n super().__init__(\n label=label,\n every=every,\n show_label=show_label,\n container=container,\n scale=scale,\n min_width=min_width,\n visible=visible,\n elem_id=elem_id,\n elem_classes=elem_classes,\n render=render,\n value=value,\n )\n\n def preprocess(self, payload: str | None) -> "LogsView":\n raise NotImplementedError(\n "LogsView cannot be used as an input component."\n )\n\n def postprocess(self, value: List[Log]) -> List[Log]:\n return value\n\n def api_info(self) -> dict[str, Any]:\n return {\n "items": {\n "level": "string",\n "message": "string",\n "timestamp": "number",\n },\n "title": "Logs",\n "type": "array",\n }\n\n def example_payload(self) -> Any:\n return [\n Log(\n "INFO",\n "Hello World",\n datetime.now().isoformat(),\n )\n ]\n\n def example_value(self) -> Any:\n return [\n Log(\n "INFO",\n "Hello World",\n datetime.now().isoformat(),\n )\n ]\n\n @classmethod\n def run_process(\n cls,\n command: List[str],\n date_format: str = "%Y-%m-%d %H:%M:%S",\n ) -> Generator[List[Log], None, None]:\n process = subprocess.Popen(\n command,\n stdout=subprocess.PIPE,\n stderr=subprocess.STDOUT,\n text=True,\n )\n\n if process.stdout is None:\n raise ValueError("stdout is None")\n\n logs = []\n\n def _log(level: str, message: str):\n log = Log(\n level=level,\n message=message,\n timestamp=datetime.now().strftime(\n date_format\n ),\n )\n logs.append(log)\n return logs\n\n _log("INFO", f"Running {\' \'.join(command)}")\n for line in process.stdout:\n yield _log("INFO", line.strip())\n\n # TODO: what if task is cancelled but process is still running?\n\n process.stdout.close()\n return_code = process.wait()\n if return_code:\n yield _log(\n "ERROR",\n f"Process exited with code {return_code}",\n )\n else:\n yield _log(\n "INFO", "Process exited successfully"\n )\n\n @classmethod\n def run_thread(\n cls,\n fn: Callable,\n log_level: int = logging.INFO,\n logger_name: str | None = None,\n date_format: str = "%Y-%m-%d %H:%M:%S",\n **kwargs,\n ) -> Generator[List[Log], None, None]:\n logs = [\n Log(\n level="INFO",\n message=f"Running {fn.__name__}({\', \'.join(f\'{k}={v}\' for k, v in kwargs.items())})",\n timestamp=datetime.now().strftime(\n date_format\n ),\n )\n ]\n yield logs\n\n thread = Thread(\n target=non_failing_fn(fn), kwargs=kwargs\n )\n\n def _log(record: logging.LogRecord) -> bool:\n if record.thread != thread.ident:\n return False # Skip if not from the thread\n if logger_name and not record.name.startswith(\n logger_name\n ):\n return False # Skip if not from the logger\n if record.levelno < log_level:\n return False # Skip if too verbose\n log = Log(\n level=record.levelname,\n message=record.getMessage(),\n timestamp=datetime.fromtimestamp(\n record.created\n ).strftime(date_format),\n )\n logs.append(log)\n return True\n\n with capture_logging(log_level) as log_queue:\n thread.start()\n\n # Loop to capture and yield logs from the thread\n while thread.is_alive():\n while True:\n try:\n if _log(log_queue.get_nowait()):\n yield logs\n except queue.Empty:\n break\n thread.join(\n timeout=0.1\n ) # adjust the timeout as needed\n\n # After the thread completes, yield any remaining logs\n while True:\n try:\n if _log(log_queue.get_nowait()):\n yield logs\n except queue.Empty:\n break\n\n logs.append(\n Log(\n level="INFO",\n message="Thread completed successfully",\n timestamp=datetime.now().strftime(\n date_format\n ),\n )\n )'}}, 'user_fn_refs': {'LogsView': ['Log', 'LogsView']}}}
7
-
8
- abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
-
10
- with gr.Blocks(
11
- css=abs_path,
12
- theme=gr.themes.Default(
13
- font_mono=[
14
- gr.themes.GoogleFont("Inconsolata"),
15
- "monospace",
16
- ],
17
- ),
18
- ) as demo:
19
- gr.Markdown(
20
- """
21
- # `gradio_logsview`
22
-
23
- <div style="display: flex; gap: 7px;">
24
- <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
- </div>
26
-
27
- Visualize logs in your Gradio app
28
- """, elem_classes=["md-custom"], header_links=True)
29
- app.render()
30
- gr.Markdown(
31
- """
32
- ## Installation
33
-
34
- ```bash
35
- pip install gradio_logsview
36
- ```
37
-
38
- ## Usage
39
-
40
- ```python
41
  import logging
42
  import random
43
  import time
@@ -81,7 +41,7 @@ def fn_thread_failing():
81
  yield from LogsView.run_thread(random_values, log_level=logging.INFO, failing=True)
82
 
83
 
84
- markdown_top = \"\"\"
85
  # LogsView Demo
86
 
87
  This demo shows how to use the `LogsView` component to display logs from a process or a thread in real-time.
@@ -89,10 +49,10 @@ This demo shows how to use the `LogsView` component to display logs from a proce
89
  Click on any button to launch a process or a thread and see the logs displayed in real-time.
90
  In the thread example, logs are generated randomly with different log levels.
91
  In the process example, logs are generated by a Python script but any command can be executed.
92
- \"\"\"
93
 
94
 
95
- markdown_bottom = \"\"\"
96
  ## How to run in a thread?
97
 
98
  With `LogsView.run_thread`, you can run a function in a separate thread and capture logs in real-time.
@@ -130,7 +90,7 @@ with gr.Blocks() as demo:
130
  btn = gr.Button("Run process")
131
  btn.click(fn_process, outputs=logs)
132
  ```
133
- \"\"\"
134
 
135
  with gr.Blocks() as demo:
136
  gr.Markdown(markdown_top)
@@ -153,304 +113,3 @@ with gr.Blocks() as demo:
153
 
154
  if __name__ == "__main__":
155
  demo.launch()
156
-
157
- ```
158
- """, elem_classes=["md-custom"], header_links=True)
159
-
160
-
161
- gr.Markdown("""
162
- ## `LogsView`
163
-
164
- ### Initialization
165
- """, elem_classes=["md-custom"], header_links=True)
166
-
167
- gr.ParamViewer(value=_docs["LogsView"]["members"]["__init__"], linkify=['Log', 'LogsView'])
168
-
169
-
170
- gr.Markdown("### Events")
171
- gr.ParamViewer(value=_docs["LogsView"]["events"], linkify=['Event'])
172
-
173
-
174
-
175
-
176
- gr.Markdown("""
177
-
178
- ### User function
179
-
180
- The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
181
-
182
- - When used as an Input, the component only impacts the input signature of the user function.
183
- - When used as an output, the component only impacts the return signature of the user function.
184
-
185
- The code snippet below is accurate in cases where the component is used as both an input and an output.
186
-
187
- - **As input:** Is passed, passes the code entered as a `str`.
188
- - **As output:** Should return, expects a list of `Log` logs.
189
-
190
- ```python
191
- def predict(
192
- value: LogsView
193
- ) -> list[Log]:
194
- return value
195
- ```
196
- """, elem_classes=["md-custom", "LogsView-user-fn"], header_links=True)
197
-
198
-
199
-
200
-
201
- code_Log = gr.Markdown("""
202
- ## `Log`
203
- ```python
204
- @dataclass
205
- class Log:
206
- level: Literal[
207
- "INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"
208
- ]
209
- message: str
210
- timestamp: str
211
- ```""", elem_classes=["md-custom", "Log"], header_links=True)
212
-
213
- code_LogsView = gr.Markdown("""
214
- ## `LogsView`
215
- ```python
216
- class LogsView(Component):
217
- EVENTS = [
218
- Events.change,
219
- Events.input,
220
- Events.focus,
221
- Events.blur,
222
- ]
223
-
224
- def __init__(
225
- self,
226
- value: str | Callable | tuple[str] | None = None,
227
- *,
228
- every: float | None = None,
229
- lines: int = 5,
230
- label: str | None = None,
231
- show_label: bool | None = None,
232
- container: bool = True,
233
- scale: int | None = None,
234
- min_width: int = 160,
235
- visible: bool = True,
236
- elem_id: str | None = None,
237
- elem_classes: list[str] | str | None = None,
238
- render: bool = True,
239
- ):
240
- self.language = "shell"
241
- self.lines = lines
242
- self.interactive = False
243
- super().__init__(
244
- label=label,
245
- every=every,
246
- show_label=show_label,
247
- container=container,
248
- scale=scale,
249
- min_width=min_width,
250
- visible=visible,
251
- elem_id=elem_id,
252
- elem_classes=elem_classes,
253
- render=render,
254
- value=value,
255
- )
256
-
257
- def preprocess(self, payload: str | None) -> "LogsView":
258
- raise NotImplementedError(
259
- "LogsView cannot be used as an input component."
260
- )
261
-
262
- def postprocess(self, value: List[Log]) -> List[Log]:
263
- return value
264
-
265
- def api_info(self) -> dict[str, Any]:
266
- return {
267
- "items": {
268
- "level": "string",
269
- "message": "string",
270
- "timestamp": "number",
271
- },
272
- "title": "Logs",
273
- "type": "array",
274
- }
275
-
276
- def example_payload(self) -> Any:
277
- return [
278
- Log(
279
- "INFO",
280
- "Hello World",
281
- datetime.now().isoformat(),
282
- )
283
- ]
284
-
285
- def example_value(self) -> Any:
286
- return [
287
- Log(
288
- "INFO",
289
- "Hello World",
290
- datetime.now().isoformat(),
291
- )
292
- ]
293
-
294
- @classmethod
295
- def run_process(
296
- cls,
297
- command: List[str],
298
- date_format: str = "%Y-%m-%d %H:%M:%S",
299
- ) -> Generator[List[Log], None, None]:
300
- process = subprocess.Popen(
301
- command,
302
- stdout=subprocess.PIPE,
303
- stderr=subprocess.STDOUT,
304
- text=True,
305
- )
306
-
307
- if process.stdout is None:
308
- raise ValueError("stdout is None")
309
-
310
- logs = []
311
-
312
- def _log(level: str, message: str):
313
- log = Log(
314
- level=level,
315
- message=message,
316
- timestamp=datetime.now().strftime(
317
- date_format
318
- ),
319
- )
320
- logs.append(log)
321
- return logs
322
-
323
- _log("INFO", f"Running {' '.join(command)}")
324
- for line in process.stdout:
325
- yield _log("INFO", line.strip())
326
-
327
- # TODO: what if task is cancelled but process is still running?
328
-
329
- process.stdout.close()
330
- return_code = process.wait()
331
- if return_code:
332
- yield _log(
333
- "ERROR",
334
- f"Process exited with code {return_code}",
335
- )
336
- else:
337
- yield _log(
338
- "INFO", "Process exited successfully"
339
- )
340
-
341
- @classmethod
342
- def run_thread(
343
- cls,
344
- fn: Callable,
345
- log_level: int = logging.INFO,
346
- logger_name: str | None = None,
347
- date_format: str = "%Y-%m-%d %H:%M:%S",
348
- **kwargs,
349
- ) -> Generator[List[Log], None, None]:
350
- logs = [
351
- Log(
352
- level="INFO",
353
- message=f"Running {fn.__name__}({', '.join(f'{k}={v}' for k, v in kwargs.items())})",
354
- timestamp=datetime.now().strftime(
355
- date_format
356
- ),
357
- )
358
- ]
359
- yield logs
360
-
361
- thread = Thread(
362
- target=non_failing_fn(fn), kwargs=kwargs
363
- )
364
-
365
- def _log(record: logging.LogRecord) -> bool:
366
- if record.thread != thread.ident:
367
- return False # Skip if not from the thread
368
- if logger_name and not record.name.startswith(
369
- logger_name
370
- ):
371
- return False # Skip if not from the logger
372
- if record.levelno < log_level:
373
- return False # Skip if too verbose
374
- log = Log(
375
- level=record.levelname,
376
- message=record.getMessage(),
377
- timestamp=datetime.fromtimestamp(
378
- record.created
379
- ).strftime(date_format),
380
- )
381
- logs.append(log)
382
- return True
383
-
384
- with capture_logging(log_level) as log_queue:
385
- thread.start()
386
-
387
- # Loop to capture and yield logs from the thread
388
- while thread.is_alive():
389
- while True:
390
- try:
391
- if _log(log_queue.get_nowait()):
392
- yield logs
393
- except queue.Empty:
394
- break
395
- thread.join(
396
- timeout=0.1
397
- ) # adjust the timeout as needed
398
-
399
- # After the thread completes, yield any remaining logs
400
- while True:
401
- try:
402
- if _log(log_queue.get_nowait()):
403
- yield logs
404
- except queue.Empty:
405
- break
406
-
407
- logs.append(
408
- Log(
409
- level="INFO",
410
- message="Thread completed successfully",
411
- timestamp=datetime.now().strftime(
412
- date_format
413
- ),
414
- )
415
- )
416
- ```""", elem_classes=["md-custom", "LogsView"], header_links=True)
417
-
418
- demo.load(None, js=r"""function() {
419
- const refs = {
420
- Log: [],
421
- LogsView: [], };
422
- const user_fn_refs = {
423
- LogsView: ['Log', 'LogsView'], };
424
- requestAnimationFrame(() => {
425
-
426
- Object.entries(user_fn_refs).forEach(([key, refs]) => {
427
- if (refs.length > 0) {
428
- const el = document.querySelector(`.${key}-user-fn`);
429
- if (!el) return;
430
- refs.forEach(ref => {
431
- el.innerHTML = el.innerHTML.replace(
432
- new RegExp("\\b"+ref+"\\b", "g"),
433
- `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
434
- );
435
- })
436
- }
437
- })
438
-
439
- Object.entries(refs).forEach(([key, refs]) => {
440
- if (refs.length > 0) {
441
- const el = document.querySelector(`.${key}`);
442
- if (!el) return;
443
- refs.forEach(ref => {
444
- el.innerHTML = el.innerHTML.replace(
445
- new RegExp("\\b"+ref+"\\b", "g"),
446
- `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
447
- );
448
- })
449
- }
450
- })
451
- })
452
- }
453
-
454
- """)
455
-
456
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import logging
2
  import random
3
  import time
 
41
  yield from LogsView.run_thread(random_values, log_level=logging.INFO, failing=True)
42
 
43
 
44
+ markdown_top = """
45
  # LogsView Demo
46
 
47
  This demo shows how to use the `LogsView` component to display logs from a process or a thread in real-time.
 
49
  Click on any button to launch a process or a thread and see the logs displayed in real-time.
50
  In the thread example, logs are generated randomly with different log levels.
51
  In the process example, logs are generated by a Python script but any command can be executed.
52
+ """
53
 
54
 
55
+ markdown_bottom = """
56
  ## How to run in a thread?
57
 
58
  With `LogsView.run_thread`, you can run a function in a separate thread and capture logs in real-time.
 
90
  btn = gr.Button("Run process")
91
  btn.click(fn_process, outputs=logs)
92
  ```
93
+ """
94
 
95
  with gr.Blocks() as demo:
96
  gr.Markdown(markdown_top)
 
113
 
114
  if __name__ == "__main__":
115
  demo.launch()