mdify-cli 2.11.5__py3-none-any.whl → 2.11.7__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.
- mdify/__init__.py +1 -1
- mdify/cli.py +98 -6
- mdify/container.py +37 -7
- {mdify_cli-2.11.5.dist-info → mdify_cli-2.11.7.dist-info}/METADATA +1 -1
- mdify_cli-2.11.7.dist-info/RECORD +12 -0
- mdify_cli-2.11.5.dist-info/RECORD +0 -12
- {mdify_cli-2.11.5.dist-info → mdify_cli-2.11.7.dist-info}/WHEEL +0 -0
- {mdify_cli-2.11.5.dist-info → mdify_cli-2.11.7.dist-info}/entry_points.txt +0 -0
- {mdify_cli-2.11.5.dist-info → mdify_cli-2.11.7.dist-info}/licenses/LICENSE +0 -0
- {mdify_cli-2.11.5.dist-info → mdify_cli-2.11.7.dist-info}/top_level.txt +0 -0
mdify/__init__.py
CHANGED
mdify/cli.py
CHANGED
|
@@ -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=
|
|
1123
|
+
logs, log_error = container.get_logs(tail=50)
|
|
1058
1124
|
if logs:
|
|
1059
|
-
print(" Container logs (last
|
|
1125
|
+
print(" Container logs (last 50 lines):", file=sys.stderr)
|
|
1060
1126
|
for line in logs.strip().split("\n"):
|
|
1061
|
-
|
|
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
|
-
#
|
|
1147
|
+
# Restart container if it crashed
|
|
1074
1148
|
if not container_alive:
|
|
1075
|
-
|
|
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:
|
mdify/container.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
cmd,
|
|
153
179
|
capture_output=True,
|
|
154
180
|
text=True,
|
|
155
181
|
check=False,
|
|
156
182
|
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
assets/mdify.png,sha256=qUj7WXWqNwpI2KNXOW79XJwqFqa-UI0JEkmt1mmy4Rg,1820418
|
|
2
|
+
mdify/__init__.py,sha256=QROoTzZ7DPEkOz5xppOPbt6mrhz2S9R4qPalnU_JuXY,91
|
|
3
|
+
mdify/__main__.py,sha256=bhpJ00co6MfaVOdH4XLoW04NtLYDa_oJK7ODzfLrn9M,143
|
|
4
|
+
mdify/cli.py,sha256=Bq6E-U-TMBDPPduHz6pOfFcVHPGSOhrwsvxVOTnA8KA,43261
|
|
5
|
+
mdify/container.py,sha256=TEVfWXVQoF8OdMX3K_X460K4sJ59ysVOOy5z5E1RH84,8139
|
|
6
|
+
mdify/docling_client.py,sha256=xuQR6sC1v3EPloOSwExoHCqT4uUxE8myYq-Yeby3C2I,7975
|
|
7
|
+
mdify_cli-2.11.7.dist-info/licenses/LICENSE,sha256=NWM66Uv-XuSMKaU-gaPmvfyk4WgE6zcIPr78wyg6GAo,1065
|
|
8
|
+
mdify_cli-2.11.7.dist-info/METADATA,sha256=ksvj2cFwDeDb36tYVfUe3YpHp306Mt4QyON62voR9s0,9623
|
|
9
|
+
mdify_cli-2.11.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
+
mdify_cli-2.11.7.dist-info/entry_points.txt,sha256=0Xki8f5lADQUtwdt6Eq_FEaieI6Byhk8UE7BuDhChMg,41
|
|
11
|
+
mdify_cli-2.11.7.dist-info/top_level.txt,sha256=qltzf7h8owHq7dxCdfCkSHY8gT21hn1_E8P-VWS_OKM,6
|
|
12
|
+
mdify_cli-2.11.7.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
assets/mdify.png,sha256=qUj7WXWqNwpI2KNXOW79XJwqFqa-UI0JEkmt1mmy4Rg,1820418
|
|
2
|
-
mdify/__init__.py,sha256=3suYlg_g3WiwzZ4VOd6l_T33tmf0s2kt-XchFQcBdZw,91
|
|
3
|
-
mdify/__main__.py,sha256=bhpJ00co6MfaVOdH4XLoW04NtLYDa_oJK7ODzfLrn9M,143
|
|
4
|
-
mdify/cli.py,sha256=hQldYK0BziW0SZcwvQW4ZMq_Osj1I664TiQIpLKeUSM,37300
|
|
5
|
-
mdify/container.py,sha256=tX66OaT7uKhopQm88XtULJUsGUzxtlXvxNXyW7Mtbbw,6909
|
|
6
|
-
mdify/docling_client.py,sha256=xuQR6sC1v3EPloOSwExoHCqT4uUxE8myYq-Yeby3C2I,7975
|
|
7
|
-
mdify_cli-2.11.5.dist-info/licenses/LICENSE,sha256=NWM66Uv-XuSMKaU-gaPmvfyk4WgE6zcIPr78wyg6GAo,1065
|
|
8
|
-
mdify_cli-2.11.5.dist-info/METADATA,sha256=2e3vNMDSVJoPwxG3B4YCYp0yfOgHo4jXelhdeHRj49w,9623
|
|
9
|
-
mdify_cli-2.11.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
-
mdify_cli-2.11.5.dist-info/entry_points.txt,sha256=0Xki8f5lADQUtwdt6Eq_FEaieI6Byhk8UE7BuDhChMg,41
|
|
11
|
-
mdify_cli-2.11.5.dist-info/top_level.txt,sha256=qltzf7h8owHq7dxCdfCkSHY8gT21hn1_E8P-VWS_OKM,6
|
|
12
|
-
mdify_cli-2.11.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|