ls-algorithm-plugin-sdk 0.3.6__py3-none-any.whl → 0.3.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.
@@ -0,0 +1,336 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import multiprocessing
5
+ import os
6
+ import shutil
7
+ import time
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ DEFAULT_LOG_RETENTION_DAYS = 7
13
+ DEFAULT_OUTPUT_RETENTION_DAYS = 2
14
+ DEFAULT_SCRATCH_RETENTION_DAYS = 2
15
+ SCAN_INTERVAL_SECONDS = 60 * 60
16
+
17
+ _AUDIT_LOG_PREFIX = "cleanup-"
18
+ _SECONDS_PER_DAY = 24 * 60 * 60
19
+
20
+
21
+ def _write_audit(log_dir: Path, event: str, **values: Any) -> None:
22
+ log_dir.mkdir(parents=True, exist_ok=True)
23
+ timestamp = datetime.now().astimezone()
24
+ path = log_dir / f"{_AUDIT_LOG_PREFIX}{timestamp:%Y%m%d}.log"
25
+ payload = {
26
+ "timestamp": timestamp.isoformat(),
27
+ "event": event,
28
+ **values,
29
+ }
30
+ with path.open("a", encoding="utf-8", buffering=1) as stream:
31
+ stream.write(json.dumps(payload, ensure_ascii=False, default=str) + "\n")
32
+
33
+
34
+ def _job_logs(log_dir: Path) -> list[Path]:
35
+ return sorted(
36
+ path
37
+ for path in log_dir.glob("*.log")
38
+ if not path.name.startswith(_AUDIT_LOG_PREFIX) and path.is_file()
39
+ )
40
+
41
+
42
+ def _request_roots(path: Path) -> tuple[set[Path], set[Path]]:
43
+ output_roots: set[Path] = set()
44
+ scratch_roots: set[Path] = set()
45
+ with path.open(encoding="utf-8", errors="replace") as stream:
46
+ for line in stream:
47
+ try:
48
+ payload = json.loads(line)
49
+ except json.JSONDecodeError:
50
+ continue
51
+ if payload.get("event") != "request":
52
+ continue
53
+ output_root = _safe_root(payload.get("outputRoot"))
54
+ scratch_root = _safe_root(payload.get("scratchRoot"))
55
+ if output_root is not None:
56
+ output_roots.add(output_root)
57
+ if scratch_root is not None:
58
+ scratch_roots.add(scratch_root)
59
+ return output_roots, scratch_roots
60
+
61
+
62
+ def _safe_root(value: Any) -> Path | None:
63
+ if not isinstance(value, str) or not value.strip():
64
+ return None
65
+ path = Path(value).expanduser()
66
+ if not path.is_absolute():
67
+ return None
68
+ resolved = path.resolve()
69
+ if resolved == Path(resolved.anchor):
70
+ return None
71
+ return resolved
72
+
73
+
74
+ def _last_nonempty_line(path: Path) -> str:
75
+ with path.open("rb") as stream:
76
+ stream.seek(0, os.SEEK_END)
77
+ position = stream.tell()
78
+ data = b""
79
+ while position > 0:
80
+ read_size = min(8192, position)
81
+ position -= read_size
82
+ stream.seek(position)
83
+ data = stream.read(read_size) + data
84
+ lines = data.splitlines()
85
+ if position == 0 or len(lines) > 1:
86
+ for line in reversed(lines):
87
+ if line.strip():
88
+ return line.decode("utf-8", errors="replace")
89
+ return ""
90
+
91
+
92
+ def _succeeded(path: Path) -> bool:
93
+ try:
94
+ payload = json.loads(_last_nonempty_line(path))
95
+ except (OSError, json.JSONDecodeError):
96
+ return False
97
+ return payload.get("state") == "succeeded"
98
+
99
+
100
+ def _log_created_at(path: Path) -> float:
101
+ timestamp, separator, _ = path.name.partition("-")
102
+ if separator and len(timestamp) == 14 and timestamp.isdigit():
103
+ try:
104
+ return datetime.strptime(timestamp, "%Y%m%d%H%M%S").replace(
105
+ tzinfo=timezone.utc
106
+ ).timestamp()
107
+ except ValueError:
108
+ pass
109
+ stat = path.stat()
110
+ return getattr(stat, "st_birthtime", stat.st_ctime)
111
+
112
+
113
+ def _tree_stats(path: Path) -> tuple[int, float]:
114
+ stat = path.lstat()
115
+ total_size = stat.st_size if not path.is_dir() or path.is_symlink() else 0
116
+ latest_mtime = stat.st_mtime
117
+ if path.is_dir() and not path.is_symlink():
118
+ for root, directories, files in os.walk(path, followlinks=False):
119
+ root_path = Path(root)
120
+ for name in (*directories, *files):
121
+ child = root_path / name
122
+ try:
123
+ child_stat = child.lstat()
124
+ except FileNotFoundError:
125
+ continue
126
+ latest_mtime = max(latest_mtime, child_stat.st_mtime)
127
+ if not child.is_dir() or child.is_symlink():
128
+ total_size += child_stat.st_size
129
+ return total_size, latest_mtime
130
+
131
+
132
+ def _delete(path: Path) -> None:
133
+ if path.is_dir() and not path.is_symlink():
134
+ shutil.rmtree(path)
135
+ else:
136
+ path.unlink()
137
+
138
+
139
+ def cleanup_once(
140
+ log_dir: str | Path,
141
+ *,
142
+ log_retention_days: int = DEFAULT_LOG_RETENTION_DAYS,
143
+ output_retention_days: int = DEFAULT_OUTPUT_RETENTION_DAYS,
144
+ scratch_retention_days: int = DEFAULT_SCRATCH_RETENTION_DAYS,
145
+ known_output_roots: set[Path] | None = None,
146
+ known_scratch_roots: set[Path] | None = None,
147
+ now: float | None = None,
148
+ ) -> tuple[set[Path], set[Path]]:
149
+ directory = Path(log_dir).expanduser().resolve()
150
+ directory.mkdir(parents=True, exist_ok=True)
151
+ output_roots = known_output_roots if known_output_roots is not None else set()
152
+ scratch_roots = known_scratch_roots if known_scratch_roots is not None else set()
153
+ current_time = time.time() if now is None else now
154
+ started_at = datetime.now().astimezone().isoformat()
155
+ deleted_count = 0
156
+ deleted_bytes = 0
157
+ error_count = 0
158
+
159
+ logs = _job_logs(directory)
160
+ for path in logs:
161
+ try:
162
+ discovered_output, discovered_scratch = _request_roots(path)
163
+ except OSError as exc:
164
+ error_count += 1
165
+ _write_audit(
166
+ directory,
167
+ "scan_failed",
168
+ category="log",
169
+ path=str(path),
170
+ error=f"{type(exc).__name__}: {exc}",
171
+ )
172
+ continue
173
+ output_roots.update(discovered_output)
174
+ scratch_roots.update(discovered_scratch)
175
+
176
+ def remove(path: Path, category: str, size: int) -> None:
177
+ nonlocal deleted_count, deleted_bytes, error_count
178
+ try:
179
+ _delete(path)
180
+ except FileNotFoundError:
181
+ return
182
+ except OSError as exc:
183
+ error_count += 1
184
+ _write_audit(
185
+ directory,
186
+ "delete_failed",
187
+ category=category,
188
+ path=str(path),
189
+ sizeBytes=size,
190
+ error=f"{type(exc).__name__}: {exc}",
191
+ )
192
+ return
193
+ deleted_count += 1
194
+ deleted_bytes += size
195
+ _write_audit(
196
+ directory,
197
+ "deleted",
198
+ category=category,
199
+ path=str(path),
200
+ sizeBytes=size,
201
+ )
202
+
203
+ log_cutoff = current_time - log_retention_days * _SECONDS_PER_DAY
204
+ for path in logs:
205
+ try:
206
+ if _log_created_at(path) < log_cutoff and _succeeded(path):
207
+ remove(path, "log", path.stat().st_size)
208
+ except OSError as exc:
209
+ error_count += 1
210
+ _write_audit(
211
+ directory,
212
+ "scan_failed",
213
+ category="log",
214
+ path=str(path),
215
+ error=f"{type(exc).__name__}: {exc}",
216
+ )
217
+
218
+ protected = directory.resolve()
219
+ for category, roots, retention_days in (
220
+ ("output", output_roots, output_retention_days),
221
+ ("scratch", scratch_roots, scratch_retention_days),
222
+ ):
223
+ cutoff = current_time - retention_days * _SECONDS_PER_DAY
224
+ for root in sorted(roots):
225
+ if not root.is_dir():
226
+ continue
227
+ try:
228
+ entries = list(root.iterdir())
229
+ except OSError as exc:
230
+ error_count += 1
231
+ _write_audit(
232
+ directory,
233
+ "scan_failed",
234
+ category=category,
235
+ path=str(root),
236
+ error=f"{type(exc).__name__}: {exc}",
237
+ )
238
+ continue
239
+ for path in entries:
240
+ resolved = path.resolve()
241
+ if (
242
+ resolved == protected
243
+ or resolved in protected.parents
244
+ or protected in resolved.parents
245
+ ):
246
+ continue
247
+ try:
248
+ size, latest_mtime = _tree_stats(path)
249
+ if latest_mtime < cutoff:
250
+ remove(path, category, size)
251
+ except OSError as exc:
252
+ error_count += 1
253
+ _write_audit(
254
+ directory,
255
+ "scan_failed",
256
+ category=category,
257
+ path=str(path),
258
+ error=f"{type(exc).__name__}: {exc}",
259
+ )
260
+
261
+ _write_audit(
262
+ directory,
263
+ "scan_completed",
264
+ startedAt=started_at,
265
+ finishedAt=datetime.now().astimezone().isoformat(),
266
+ deletedCount=deleted_count,
267
+ deletedBytes=deleted_bytes,
268
+ errorCount=error_count,
269
+ )
270
+ return output_roots, scratch_roots
271
+
272
+
273
+ def _cleanup_loop(
274
+ log_dir: str,
275
+ log_retention_days: int,
276
+ output_retention_days: int,
277
+ scratch_retention_days: int,
278
+ stop_event: Any,
279
+ ) -> None:
280
+ output_roots: set[Path] = set()
281
+ scratch_roots: set[Path] = set()
282
+ while True:
283
+ try:
284
+ cleanup_once(
285
+ log_dir,
286
+ log_retention_days=log_retention_days,
287
+ output_retention_days=output_retention_days,
288
+ scratch_retention_days=scratch_retention_days,
289
+ known_output_roots=output_roots,
290
+ known_scratch_roots=scratch_roots,
291
+ )
292
+ except Exception as exc:
293
+ _write_audit(
294
+ Path(log_dir),
295
+ "scan_crashed",
296
+ error=f"{type(exc).__name__}: {exc}",
297
+ )
298
+ if stop_event.wait(SCAN_INTERVAL_SECONDS):
299
+ return
300
+
301
+
302
+ class CleanupProcess:
303
+ def __init__(
304
+ self,
305
+ log_dir: str | Path,
306
+ *,
307
+ log_retention_days: int = DEFAULT_LOG_RETENTION_DAYS,
308
+ output_retention_days: int = DEFAULT_OUTPUT_RETENTION_DAYS,
309
+ scratch_retention_days: int = DEFAULT_SCRATCH_RETENTION_DAYS,
310
+ ) -> None:
311
+ context = multiprocessing.get_context("spawn")
312
+ self._stop_event = context.Event()
313
+ self._process = context.Process(
314
+ target=_cleanup_loop,
315
+ args=(
316
+ str(Path(log_dir).expanduser().resolve()),
317
+ log_retention_days,
318
+ output_retention_days,
319
+ scratch_retention_days,
320
+ self._stop_event,
321
+ ),
322
+ name="algorithm-plugin-cleanup",
323
+ daemon=True,
324
+ )
325
+
326
+ def start(self) -> None:
327
+ self._process.start()
328
+
329
+ def stop(self) -> None:
330
+ if not self._process.is_alive():
331
+ return
332
+ self._stop_event.set()
333
+ self._process.join(timeout=5)
334
+ if self._process.is_alive():
335
+ self._process.terminate()
336
+ self._process.join(timeout=5)
@@ -10,6 +10,11 @@ import sys
10
10
  from pathlib import Path
