mdify-cli 2.5.0__tar.gz → 2.6.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdify-cli
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Convert PDFs and document images into structured Markdown for LLM workflows
5
5
  Author: tiroq
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """mdify - Convert documents to Markdown via Docling container."""
2
2
 
3
- __version__ = "2.5.0"
3
+ __version__ = "2.6.0"
@@ -41,6 +41,39 @@ class DoclingContainer:
41
41
  """Return base URL for API requests."""
42
42
  return f"http://localhost:{self.port}"
43
43
 
44
+ def _cleanup_stale_containers(self) -> None:
45
+ """Stop any existing mdify-serve containers.
46
+
47
+ This handles the case where a previous run left a container running
48
+ (e.g., due to crash, interrupt, or timeout).
49
+ """
50
+ # Find running containers matching mdify-serve-* pattern
51
+ result = subprocess.run(
52
+ [
53
+ self.runtime,
54
+ "ps",
55
+ "--filter",
56
+ "name=mdify-serve-",
57
+ "--format",
58
+ "{{.Names}}",
59
+ ],
60
+ capture_output=True,
61
+ text=True,
62
+ check=False,
63
+ )
64
+
65
+ if result.returncode != 0 or not result.stdout.strip():
66
+ return
67
+
68
+ # Stop each stale container
69
+ for container_name in result.stdout.strip().split("\n"):
70
+ if container_name:
71
+ subprocess.run(
72
+ [self.runtime, "stop", container_name],
73
+ capture_output=True,
74
+ check=False,
75
+ )
76
+
44
77
  def start(self, timeout: int = 120) -> None:
45
78
  """Start container and wait for health check.
46
79
 
@@ -51,6 +84,8 @@ class DoclingContainer:
51
84
  subprocess.CalledProcessError: If container fails to start
52
85
  TimeoutError: If health check doesn't pass within timeout
53
86
  """
87
+ self._cleanup_stale_containers()
88
+
54
89
  # Start container in detached mode
55
90
  cmd = [
56
91
  self.runtime,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdify-cli
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Convert PDFs and document images into structured Markdown for LLM workflows
5
5
  Author: tiroq
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mdify-cli"
3
- version = "2.5.0"
3
+ version = "2.6.0"
4
4
  description = "Convert PDFs and document images into structured Markdown for LLM workflows"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -311,7 +311,110 @@ class TestDoclingContainerIntegration:
311
311
  container1 = DoclingContainer("docker", "image1", port=5001)
312
312
  container2 = DoclingContainer("docker", "image2", port=5002)
313
313
 
314
- # Each should be independent
315
314
  assert container1.port == 5001
316
315
  assert container2.port == 5002
317
316
  assert container1.container_name != container2.container_name
317
+
318
+
319
+ class TestDoclingContainerCleanup:
320
+ """Test cleanup of stale containers."""
321
+
322
+ def test_cleanup_no_stale_containers(self):
323
+ """Test cleanup runs when no stale containers exist."""
324
+ with patch("mdify.container.subprocess.run") as mock_run, patch(
325
+ "mdify.container.check_health"
326
+ ) as mock_health:
327
+ ps_result = Mock()
328
+ ps_result.returncode = 0
329
+ ps_result.stdout = ""
330
+
331
+ run_result = Mock()
332
+ run_result.stdout = "container_id\n"
333
+
334
+ mock_run.side_effect = [ps_result, run_result]
335
+ mock_health.return_value = True
336
+
337
+ container = DoclingContainer("docker", "test-image")
338
+ container.start(timeout=5)
339
+
340
+ ps_call = mock_run.call_args_list[0][0][0]
341
+ assert "ps" in ps_call
342
+ assert "--filter" in ps_call
343
+ assert "name=mdify-serve-" in ps_call
344
+
345
+ def test_cleanup_stops_stale_containers(self):
346
+ """Test cleanup finds and stops stale containers."""
347
+ with patch("mdify.container.subprocess.run") as mock_run, patch(
348
+ "mdify.container.check_health"
349
+ ) as mock_health:
350
+ ps_result = Mock()
351
+ ps_result.returncode = 0
352
+ ps_result.stdout = "mdify-serve-abc123\nmdify-serve-def456\n"
353
+
354
+ stop_result1 = Mock()
355
+ stop_result2 = Mock()
356
+
357
+ run_result = Mock()
358
+ run_result.stdout = "container_id\n"
359
+
360
+ mock_run.side_effect = [ps_result, stop_result1, stop_result2, run_result]
361
+ mock_health.return_value = True
362
+
363
+ container = DoclingContainer("docker", "test-image")
364
+ container.start(timeout=5)
365
+
366
+ ps_call = mock_run.call_args_list[0][0][0]
367
+ assert "ps" in ps_call
368
+
369
+ stop_calls = [
370
+ call for call in mock_run.call_args_list if "stop" in str(call)
371
+ ]
372
+ assert len(stop_calls) == 2
373
+ assert "mdify-serve-abc123" in str(stop_calls[0])
374
+ assert "mdify-serve-def456" in str(stop_calls[1])
375
+
376
+ def test_cleanup_handles_subprocess_error(self):
377
+ """Test cleanup handles subprocess errors gracefully."""
378
+ with patch("mdify.container.subprocess.run") as mock_run, patch(
379
+ "mdify.container.check_health"
380
+ ) as mock_health:
381
+ ps_result = Mock()
382
+ ps_result.returncode = 1
383
+ ps_result.stdout = ""
384
+
385
+ run_result = Mock()
386
+ run_result.stdout = "container_id\n"
387
+
388
+ mock_run.side_effect = [ps_result, run_result]
389
+ mock_health.return_value = True
390
+
391
+ container = DoclingContainer("docker", "test-image")
392
+ container.start(timeout=5)
393
+
394
+ assert container.container_id == "container_id"
395
+
396
+ def test_start_calls_cleanup(self):
397
+ """Test that start() calls _cleanup_stale_containers()."""
398
+ with patch("mdify.container.subprocess.run") as mock_run, patch(
399
+ "mdify.container.check_health"
400
+ ) as mock_health:
401
+ ps_result = Mock()
402
+ ps_result.returncode = 0
403
+ ps_result.stdout = ""
404
+
405
+ run_result = Mock()
406
+ run_result.stdout = "new_container_id\n"
407
+
408
+ mock_run.side_effect = [ps_result, run_result]
409
+ mock_health.return_value = True
410
+
411
+ container = DoclingContainer("docker", "test-image")
412
+ container.start(timeout=5)
413
+
414
+ all_calls = mock_run.call_args_list
415
+ ps_called = any("ps" in str(call) for call in all_calls[:1])
416
+ run_called = any("run" in str(call) for call in all_calls)
417
+
418
+ assert ps_called
419
+ assert run_called
420
+ assert "ps" in all_calls[0][0][0]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes