mdify-cli 2.11.5__tar.gz → 2.11.7__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.11.5
3
+ Version: 2.11.7
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.11.5"
3
+ __version__ = "2.11.7"
@@ -752,6 +752,13 @@ Examples:
752
752
  help="Conversion timeout in seconds (default: 1200, can be set via MDIFY_TIMEOUT env var)",
753
753
  )
754
754
 
755
+ parser.add_argument(
756
+ "--memory",
757
+ type=str,
758
+ default=None,
759
+ help="Container memory limit (e.g., 2g, 512m, 4096m). Default: no limit",
760
+ )
761
+
755
762
  # Utility options
756
763
  parser.add_argument(
757
764
  "--check-update",
@@ -961,6 +968,7 @@ def main() -> int:
961
968
  args.port,
962
969
  timeout=timeout,
963
970
  keep_container=DEBUG,
971
+ memory=args.memory,
964
972
  ) as container:
965
973
  # Convert files
966
974
  conversion_start = time.time()
@@ -1020,6 +1028,60 @@ def main() -> int:
1020
1028
  f"{progress} {input_file.name} ✗ ({format_duration(elapsed)})"
1021
1029
  )
1022
1030
  print(f" Error: {error_msg}", file=sys.stderr)
1031
+
1032
+ # Check if it's a connection error and retrieve logs
1033
+ is_connection_error = "Connection refused" in error_msg or "Connection aborted" in error_msg or "RemoteDisconnected" in error_msg
1034
+ if is_connection_error:
1035
+ container_alive = container.is_ready()
1036
+ if container_alive:
1037
+ print(
1038
+ " Connection lost (server may have crashed and restarted)",
1039
+ file=sys.stderr,
1040
+ )
1041
+ else:
1042
+ print(
1043
+ " Container crashed while processing file",
1044
+ file=sys.stderr,
1045
+ )
1046
+ print(
1047
+ " File may be too complex, large, or malformed",
1048
+ file=sys.stderr,
1049
+ )
1050
+
1051
+ # Always show logs for connection errors
1052
+ print(" Retrieving container logs...", file=sys.stderr)
1053
+ logs, log_error = container.get_logs(tail=50)
1054
+ if logs:
1055
+ print(" Container logs (last 50 lines):", file=sys.stderr)
1056
+ for line in logs.strip().split("\n"):
1057
+ if line.strip():
1058
+ print(f" {line}", file=sys.stderr)
1059
+ elif log_error:
1060
+ print(f" Error retrieving logs: {log_error}", file=sys.stderr)
1061
+ else:
1062
+ print(" No logs available (container may have been removed)", file=sys.stderr)
1063
+
1064
+ # Restart container if it crashed
1065
+ if not container_alive:
1066
+ print(" Container crashed - attempting to restart...", file=sys.stderr)
1067
+ try:
1068
+ # Stop and remove the dead container
1069
+ container.stop()
1070
+ container.remove()
1071
+ # Generate new container name to avoid conflicts
1072
+ import uuid
1073
+ container.container_name = f"mdify-serve-{uuid.uuid4().hex[:8]}"
1074
+ # Start a new one
1075
+ container.start(timeout=120)
1076
+ print(" Container restarted successfully", file=sys.stderr)
1077
+ print(" Continuing with next file...", file=sys.stderr)
1078
+ except Exception as restart_error:
1079
+ print(f" Failed to restart container: {restart_error}", file=sys.stderr)
1080
+ if DEBUG:
1081
+ import traceback
1082
+ traceback.print_exc()
1083
+ print(" Stopping remaining conversions", file=sys.stderr)
1084
+ break
1023
1085
  except Exception as e:
1024
1086
  elapsed = time.time() - start_time
1025
1087
  failed_count += 1
@@ -1031,6 +1093,10 @@ def main() -> int:
1031
1093
  error_msg = str(e)
1032
1094
  is_connection_error = "Connection refused" in error_msg or "Connection aborted" in error_msg or "RemoteDisconnected" in error_msg
1033
1095
 
1096
+ if DEBUG:
1097
+ print(f" DEBUG: Exception caught: {type(e).__name__}", file=sys.stderr)
1098
+ print(f" DEBUG: is_connection_error={is_connection_error}", file=sys.stderr)
1099
+
1034
1100
  if is_connection_error:
1035
1101
  container_alive = container.is_ready()
1036
1102
  if not args.quiet:
@@ -1054,13 +1120,21 @@ def main() -> int:
1054
1120
 
1055
1121
  # Always show logs for connection errors to surface root cause
1056
1122
  print(" Retrieving container logs...", file=sys.stderr)
1057
- logs = container.get_logs(tail=30)
1123
+ logs, log_error = container.get_logs(tail=50)
1058
1124
  if logs:
1059
- print(" Container logs (last 30 lines):", file=sys.stderr)
1125
+ print(" Container logs (last 50 lines):", file=sys.stderr)
1060
1126
  for line in logs.strip().split("\n"):
1061
- print(f" {line}", file=sys.stderr)
1127
+ if line.strip(): # Skip empty lines
1128
+ print(f" {line}", file=sys.stderr)
1129
+ elif log_error:
1130
+ print(f" Error retrieving logs: {log_error}", file=sys.stderr)
1131
+ if not DEBUG:
1132
+ print(
1133
+ " Tip: re-run with MDIFY_DEBUG=1 to preserve container for inspection",
1134
+ file=sys.stderr,
1135
+ )
1062
1136
  else:
