vlmparse 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vlmparse/cli.py CHANGED
@@ -1,334 +1,486 @@
1
+ # ruff: noqa: B008
1
2
  from typing import Literal
2
3
 
4
+ import typer
3
5
  from loguru import logger
4
6
 
5
-
6
- class DParseCLI:
7
- """Parsing of pdf to text using VLMs: typ in vlmparse to get the command lists, then `vlmparse <command> --help` to get help on a specific command."""
8
-
9
- def serve(
10
- self,
11
- model: str,
12
- port: int | None = None,
13
- gpus: str | None = None,
14
- vllm_args: list[str] | None = None,
15
- forget_predefined_vllm_args: bool = False,
16
- ):
17
- """Deploy a VLLM server in a Docker container.
18
-
19
- Args:
20
- model: Model name
21
- port: VLLM server port (default: 8056)
22
- gpus: Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, all GPUs will be used.
23
- vllm_args: Additional keyword arguments to pass to the VLLM server.
24
- forget_predefined_vllm_args: If True, the predefined VLLM kwargs from the docker config will be replaced by vllm_args otherwise the predefined kwargs will be updated with vllm_args with a risk of collision of argument names.
25
- """
26
-
27
- from vlmparse.converter_with_server import start_server
28
-
29
- base_url, container, _, _ = start_server(
30
- model=model,
31
- gpus=gpus,
32
- port=port,
33
- with_vllm_server=True,
34
- vllm_args=vllm_args,
35
- forget_predefined_vllm_args=forget_predefined_vllm_args,
36
- auto_stop=False,
7
+ app = typer.Typer(help="Parse PDF documents with VLMs.", pretty_exceptions_enable=False)
8
+
9
+
10
+ @app.command("serve")
11
+ def serve(
12
+ model: str = typer.Argument(..., help="Model name"),
13
+ port: int | None = typer.Option(None, help="VLLM server port (default: 8056)"),
14
+ gpus: str | None = typer.Option(
15
+ None,
16
+ help='Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, all GPUs will be used.',
17
+ ),
18
+ provider: Literal["registry", "hf"] = typer.Option(
19
+ "registry", help="provider type for the model. 'registry' (default) or 'hf'."
20
+ ),
21
+ vllm_args: list[str] | None = typer.Option(
22
+ None,
23
+ "--vllm-args",
24
+ help="Additional keyword arguments to pass to the VLLM server.",
25
+ ),
26
+ forget_predefined_vllm_args: bool = typer.Option(
27
+ False,
28
+ help=(
29
+ "If True, the predefined VLLM kwargs from the docker config will be replaced by vllm_args. "
30
+ "Otherwise they will be updated with vllm_args (may overwrite keys)."
31
+ ),
32
+ ),
33
+ ):
34
+ """Deploy a VLLM server in a Docker container.
35
+
36
+ Args:
37
+ model: Model name
38
+ port: VLLM server port (default: 8056)
39
+ gpus: Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, GPU 0 will be used.
40
+ server: Server type for the model. 'registry' (default) or 'hf'.
41
+ vllm_args: Additional keyword arguments to pass to the VLLM server.
42
+ forget_predefined_vllm_args: If True, the predefined VLLM kwargs from the docker config will be replaced by vllm_args otherwise the predefined kwargs will be updated with vllm_args with a risk of collision of argument names.
43
+ """
44
+
45
+ from vlmparse.converter_with_server import start_server
46
+
47
+ base_url, container, _, _ = start_server(
48
+ model=model,
49
+ gpus=gpus,
50
+ port=port,
51
+ provider=provider,
52
+ vllm_args=vllm_args,
53
+ forget_predefined_vllm_args=forget_predefined_vllm_args,
54
+ auto_stop=False,
55
+ )
56
+
57
+ logger.info(f"✓ VLLM server ready at {base_url}")
58
+ if container is not None:
59
+ logger.info(f"✓ Container ID: {container.id}")
60
+ logger.info(f"✓ Container name: {container.name}")
61
+
62
+
63
+ @app.command("convert")
64
+ def convert(
65
+ inputs: str = typer.Argument(..., help="List of folders to process"),
66
+ out_folder: str = typer.Option(".", help="Output folder for parsed documents"),
67
+ model: str | None = typer.Option(
68
+ None, help="Model name. If not specified, inferred from the URI."
69
+ ),
70
+ uri: str | None = typer.Option(
71
+ None,
72
+ help=(
73
+ "URI of the server. If not specified and the pipe is vllm, "
74
+ "a local server will be deployed."
75
+ ),
76
+ ),
77
+ gpus: str | None = typer.Option(
78
+ None,
79
+ help='Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, GPU 0 will be used.',
80
+ ),
81
+ mode: Literal["document", "md", "md_page"] = typer.Option(
82
+ "document",
83
+ help=(
84
+ "Output mode - document (save as JSON zip), md (save as markdown file), "
85
+ "md_page (save as folder of markdown pages)"
86
+ ),
87
+ ),
88
+ conversion_mode: Literal[
89
+ "ocr",
90
+ "ocr_layout",
91
+ "table",
92
+ "image_description",
93
+ "formula",
94
+ "chart",
95
+ ] = typer.Option(
96
+ "ocr",
97
+ help=(
98
+ "Conversion mode - ocr (plain), ocr_layout (OCR with layout), table (table-centric), "
99
+ "image_description (describe the image), formula (formula extraction), chart (chart recognition)"
100
+ ),
101
+ ),
102
+ provider: Literal["registry", "hf", "google", "openai"] = typer.Option(
103
+ "registry",
104
+ help="Server type for the model. Defaults to 'registry'.",
105
+ ),
106
+ with_vllm_server: bool = typer.Option(
107
+ False,
108
+ help=(
109
+ "Deprecated. Use --server hf instead. "
110
+ "If True, a local VLLM server will be deployed if the model is not found in the registry. "
111
+ "Note that if the model is in the registry and uri is None, the server will be deployed."
112
+ ),
113
+ ),
114
+ concurrency: int = typer.Option(10, help="Number of parallel requests"),
115
+ dpi: int | None = typer.Option(None, help="DPI to use for the conversion"),
116
+ debug: bool = typer.Option(False, help="Run in debug mode"),
117
+ _return_documents: bool = typer.Option(False, hidden=True),
118
+ ):
119
+ """Parse PDF documents and save results.
120
+
121
+ Args:
122
+ inputs: List of folders to process
123
+ out_folder: Output folder for parsed documents
124
+ pipe: Converter type ("vllm", "openai", or "lightonocr", default: "vllm")
125
+ model: Model name. If not specified, the model will be inferred from the URI.
126
+ uri: URI of the server, if not specified and the pipe is vllm, a local server will be deployed
127
+ gpus: Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, all GPUs will be used.
128
+ mode: Output mode - "document" (save as JSON zip), "md" (save as markdown file), "md_page" (save as folder of markdown pages)
129
+ conversion_mode: Conversion mode - "ocr" (plain), "ocr_layout" (OCR with layout), "table" (table-centric), "image_description" (describe the image), "formula" (formula extraction), "chart" (chart recognition)
130
+ provider: provider type for the model. Defaults to 'registry'.
131
+ dpi: DPI to use for the conversion. If not specified, the default DPI will be used.
132
+ debug: If True, run in debug mode (single-threaded, no concurrency)
133
+ """
134
+ from vlmparse.converter_with_server import ConverterWithServer
135
+
136
+ if with_vllm_server and provider == "registry":
137
+ provider = "hf"
138
+
139
+ with ConverterWithServer(
140
+ model=model,
141
+ uri=uri,
142
+ gpus=gpus,
143
+ provider=provider,
144
+ concurrency=concurrency,
145
+ return_documents=_return_documents,
146
+ ) as converter_with_server:
147
+ return converter_with_server.parse(
148
+ inputs=inputs,
149
+ out_folder=out_folder,
150
+ mode=mode,
151
+ conversion_mode=conversion_mode,
152
+ dpi=dpi,
153
+ debug=debug,
37
154
  )
38
155
 
39
- logger.info(f"✓ VLLM server ready at {base_url}")
40
- if container is not None:
41
- logger.info(f"✓ Container ID: {container.id}")
42
- logger.info(f"✓ Container name: {container.name}")
43
-
44
- def convert(
45
- self,
46
- inputs: str | list[str],
47
- out_folder: str = ".",
48
- model: str | None = None,
49
- uri: str | None = None,
50
- gpus: str | None = None,
51
- mode: Literal["document", "md", "md_page"] = "document",
52
- conversion_mode: Literal[
53
- "ocr",
54
- "ocr_layout",
55
- "table",
56
- "image_description",
57
- "formula",
58
- "chart",
59
- ] = "ocr",
60
- with_vllm_server: bool = False,
61
- concurrency: int = 10,
62
- dpi: int | None = None,
63
- debug: bool = False,
64
- _return_documents: bool = False,
65
- ):
66
- """Parse PDF documents and save results.
67
-
68
- Args:
69
- inputs: List of folders to process
70
- out_folder: Output folder for parsed documents
71
- pipe: Converter type ("vllm", "openai", or "lightonocr", default: "vllm")
72
- model: Model name. If not specified, the model will be inferred from the URI.
73
- uri: URI of the server, if not specified and the pipe is vllm, a local server will be deployed
74
- gpus: Comma-separated GPU device IDs (e.g., "0" or "0,1,2"). If not specified, all GPUs will be used.
75
- mode: Output mode - "document" (save as JSON zip), "md" (save as markdown file), "md_page" (save as folder of markdown pages)
76
- conversion_mode: Conversion mode - "ocr" (plain), "ocr_layout" (OCR with layout), "table" (table-centric), "image_description" (describe the image), "formula" (formula extraction), "chart" (chart recognition)
77
- with_vllm_server: If True, a local VLLM server will be deployed if the model is not found in the registry. Note that if the model is in the registry and the uri is None, the server will be anyway deployed.
78
- dpi: DPI to use for the conversion. If not specified, the default DPI will be used.
79
- debug: If True, run in debug mode (single-threaded, no concurrency)
80
- """
81
- from vlmparse.converter_with_server import ConverterWithServer
82
-
83
- with ConverterWithServer(
84
- model=model,
85
- uri=uri,
86
- gpus=gpus,
87
- with_vllm_server=with_vllm_server,
88
- concurrency=concurrency,
89
- return_documents=_return_documents,
90
- ) as converter_with_server:
91
- return converter_with_server.parse(
92
- inputs=inputs,
93
- out_folder=out_folder,
94
- mode=mode,
95
- conversion_mode=conversion_mode,
96
- dpi=dpi,
97
- debug=debug,
98
- )
99
156
 
100
- def list(self):
101
- """List all containers whose name begins with vlmparse."""
102
- import docker
157
+ @app.command("list")
158
+ def containers():
159
+ """List all containers whose name begins with vlmparse."""
160
+ import docker
103
161
 
104
- try:
105
- client = docker.from_env()
106
- containers = client.containers.list()
162
+ from vlmparse.servers.utils import _get_container_labels
107
163
 
108
- if not containers:
109
- logger.info("No running containers found")
110
- return
164
+ try:
165
+ client = docker.from_env()
166
+ all_containers = client.containers.list(all=True)
111
167
 
112
- # Filter for containers whose name begins with "vlmparse"
113
- vlmparse_containers = [
114
- container
115
- for container in containers
116
- if container.name.startswith("vlmparse")
117
- ]
168
+ if not all_containers:
169
+ logger.info("No containers found")
170
+ return
118
171
 
119
- if not vlmparse_containers:
120
- logger.info("No vlmparse containers found")
121
- return
172
+ # Group containers by compose project or as standalone
173
+ projects = {} # project_name -> list of containers
122
174
 
123
- # Prepare table data
124
- table_data = []
125
- for container in vlmparse_containers:
126
- # Extract port mappings
127
- ports = []
175
+ for container in all_containers:
176
+ labels = _get_container_labels(container)
177
+ project = labels.get("com.docker.compose.project") or labels.get(
178
+ "vlmparse_compose_project"
179
+ )
180
+
181
+ # Include if name starts with vlmparse OR if it's in a vlmparse compose project
182
+ if container.name.startswith("vlmparse"):
183
+ if project:
184
+ projects.setdefault(project, []).append(container)
185
+ else:
186
+ projects[container.name] = [container]
187
+ elif project and project.startswith("vlmparse"):
188
+ projects.setdefault(project, []).append(container)
189
+
190
+ if not projects:
191
+ logger.info("No vlmparse containers found")
192
+ return
193
+
194
+ # Prepare table data - one row per project/standalone container
195
+ table_data = []
196
+ for project_name, project_containers in projects.items():
197
+ # Find the main container with vlmparse labels
198
+ main_container = project_containers[0]
199
+ for c in project_containers:
200
+ labels = _get_container_labels(c)
201
+ if labels.get("vlmparse_uri"):
202
+ main_container = c
203
+ break
204
+
205
+ labels = _get_container_labels(main_container)
206
+
207
+ # Extract port mappings from all containers in the project
208
+ ports = []
209
+ for container in project_containers:
128
210
  if container.ports:
129
211
  for _, host_bindings in container.ports.items():
130
212
  if host_bindings:
131
213
  for binding in host_bindings:
132
214
  ports.append(f"{binding['HostPort']}")
133
215
 
134
- port_str = ", ".join(set(ports)) if ports else "N/A"
135
- uri = container.labels.get("vlmparse_uri", "N/A")
136
- gpu = container.labels.get("vlmparse_gpus", "N/A")
137
-
138
- table_data.append(
139
- [
140
- container.name,
141
- container.status,
142
- port_str,
143
- gpu,
144
- uri,
145
- ]
146
- )
216
+ port_str = ", ".join(sorted(set(ports))) if ports else "N/A"
217
+ uri = labels.get("vlmparse_uri", "N/A")
218
+ gpu = labels.get("vlmparse_gpus", "N/A")
147
219
 
148
- # Display as table
149
- from tabulate import tabulate
220
+ # Get all statuses
221
+ statuses = list(set(c.status for c in project_containers))
222
+ status_str = (
223
+ statuses[0] if len(statuses) == 1 else f"mixed ({', '.join(statuses)})"
224
+ )
225
+
226
+ # Name: show project name if compose, otherwise container name
227
+ is_compose = labels.get("com.docker.compose.project") or labels.get(
228
+ "vlmparse_compose_project"
229
+ )
230
+ name = project_name if is_compose else main_container.name
150
231
 
151
- headers = ["Name", "Status", "Port(s)", "GPU", "URI"]
152
- table = tabulate(table_data, headers=headers, tablefmt="grid")
232
+ table_data.append([name, status_str, port_str, gpu, uri])
153
233
 
154
- logger.info(f"\nFound {len(vlmparse_containers)} vlmparse container(s):\n")
155
- print(table)
234
+ # Display as table
235
+ from tabulate import tabulate
156
236
 
157
- except docker.errors.DockerException as e:
158
- logger.error(f"Failed to connect to Docker: {e}")
159
- logger.error(
160
- "Make sure Docker is running and you have the necessary permissions"
161
- )
237
+ headers = ["Name", "Status", "Port(s)", "GPU", "URI"]
238
+ table = tabulate(table_data, headers=headers, tablefmt="grid")
162
239
 
163
- def stop(self, container: str | None = None):
164
- """Stop a Docker container by its ID or name.
240
+ total = sum(len(containers) for containers in projects.values())
241
+ logger.info(
242
+ f"\nFound {len(projects)} vlmparse deployment(s) ({total} container(s)):\n"
243
+ )
244
+ print(table)
165
245
 
166
- Args:
167
- container: Container ID or name to stop. If not specified, automatically stops the container if only one vlmparse container is running.
168
- """
169
- import docker
246
+ except docker.errors.DockerException as e:
247
+ logger.error(f"Failed to connect to Docker: {e}")
248
+ logger.error(
249
+ "Make sure Docker is running and you have the necessary permissions"
250
+ )
170
251
 
171
- try:
172
- client = docker.from_env()
173
-
174
- # If no container specified, try to auto-select
175
- if container is None:
176
- containers = client.containers.list()
177
- vlmparse_containers = [
178
- c for c in containers if c.name.startswith("vlmparse")
179
- ]
180
-
181
- if len(vlmparse_containers) == 0:
182
- logger.error("No vlmparse containers found")
183
- return
184
- elif len(vlmparse_containers) > 1:
185
- logger.error(
186
- f"Multiple vlmparse containers found ({len(vlmparse_containers)}). "
187
- "Please specify a container ID or name:"
188
- )
189
- for c in vlmparse_containers:
190
- logger.info(f" - {c.name} ({c.short_id})")
191
- return
192
- else:
193
- target_container = vlmparse_containers[0]
252
+
253
+ @app.command("stop")
254
+ def stop(
255
+ container: str | None = typer.Argument(None, help="Container ID or name to stop"),
256
+ ):
257
+ """Stop a Docker container by its ID or name.
258
+
259
+ If the selected container belongs to a Docker Compose project, the whole
260
+ compose stack is stopped and removed.
261
+
262
+ Args:
263
+ container: Container ID or name to stop. If not specified, automatically stops the container if only one vlmparse container is running.
264
+ """
265
+ import docker
266
+
267
+ from vlmparse.servers.utils import _stop_compose_stack_for_container
268
+
269
+ try:
270
+ client = docker.from_env()
271
+
272
+ # If no container specified, try to auto-select
273
+ if container is None:
274
+ from vlmparse.servers.utils import _get_container_labels
275
+
276
+ all_containers = client.containers.list()
277
+
278
+ # Group containers by compose project or as standalone
279
+ projects = {} # project_name -> list of containers
280
+ for c in all_containers:
281
+ labels = _get_container_labels(c)
282
+ project = labels.get("com.docker.compose.project") or labels.get(
283
+ "vlmparse_compose_project"
284
+ )
285
+
286
+ # Include if name starts with vlmparse OR if it's in a vlmparse compose project
287
+ if c.name.startswith("vlmparse"):
288
+ if project:
289
+ projects.setdefault(project, []).append(c)
290
+ else:
291
+ projects[c.name] = [c]
292
+ elif project and project.startswith("vlmparse"):
293
+ projects.setdefault(project, []).append(c)
294
+
295
+ if len(projects) == 0:
296
+ logger.error("No vlmparse containers found")
297
+ return
298
+ elif len(projects) > 1:
299
+ logger.error(
300
+ f"Multiple vlmparse deployments found ({len(projects)}). "
301
+ "Please specify a container ID or name:"
302
+ )
303
+ for project_name, containers in projects.items():
304
+ if len(containers) > 1:
305
+ logger.info(
306
+ f" - {project_name} ({len(containers)} containers)"
307
+ )
308
+ else:
309
+ logger.info(
310
+ f" - {containers[0].name} ({containers[0].short_id})"
311
+ )
312
+ return
194
313
  else:
195
- # Try to get the specified container
196
- try:
197
- target_container = client.containers.get(container)
198
- except docker.errors.NotFound:
199
- logger.error(f"Container not found: {container}")
200
- return
201
-
202
- # Stop the container
203
- logger.info(
204
- f"Stopping container: {target_container.name} ({target_container.short_id})"
205
- )
314
+ # Only one project/deployment, pick any container from it
315
+ target_container = list(projects.values())[0][0]
316
+ else:
317
+ # Try to get the specified container
318
+ try:
319
+ target_container = client.containers.get(container)
320
+ except docker.errors.NotFound:
321
+ logger.error(f"Container not found: {container}")
322
+ return
323
+
324
+ # If the container is part of a docker-compose stack, bring the whole stack down.
325
+ if _stop_compose_stack_for_container(target_container):
326
+ return
327
+
328
+ # Stop + remove the container
329
+ logger.info(
330
+ f"Stopping container: {target_container.name} ({target_container.short_id})"
331
+ )
332
+ try:
206
333
  target_container.stop()
207
- logger.info("✓ Container stopped successfully")
334
+ except Exception:
335
+ pass
336
+ try:
337
+ target_container.remove(force=True)
338
+ except Exception:
339
+ pass
340
+ logger.info("✓ Container stopped and removed successfully")
341
+
342
+ except docker.errors.DockerException as e:
343
+ logger.error(f"Failed to connect to Docker: {e}")
344
+ logger.error(
345
+ "Make sure Docker is running and you have the necessary permissions"
346
+ )
208
347
 
209
- except docker.errors.DockerException as e:
210
- logger.error(f"Failed to connect to Docker: {e}")
211
- logger.error(
212
- "Make sure Docker is running and you have the necessary permissions"
213
- )
214
348
 
215
- def log(self, container: str | None = None, follow: bool = True, tail: int = 500):
216
- """Show logs from a Docker container.
349
+ @app.command("log")
350
+ def log(
351
+ container: str | None = typer.Argument(
352
+ None, help="Container ID or name. If not specified, auto-selects."
353
+ ),
354
+ follow: bool = typer.Option(True, help="Follow log output"),
355
+ tail: int = typer.Option(500, help="Number of lines to show from the end"),
356
+ ):
357
+ """Show logs from a Docker container.
358
+
359
+ Args:
360
+ container: Container ID or name. If not specified, automatically selects the container if only one vlmparse container is running.
361
+ follow: If True, follow log output (stream logs in real-time)
362
+ tail: Number of lines to show from the end of the logs
363
+ """
364
+ import docker
365
+
366
+ try:
367
+ client = docker.from_env()
368
+
369
+ # If no container specified, try to auto-select
370
+ if container is None:
371
+ from vlmparse.servers.utils import _get_container_labels
372
+
373
+ all_containers = client.containers.list()
374
+ vlmparse_containers = []
375
+
376
+ for c in all_containers:
377
+ labels = _get_container_labels(c)
378
+ project = labels.get("com.docker.compose.project") or labels.get(
379
+ "vlmparse_compose_project"
380
+ )
217
381
 
218
- Args:
219
- container: Container ID or name. If not specified, automatically selects the container if only one vlmparse container is running.
220
- follow: If True, follow log output (stream logs in real-time)
221
- tail: Number of lines to show from the end of the logs
222
- """
223
- import docker
382
+ # Include if name starts with vlmparse OR if it's in a vlmparse compose project
383
+ if c.name.startswith("vlmparse") or (
384
+ project and project.startswith("vlmparse")
385
+ ):
386
+ vlmparse_containers.append(c)
224
387
 
225
- try:
226
- client = docker.from_env()
227
-
228
- # If no container specified, try to auto-select
229
- if container is None:
230
- containers = client.containers.list()
231
- vlmparse_containers = [
232
- c for c in containers if c.name.startswith("vlmparse")
233
- ]
234
-
235
- if len(vlmparse_containers) == 0:
236
- logger.error("No vlmparse containers found")
237
- return
238
- elif len(vlmparse_containers) > 1:
239
- logger.error(
240
- f"Multiple vlmparse containers found ({len(vlmparse_containers)}). "
241
- "Please specify a container ID or name:"
242
- )
243
- for c in vlmparse_containers:
244
- logger.info(f" - {c.name} ({c.short_id})")
245
- return
246
- else:
247
- target_container = vlmparse_containers[0]
248
- logger.info(
249
- f"Showing logs for: {target_container.name} ({target_container.short_id})"
250
- )
251
- else:
252
- # Try to get the specified container
253
- try:
254
- target_container = client.containers.get(container)
255
- except docker.errors.NotFound:
256
- logger.error(f"Container not found: {container}")
257
- return
258
-
259
- # Get and display logs
260
- if follow:
261
- logger.info("Following logs (press Ctrl+C to stop)...")
262
- try:
263
- for log_line in target_container.logs(
264
- stream=True, follow=True, tail=tail
265
- ):
266
- print(log_line.decode("utf-8", errors="replace"), end="")
267
- except KeyboardInterrupt:
268
- logger.info("\nStopped following logs")
388
+ if len(vlmparse_containers) == 0:
389
+ logger.error("No vlmparse containers found")
390
+ return
391
+ elif len(vlmparse_containers) > 1:
392
+ logger.error(
393
+ f"Multiple vlmparse containers found ({len(vlmparse_containers)}). "
394
+ "Please specify a container ID or name:"
395
+ )
396
+ for c in vlmparse_containers:
397
+ logger.info(f" - {c.name} ({c.short_id})")
398
+ return
269
399
  else:
270
- logs = target_container.logs().decode("utf-8", errors="replace")
271
- print(logs)
400
+ target_container = vlmparse_containers[0]
401
+ logger.info(
402
+ f"Showing logs for: {target_container.name} ({target_container.short_id})"
403
+ )
404
+ else:
405
+ # Try to get the specified container
406
+ try:
407
+ target_container = client.containers.get(container)
408
+ except docker.errors.NotFound:
409
+ logger.error(f"Container not found: {container}")
410
+ return
272
411
 
273
- except docker.errors.DockerException as e:
274
- logger.error(f"Failed to connect to Docker: {e}")
275
- logger.error(
276
- "Make sure Docker is running and you have the necessary permissions"
277
- )
412
+ # Get and display logs
413
+ if follow:
414
+ logger.info("Following logs (press Ctrl+C to stop)...")
415
+ try:
416
+ for log_line in target_container.logs(
417
+ stream=True, follow=True, tail=tail
418
+ ):
419
+ print(log_line.decode("utf-8", errors="replace"), end="")
420
+ except KeyboardInterrupt:
421
+ logger.info("\nStopped following logs")
422
+ else:
423
+ logs = target_container.logs().decode("utf-8", errors="replace")
424
+ print(logs)
278
425
 
279
- def list_register(self):
280
- """List all model keys registered in client and server registries."""
281
- from vlmparse.registries import (
282
- converter_config_registry,
283
- docker_config_registry,
426
+ except docker.errors.DockerException as e:
427
+ logger.error(f"Failed to connect to Docker: {e}")
428
+ logger.error(
429
+ "Make sure Docker is running and you have the necessary permissions"
284
430
  )
285
431
 
286
- client_models = sorted(converter_config_registry.list_models())
287
- server_models = sorted(docker_config_registry.list_models())
288
432
 
289
- print("\nClient Models Registry:")
290
- for model in client_models:
291
- print(f" - {model}")
433
+ @app.command("list-register")
434
+ def list_register():
435
+ """List all model keys registered in client and server registries."""
436
+ from vlmparse.registries import converter_config_registry, docker_config_registry
292
437
 
293
- print("\nServer Models Registry:")
294
- for model in server_models:
295
- print(f" - {model}")
438
+ client_models = sorted(converter_config_registry.list_models())
439
+ server_models = sorted(docker_config_registry.list_models())
296
440
 
297
- def view(self, folder):
298
- import subprocess
299
- import sys
441
+ print("\nClient Models Registry:")
442
+ for model in client_models:
443
+ print(f" - {model}")
300
444
 
301
- from streamlit import runtime
445
+ print("\nServer Models Registry:")
446
+ for model in server_models:
447
+ print(f" - {model}")
302
448
 
303
- from vlmparse.st_viewer.st_viewer import __file__ as st_viewer_file
304
- from vlmparse.st_viewer.st_viewer import run_streamlit
305
449
 
306
- if runtime.exists():
307
- run_streamlit(folder)
308
- else:
309
- try:
310
- subprocess.run(
311
- [
312
- sys.executable,
313
- "-m",
314
- "streamlit",
315
- "run",
316
- st_viewer_file,
317
- "--",
318
- folder,
319
- ],
320
- check=True,
321
- )
322
- except KeyboardInterrupt:
323
- print("\nStreamlit app terminated by user.")
324
- except subprocess.CalledProcessError as e:
325
- print(f"Error while running Streamlit: {e}")
450
+ @app.command("view")
451
+ def view(folder: str = typer.Argument(..., help="Folder to visualize with Streamlit")):
452
+ import subprocess
453
+ import sys
326
454
 
455
+ from streamlit import runtime
327
456
 
328
- def main():
329
- import fire
457
+ from vlmparse.st_viewer.st_viewer import __file__ as st_viewer_file
458
+ from vlmparse.st_viewer.st_viewer import run_streamlit
459
+
460
+ if runtime.exists():
461
+ run_streamlit(folder)
462
+ else:
463
+ try:
464
+ subprocess.run(
465
+ [
466
+ sys.executable,
467
+ "-m",
468
+ "streamlit",
469
+ "run",
470
+ st_viewer_file,
471
+ "--",
472
+ folder,
473
+ ],
474
+ check=True,
475
+ )
476
+ except KeyboardInterrupt:
477
+ print("\nStreamlit app terminated by user.")
478
+ except subprocess.CalledProcessError as e:
479
+ print(f"Error while running Streamlit: {e}")
330
480
 
331
- fire.Fire(DParseCLI)
481
+
482
+ def main():
483
+ app()
332
484
 
333
485
 
334
486
  if __name__ == "__main__":