11
11
  from urllib.parse import urlparse
12
12
 
13
+ from ..cleanup import (
14
+ DEFAULT_LOG_RETENTION_DAYS,
15
+ DEFAULT_OUTPUT_RETENTION_DAYS,
16
+ DEFAULT_SCRATCH_RETENTION_DAYS,
17
+ )
13
18
  from ..deployment import (
14
19
  DEFAULT_CONFIG_NAME,
15
20
  DeploymentConfig,
@@ -31,6 +36,13 @@ SERVICE_NAME_PATTERN = re.compile(
31
36
  )
32
37
 
33
38
 
39
+ def _retention_days(value: str) -> int:
40
+ days = int(value)
41
+ if days < 0:
42
+ raise argparse.ArgumentTypeError("retention days must be non-negative")
43
+ return days
44
+
45
+
34
46
  def configure_parser(parser: argparse.ArgumentParser) -> None:
35
47
  parser.add_argument(
36
48
  "algorithm",
@@ -72,6 +84,24 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
72
84
  action="store_true",
73
85
  help="override request output paths with <repository>/.output/<job-id>",
74
86
  )
87
+ parser.add_argument(
88
+ "--log-retention-days",
89
+ type=_retention_days,
90
+ default=DEFAULT_LOG_RETENTION_DAYS,
91
+ help=f"successful log retention in days (default: {DEFAULT_LOG_RETENTION_DAYS})",
92
+ )
93
+ parser.add_argument(
94
+ "--output-retention-days",
95
+ type=_retention_days,
96
+ default=DEFAULT_OUTPUT_RETENTION_DAYS,
97
+ help=f"output retention in days (default: {DEFAULT_OUTPUT_RETENTION_DAYS})",
98
+ )
99
+ parser.add_argument(
100
+ "--scratch-retention-days",
101
+ type=_retention_days,
102
+ default=DEFAULT_SCRATCH_RETENTION_DAYS,
103
+ help=f"scratch retention in days (default: {DEFAULT_SCRATCH_RETENTION_DAYS})",
104
+ )
75
105
  parser.add_argument(
76
106
  "--gpu-ids",
77
107
  type=gpu_ids,
@@ -426,6 +456,9 @@ def generate_config(
426
456
  gpu_ids=args.gpu_ids,
427
457
  environment=dict(args.env),
428
458
  default_parameters=args.default_parameters,
459
+ log_retention_days=args.log_retention_days,
460
+ output_retention_days=args.output_retention_days,
461
+ scratch_retention_days=args.scratch_retention_days,
429
462
  )
430
463
  config.save()
431
464
  return config
@@ -4,6 +4,7 @@ import argparse
4
4
  import os
5
5
  from pathlib import Path
6
6
 
7
+ from ..cleanup import CleanupProcess
7
8
  from ..deployment import DEFAULT_CONFIG_NAME, DeploymentConfig
8
9
  from ..gpu_isolation import apply_gpu_isolation
9
10
  from ..loader import load_algorithm
@@ -139,12 +140,25 @@ def execute(args: argparse.Namespace) -> int:
139
140
  raise RuntimeError(
140
141
  "compute registration requires the generated release-manifest.json; run configure first"
141
142
  )
142
- run_server(
143
- manager,
144
- host=args.host,
145
- port=args.port,
146
- webui=args.webui,
147
- token=os.getenv("LDP_PLUGIN_TOKEN") or None,
148
- lifecycle=registration,
149
- )
143
+ cleanup = None
144
+ if config is not None and service is not None:
145
+ cleanup = CleanupProcess(
146
+ config.repository / "logs",
147
+ log_retention_days=int(service["logRetentionDays"]),
148
+ output_retention_days=int(service["outputRetentionDays"]),
149
+ scratch_retention_days=int(service["scratchRetentionDays"]),
150
+ )
151
+ cleanup.start()
152
+ try:
153
+ run_server(
154
+ manager,
155
+ host=args.host,
156
+ port=args.port,
157
+ webui=args.webui,
158
+ token=os.getenv("LDP_PLUGIN_TOKEN") or None,
159
+ lifecycle=registration,
160
+ )
161
+ finally:
162
+ if cleanup is not None:
163
+ cleanup.stop()
150
164
  return 0
@@ -11,6 +11,11 @@ from pathlib import Path
11
11
  from typing import Any
12
12
  from urllib.parse import urlparse
13
13
 
14
+ from .cleanup import (
15
+ DEFAULT_LOG_RETENTION_DAYS,
16
+ DEFAULT_OUTPUT_RETENTION_DAYS,
17
+ DEFAULT_SCRATCH_RETENTION_DAYS,
18
+ )
14
19
  from .loader import ALGORITHM_ENTRY_POINT_GROUP, load_algorithm
15
20
  from .release import ReleaseManifest
16
21
 
@@ -148,6 +153,9 @@ class DeploymentConfig:
148
153
  default_parameters: dict[str, Any] | None = None,
149
154
  local_scratch: bool = False,
150
155
  local_output: bool = False,
156
+ log_retention_days: int = DEFAULT_LOG_RETENTION_DAYS,
157
+ output_retention_days: int = DEFAULT_OUTPUT_RETENTION_DAYS,
158
+ scratch_retention_days: int = DEFAULT_SCRATCH_RETENTION_DAYS,
151
159
  ) -> "DeploymentConfig":
152
160
  repository = repository_root(root)
153
161
  _make_repository_importable(repository)
@@ -196,6 +204,9 @@ class DeploymentConfig:
196
204
  "gpuIds": configured_gpu_ids,
197
205
  "token": token or None,
198
206
  "defaultParameters": dict(default_parameters or {}),
207
+ "logRetentionDays": log_retention_days,
208
+ "outputRetentionDays": output_retention_days,
209
+ "scratchRetentionDays": scratch_retention_days,
199
210
  },
200
211
  "registration": registration,
201
212
  "environment": dict(sorted((environment or {}).items())),
@@ -275,6 +286,15 @@ class DeploymentConfig:
275
286
  for name in ("localScratch", "localOutput"):
276
287
  if not isinstance(service.get(name, False), bool):
277
288
  raise RuntimeError(f"service {name} must be a boolean")
289
+ retention_defaults = {
290
+ "logRetentionDays": DEFAULT_LOG_RETENTION_DAYS,
291
+ "outputRetentionDays": DEFAULT_OUTPUT_RETENTION_DAYS,
292
+ "scratchRetentionDays": DEFAULT_SCRATCH_RETENTION_DAYS,
293
+ }
294
+ for name, default in retention_defaults.items():
295
+ value = service.setdefault(name, default)
296
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
297
+ raise RuntimeError(f"service {name} must be a non-negative integer")
278
298
  gpu_ids = service.get("gpuIds")
279
299
  if not isinstance(gpu_ids, list) or any(
280
300
  isinstance(gpu_id, bool) or not isinstance(gpu_id, int) or gpu_id < 0
@@ -348,6 +348,11 @@ class ExecutionManager:
348
348
  "acceptedAt": record.accepted_at,
349
349
  }
350
350
 
351
+ def _resources(self) -> dict[str, Any]:
352
+ if not self.gpu_ids:
353
+ return {}
354
+ return {"gpuIds": list(self.gpu_ids)}
355
+
351
356
  def manifest(self) -> dict[str, Any]:
352
357
  metadata = self.runner.algorithm.metadata()
353
358
  capabilities = {
@@ -361,6 +366,7 @@ class ExecutionManager:
361
366
  manifest = self.release_manifest.plugin_manifest(
362
367
  max_concurrency=self.max_concurrent_executions,
363
368
  capabilities=capabilities,
369
+ resources=self._resources(),
364
370
  )
365
371
  manifest["metadata"] = metadata.to_dict()
366
372
  return manifest
@@ -374,6 +380,7 @@ class ExecutionManager:
374
380
  "maxConcurrency": self.max_concurrent_executions,
375
381
  "capabilities": capabilities,
376
382
  "metadata": metadata.to_dict(),
383
+ "resources": self._resources(),
377
384
  }
378
385
 
379
386
  def heartbeat(self) -> dict[str, Any]:
@@ -391,6 +398,7 @@ class ExecutionManager:
391
398
  "activeExecutions": len(active),
392
399
  "maxConcurrency": self.max_concurrent_executions,
393
400
  "executions": active,
401
+ "resources": self._resources(),
394
402
  }
395
403
 
396
404
  def status(self, execution_id: str) -> dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ls-algorithm-plugin-sdk
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Protocol-independent runtime SDK for dataset algorithms
5
5
  Author: Ling Robotics
6
6
  License: Proprietary
@@ -71,11 +71,19 @@ sudo /srv/camera-space-mano/.venv/bin/algorithm-plugin configure \
71
71
  --api-key "$LDP_INTERNAL_API_KEY" \
72
72
  --port 9030 \
73
73
  --cluster production \
74
- --gpu-ids 0,1
74
+ --gpu-ids 0,1 \
75
+ --log-retention-days 7 \
76
+ --output-retention-days 2 \
77
+ --scratch-retention-days 2
75
78
  ```
76
79
 
77
80
  重复执行 configure 需要显式提供 `--force`。
78
81
 
82
+ 配置部署的服务会启动独立清理进程,每小时扫描一次。仅删除创建超过日志保留期且
83
+ 末行状态为 `succeeded` 的任务日志;`outputRoot` 和 `scratchRoot` 下超过对应保留期
84
+ 未修改的数据也会被删除。清理记录按天写入 `<repository>/logs/cleanup-YYYYMMDD.log`,
85
+ 包含清理时间、路径和字节数;清理记录自身不会被自动删除。
86
+
79
87
  确认输出后执行:
80
88
 
81
89
  ```bash
@@ -1,8 +1,9 @@
1
1
  algorithm_plugin_sdk/__init__.py,sha256=OKj2VcNXTwZmRgPlECI6QUBlxW1YLtflr4RAgs6w064,1223
2
2
  algorithm_plugin_sdk/algorithm.py,sha256=INjX-ygMQfHk8D1AZbEEdCw1P4VNQLGF989lP1J1H1k,1025
3
+ algorithm_plugin_sdk/cleanup.py,sha256=B8SiIoTj6oxOFkCQO4Cufl2JamsGQoFCoi2FdOCG4u0,10945
3
4
  algorithm_plugin_sdk/cli.py,sha256=20cjY2JXNQOFS-7ys5sNi-y9-1CObf-GYOTkjJ0Q6WI,1419
4
5
  algorithm_plugin_sdk/context.py,sha256=Kj-eDZg7PgX6xNzrWrQs37UfepTP8fXv4_8hCgsAIDA,11016
5
- algorithm_plugin_sdk/deployment.py,sha256=5WlZfML9ymWrAFsmdX-bBHTP2kuVY4WO5wKHs35Dkd4,14936
6
+ algorithm_plugin_sdk/deployment.py,sha256=D4OlpIjPsd1UcfTa1RoGOc4ehAo2ZPOQcoQOB98vaz4,15960
6
7
  algorithm_plugin_sdk/errors.py,sha256=DNmI2c36Pd7K6tTaonqfEgpBzwX0o6pXzYrc8r2Z--k,730
7
8
  algorithm_plugin_sdk/gpu_isolation.py,sha256=toXUQ1vfzfLPiideJ8dWkfa9NW2Lc8V0sbNx-75YTjg,794
8
9
  algorithm_plugin_sdk/loader.py,sha256=XKmyqbJMoUMdKoo2eRQFGdbsmlz6NBgtUeIdqzumJFs,2561
@@ -11,14 +12,14 @@ algorithm_plugin_sdk/models.py,sha256=FIwUm8QDK8L37pV7AcCMnmYlDP-2l93zp8yc0KSu4y
11
12
  algorithm_plugin_sdk/registration.py,sha256=s5oQkf12_uRjdA4CYMYM8SDQjhsk1rzDDF-3w36U_lA,10474
12
13
  algorithm_plugin_sdk/release.py,sha256=KM-kRM1oFYMgjDFnXdu6BLR1b4dsvWWDCEgTNq8cb3w,9439
13
14
  algorithm_plugin_sdk/runner.py,sha256=583Y9_7efvW5rrmM2nt316NEpCRO6kd8dKYjFRjgtCk,3183
14
- algorithm_plugin_sdk/service.py,sha256=HQLDW5nlGGXJNZ0KMFU4I1rPzWHMf_LlE-tsY42PHnc,31045
15
+ algorithm_plugin_sdk/service.py,sha256=zCEhUMjboVTNqnQSKc1czzjdQOlXVa94y38P8McfWyE,31320
15
16
  algorithm_plugin_sdk/webui_app.py,sha256=Gvt2FPt0xZBjYi39sqTQqcosllifYeJam49_mGfkEVw,1530
16
17
  algorithm_plugin_sdk/cli_impl/__init__.py,sha256=uLknQVGipNd7eSJegDHZS0NVowMa0qAaOf63wFI3zHM,78
17
- algorithm_plugin_sdk/cli_impl/configure.py,sha256=hO9vwFfZ4yqeQUyYc3heE1MDLK1lppN69OoF2zeVVaM,13761
18
+ algorithm_plugin_sdk/cli_impl/configure.py,sha256=270-0DVxciyxsdipoi16DKL38n1vQft_REx_scJu1SU,14924
18
19
  algorithm_plugin_sdk/cli_impl/cut.py,sha256=R3ecRzDHffJZNqbJaKeX4S4hUFdQl2afXAfklkX1Ah0,19764
19
20
  algorithm_plugin_sdk/cli_impl/parsing.py,sha256=TDX45Spu4WMrczuzTMN9ZC2SYyHE11OPBCvN45yPsxs,1399
20
21
  algorithm_plugin_sdk/cli_impl/run.py,sha256=1FuOpYYOAp-iyc8-KRSVch33v3D3Zd6j2oGUkgYi9PI,6001
21
- algorithm_plugin_sdk/cli_impl/serve.py,sha256=1Ma08MHMjKk9wKO8tGdymVvedcGC-jaKdaUCVBTPMro,5187
22
+ algorithm_plugin_sdk/cli_impl/serve.py,sha256=spsGFb3ay__YWnOkvo-FWisDJzCZRaHuAxJCG4hkvQY,5724
22
23
  algorithm_plugin_sdk/examples/__init__.py,sha256=WxZZzUzxKhqZg3KibVY-7CB960DNjt6IfjciZ8JXbq0,57
23
24
  algorithm_plugin_sdk/examples/example_algorithm.py,sha256=j2RsVaRPxpJn3kK6_H_JqAceKTpJE8txNm-oTrH2v6E,3787
24
25
  algorithm_plugin_sdk/examples/simulated_algorithm.py,sha256=bIaneSMM09CJfV5-oGuZ5QwBDWV1u-7Fn0WDN1TuwGw,2944
@@ -26,8 +27,8 @@ algorithm_plugin_sdk/webui/__init__.py,sha256=cvtaktJXz_DYG4QbV5ppoqY2bFaMLuiUcT
26
27
  algorithm_plugin_sdk/webui/app.css,sha256=5HGDKlbUFLZMgCknlRuwCjiNcCr8poIF5YGHchyaamE,2965
27
28
  algorithm_plugin_sdk/webui/app.js,sha256=0YsXsocEY3yDseG3jh0RhzI8xo_bYmsbSxdN3FdCswQ,7657
28
29
  algorithm_plugin_sdk/webui/index.html,sha256=Z222OuaacWdbr0s-sjJq6wmg45u96tIneM5-LP55Z8I,2261
29
- ls_algorithm_plugin_sdk-0.3.6.dist-info/METADATA,sha256=2Hq86ZVOEuL6xLDMbPvJBgC_w67uF1JXIiAguEh7mNA,2709
30
- ls_algorithm_plugin_sdk-0.3.6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
31
- ls_algorithm_plugin_sdk-0.3.6.dist-info/entry_points.txt,sha256=TU0R_TxuB5OuOiZ5BHOEmUPtHvN3v8ARUW8d3PztHcE,67
32
- ls_algorithm_plugin_sdk-0.3.6.dist-info/top_level.txt,sha256=8lsgxZ8HJGLlzJ5Rt8CiVzw9Ccme286i3akbmeMjBos,21
33
- ls_algorithm_plugin_sdk-0.3.6.dist-info/RECORD,,
30
+ ls_algorithm_plugin_sdk-0.3.7.dist-info/METADATA,sha256=Czb68efzbr0tPKqCkD5uc45Rn92P1Auha-rCNC-ovqQ,3205
31
+ ls_algorithm_plugin_sdk-0.3.7.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
32
+ ls_algorithm_plugin_sdk-0.3.7.dist-info/entry_points.txt,sha256=TU0R_TxuB5OuOiZ5BHOEmUPtHvN3v8ARUW8d3PztHcE,67
33
+ ls_algorithm_plugin_sdk-0.3.7.dist-info/top_level.txt,sha256=8lsgxZ8HJGLlzJ5Rt8CiVzw9Ccme286i3akbmeMjBos,21
34
+ ls_algorithm_plugin_sdk-0.3.7.dist-info/RECORD,,