1063
- print(" No logs available", file=sys.stderr)
1137
+ print(" No logs available (container may have been removed)", file=sys.stderr)
1064
1138
  if not DEBUG:
1065
1139
  print(
1066
1140
  " Tip: re-run with MDIFY_DEBUG=1 to preserve container logs",
@@ -1070,9 +1144,27 @@ def main() -> int:
1070
1144
  if not container_alive:
1071
1145
  print(" Stopping remaining conversions", file=sys.stderr)
1072
1146
 
1073
- # Stop processing if container is dead
1147
+ # Restart container if it crashed
1074
1148
  if not container_alive:
1075
- break
1149
+ print(" Container crashed - attempting to restart...", file=sys.stderr)
1150
+ try:
1151
+ # Stop and remove the dead container
1152
+ container.stop()
1153
+ container.remove()
1154
+ # Generate new container name to avoid conflicts
1155
+ import uuid
1156
+ container.container_name = f"mdify-serve-{uuid.uuid4().hex[:8]}"
1157
+ # Start a new one
1158
+ container.start(timeout=120)
1159
+ print(" Container restarted successfully", file=sys.stderr)
1160
+ print(" Continuing with next file...", file=sys.stderr)
1161
+ except Exception as restart_error:
1162
+ print(f" Failed to restart container: {restart_error}", file=sys.stderr)
1163
+ if DEBUG:
1164
+ import traceback
1165
+ traceback.print_exc()
1166
+ print(" Stopping remaining conversions", file=sys.stderr)
1167
+ break
1076
1168
  else:
1077
1169
  # Non-connection error
1078
1170
  if not args.quiet:
@@ -27,6 +27,7 @@ class DoclingContainer:
27
27
  port: int = 5001,
28
28
  timeout: int = 1200,
29
29
  keep_container: bool = False,
30
+ memory: Optional[str] = None,
30
31
  ):
31
32
  """Initialize container manager.
32
33
 
@@ -36,12 +37,14 @@ class DoclingContainer:
36
37
  port: Host port to bind (default: 5001)
37
38
  timeout: Conversion timeout in seconds (default: 1200)
38
39
  keep_container: If True, do not auto-remove container (preserve logs)
40
+ memory: Memory limit (e.g., "2g", "512m"). None for no limit.
39
41
  """
40
42
  self.runtime = runtime
41
43
  self.image = image
42
44
  self.port = port
43
45
  self.timeout = timeout
44
46
  self.keep_container = keep_container
47
+ self.memory = memory
45
48
  self.container_name = f"mdify-serve-{uuid.uuid4().hex[:8]}"
46
49
  self.container_id: Optional[str] = None
47
50
 
@@ -110,6 +113,11 @@ class DoclingContainer:
110
113
  ]
111
114
  if not self.keep_container:
112
115
  cmd.insert(3, "--rm") # Auto-remove on stop
116
+
117
+ # Add memory limit if specified
118
+ if self.memory:
119
+ cmd.insert(3, self.memory)
120
+ cmd.insert(3, "-m")
113
121
 
114
122
  try:
115
123
  result = subprocess.run(cmd, capture_output=True, text=True, check=True)
@@ -135,28 +143,50 @@ class DoclingContainer:
135
143
  check=False,
136
144
  )
137
145
 
138
- def get_logs(self, tail: int = 50) -> str:
146
+ def remove(self) -> None:
147
+ """Remove container. Safe to call multiple times."""
148
+ if self.container_name:
149
+ subprocess.run(
150
+ [self.runtime, "rm", "-f", self.container_name],
151
+ capture_output=True,
152
+ check=False,
153
+ )
154
+
155
+ def get_logs(self, tail: int = 50) -> tuple[str, str]:
139
156
  """Get container logs for debugging.
140
157
 
141
158
  Args:
142
159
  tail: Number of lines to retrieve from end of logs
143
160
 
144
161
  Returns:
145
- Container logs as string
162
+ Tuple of (stdout, stderr) from container logs
146
163
  """
147
164
  if not self.container_name:
148
- return ""
165
+ return ("", "No container name set")
149
166
 
150
167
  try:
168
+ import os
169
+ runtime_name = os.path.basename(self.runtime)
170
+
171
+ # Apple Container uses -n instead of --tail
172
+ if runtime_name == "container":
173
+ cmd = [self.runtime, "logs", "-n", str(tail), self.container_name]
174
+ else:
175
+ cmd = [self.runtime, "logs", "--tail", str(tail), self.container_name]
176
+
151
177
  result = subprocess.run(
152
- [self.runtime, "logs", "--tail", str(tail), self.container_name],
178
+ cmd,
153
179
  capture_output=True,
154
180
  text=True,
155
181
  check=False,
156
182
  )
157
- return result.stdout if result.returncode == 0 else ""
158
- except Exception:
159
- return ""
183
+ if result.returncode != 0:
184
+ return ("", f"Failed to get logs (exit {result.returncode}): {result.stderr}")
185
+ # Container logs come from both stdout and stderr
186
+ combined = result.stdout + result.stderr
187
+ return (combined, "")
188
+ except Exception as e:
189
+ return ("", f"Exception getting logs: {e}")
160
190
 
161
191
  def is_running(self) -> bool:
162
192
  """Check if container process is still running.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdify-cli
3
- Version: 2.11.5
3
+ Version: 2.11.7
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.11.5"
3
+ version = "2.11.7"
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"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes