mlops-python-sdk 1.0.3__tar.gz → 1.0.4__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.
Files changed (52) hide show
  1. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/PKG-INFO +1 -1
  2. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/connection_config.py +17 -12
  3. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/task/task.py +144 -70
  4. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/pyproject.toml +1 -1
  5. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/README.md +0 -0
  6. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/__init__.py +0 -0
  7. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/__init__.py +0 -0
  8. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/__init__.py +0 -0
  9. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/storage/__init__.py +0 -0
  10. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/storage/get_storage_presign_download.py +0 -0
  11. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/storage/get_storage_presign_upload.py +0 -0
  12. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/__init__.py +0 -0
  13. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/cancel_task.py +0 -0
  14. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/delete_task.py +0 -0
  15. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/get_task.py +0 -0
  16. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/get_task_by_task_id.py +0 -0
  17. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/get_task_logs.py +0 -0
  18. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/list_tasks.py +0 -0
  19. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/api/tasks/submit_task.py +0 -0
  20. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/client.py +0 -0
  21. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/errors.py +0 -0
  22. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/__init__.py +0 -0
  23. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/error_response.py +0 -0
  24. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/get_storage_presign_download_response_200.py +0 -0
  25. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/get_storage_presign_upload_response_200.py +0 -0
  26. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/get_task_logs_direction.py +0 -0
  27. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/get_task_logs_log_type.py +0 -0
  28. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/job_spec.py +0 -0
  29. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/job_spec_env.py +0 -0
  30. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/job_spec_master_strategy.py +0 -0
  31. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/log_pagination.py +0 -0
  32. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/message_response.py +0 -0
  33. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task.py +0 -0
  34. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_alloc_tres_type_0.py +0 -0
  35. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_gres_detail_type_0_item.py +0 -0
  36. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_job_resources_type_0.py +0 -0
  37. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_list_response.py +0 -0
  38. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_log_entry.py +0 -0
  39. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_log_entry_log_type.py +0 -0
  40. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_logs_response.py +0 -0
  41. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_resources_type_0.py +0 -0
  42. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_status.py +0 -0
  43. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_submit_request.py +0 -0
  44. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_submit_request_environment_type_0.py +0 -0
  45. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_submit_response.py +0 -0
  46. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_tres_type_0.py +0 -0
  47. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/models/task_tres_used_type_0.py +0 -0
  48. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/py.typed +0 -0
  49. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/api/client/types.py +0 -0
  50. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/exceptions.py +0 -0
  51. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/task/__init__.py +0 -0
  52. {mlops_python_sdk-1.0.3 → mlops_python_sdk-1.0.4}/mlops/task/client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mlops-python-sdk
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: MLOps Python SDK for XCloud Service API
5
5
  License: MIT
6
6
  Author: mlops
@@ -66,17 +66,7 @@ class ConnectionConfig:
66
66
  self.api_path = "/" + self.api_path
67
67
 
68
68
  # Build API URL
69
- if self.debug:
70
- base_url = "http://localhost:8090"
71
- else:
72
- # If domain already includes protocol, use it as-is
73
- # Otherwise, default to http:// for backward compatibility
74
- if self.domain.startswith(("http://", "https://")):
75
- base_url = self.domain
76
- else:
77
- base_url = f"http://{self.domain}"
78
-
79
- self.api_url = f"{base_url}{self.api_path}"
69
+ self.build_api_url()
80
70
 
81
71
  @staticmethod
82
72
  def _get_request_timeout(
@@ -89,7 +79,22 @@ class ConnectionConfig:
89
79
  return request_timeout
90
80
  else:
91
81
  return default_timeout
92
-
82
+
83
+ def build_api_url(self) -> None:
84
+ if self.debug:
85
+ base_url = "http://localhost:8090"
86
+ else:
87
+ # If domain already includes protocol, use it as-is
88
+ # Otherwise, default to http:// for backward compatibility
89
+ if self.domain.startswith(("http://", "https://")):
90
+ base_url = self.domain
91
+ elif self.domain.startswith("localhost") or self.domain.startswith("127.0.0.1"):
92
+ base_url = f"http://{self.domain}"
93
+ else:
94
+ base_url = f"https://{self.domain}"
95
+
96
+ self.api_url = f"{base_url}{self.api_path}"
97
+
93
98
  def get_request_timeout(self, request_timeout: Optional[float] = None):
94
99
  return self._get_request_timeout(self.request_timeout, request_timeout)
95
100
 
@@ -6,12 +6,15 @@ This module provides a convenient interface for managing tasks through the MLOps
6
6
 
7
7
  import json
8
8
  import os
9
+ import shutil
9
10
  import sys
10
11
  import threading
12
+ import tempfile
11
13
  import time
14
+ import zipfile
12
15
  from http import HTTPStatus
13
16
  from pathlib import Path
14
- from typing import Optional
17
+ from typing import Callable, Optional
15
18
 
16
19
  import httpx
17
20
 
@@ -56,6 +59,78 @@ def _validate_archive_file_path(file_path: str) -> Path:
56
59
  raise APIException(f"file_path must be one of .zip, .tar.gz, .tgz: {p}")
57
60
  return p
58
61
 
62
+ def _is_archive_path(p: Path) -> bool:
63
+ lower = p.name.lower()
64
+ return lower.endswith(".zip") or lower.endswith(".tar.gz") or lower.endswith(".tgz")
65
+
66
+
67
+ def _zip_directory(src_dir: Path, dst_zip: Path) -> None:
68
+ src_dir = src_dir.resolve()
69
+ root_name = src_dir.name
70
+ with zipfile.ZipFile(dst_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
71
+ for p in src_dir.rglob("*"):
72
+ if p.is_dir():
73
+ continue
74
+ rel = p.relative_to(src_dir).as_posix()
75
+ if rel in ("", "."):
76
+ continue
77
+ zf.write(p, arcname=f"{root_name}/{rel}")
78
+
79
+
80
+ def _zip_file(src_file: Path, dst_zip: Path) -> None:
81
+ src_file = src_file.resolve()
82
+ with zipfile.ZipFile(dst_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
83
+ zf.write(src_file, arcname=src_file.name)
84
+
85
+
86
+ def _path_to_archive_path(task_name: str, file_path: str) -> tuple[Path, Callable[[], None]]:
87
+ """
88
+ Mirror cli-go `pathToArchivePath` behavior:
89
+ - directory: zip it
90
+ - file: if already an archive (.zip/.tar.gz/.tgz) return as-is; otherwise zip it
91
+ Returns (archive_path, cleanup_callable).
92
+ """
93
+ p = Path(os.path.expanduser(file_path)).resolve()
94
+ if not p.exists():
95
+ raise APIException(f"File not found: {p}")
96
+
97
+ if p.is_dir():
98
+ tmp_dir = (
99
+ Path(tempfile.gettempdir())
100
+ / "xservice-cli"
101
+ / task_name
102
+ / time.strftime("%Y%m%d%H%M%S")
103
+ )
104
+ tmp_dir.mkdir(parents=True, exist_ok=True)
105
+ archive_path = tmp_dir / f"{p.name}.zip"
106
+ try:
107
+ _zip_directory(p, archive_path)
108
+ except Exception as e:
109
+ shutil.rmtree(tmp_dir, ignore_errors=True)
110
+ raise APIException(f"failed to compress directory: {e}") from e
111
+ return archive_path, lambda: shutil.rmtree(tmp_dir, ignore_errors=True)
112
+
113
+ if not p.is_file():
114
+ raise APIException(f"file_path must be a file or directory: {p}")
115
+
116
+ if _is_archive_path(p):
117
+ return _validate_archive_file_path(str(p)), lambda: None
118
+
119
+ tmp_dir = (
120
+ Path(tempfile.gettempdir())
121
+ / "xservice-cli"
122
+ / task_name
123
+ / time.strftime("%Y%m%d%H%M%S")
124
+ )
125
+ tmp_dir.mkdir(parents=True, exist_ok=True)
126
+ archive_path = tmp_dir / f"{p.name}.zip"
127
+ try:
128
+ _zip_file(p, archive_path)
129
+ except Exception as e:
130
+ shutil.rmtree(tmp_dir, ignore_errors=True)
131
+ raise APIException(f"failed to compress file: {e}") from e
132
+ return archive_path, lambda: shutil.rmtree(tmp_dir, ignore_errors=True)
133
+
59
134
 
60
135
  def _upload_file_to_presigned_url(url: str, file_path: Path, timeout: Optional[float]) -> None:
61
136
  def _format_bytes_iec(n: int) -> str:
@@ -246,6 +321,7 @@ class Task:
246
321
  config.api_key = api_key
247
322
  if domain is not None:
248
323
  config.domain = domain
324
+ config.build_api_url()
249
325
  if debug is not None:
250
326
  config.debug = debug
251
327
  if request_timeout is not None:
@@ -328,84 +404,82 @@ class Task:
328
404
  request_kwargs["ntasks"] = 1
329
405
 
330
406
  if file_path:
331
- local_path = _validate_archive_file_path(file_path)
407
+ local_path, cleanup = _path_to_archive_path(name, file_path)
332
408
  timeout = self._config.get_request_timeout()
333
-
334
- # 1) Get presigned upload URL
335
- presign_upload_obj = get_storage_presign_upload.sync_detailed(
336
- client=self._client,
337
- filename=local_path.name,
338
- )
339
- presign_upload = presign_upload_obj.parsed
340
- if isinstance(presign_upload, ErrorResponse):
341
- status_code = (
342
- presign_upload.code
343
- if presign_upload.code != UNSET and presign_upload.code != 0
344
- else presign_upload_obj.status_code.value
409
+ try:
410
+ # 1) Get presigned upload URL
411
+ presign_upload_obj = get_storage_presign_upload.sync_detailed(
412
+ client=self._client,
413
+ filename=local_path.name,
345
414
  )
346
- exception = handle_api_exception(
347
- Response(
348
- status_code=HTTPStatus(status_code),
349
- content=presign_upload_obj.content,
350
- headers=presign_upload_obj.headers,
351
- parsed=None,
415
+ presign_upload = presign_upload_obj.parsed
416
+ if isinstance(presign_upload, ErrorResponse):
417
+ status_code = (
418
+ presign_upload.code
419
+ if presign_upload.code != UNSET and presign_upload.code != 0
420
+ else presign_upload_obj.status_code.value
421
+ )
422
+ exception = handle_api_exception(
423
+ Response(
424
+ status_code=HTTPStatus(status_code),
425
+ content=presign_upload_obj.content,
426
+ headers=presign_upload_obj.headers,
427
+ parsed=None,
428
+ )
352
429
  )
430
+ raise exception
431
+
432
+ if (
433
+ presign_upload is None
434
+ or presign_upload.url in (UNSET, None)
435
+ or presign_upload.key in (UNSET, None)
436
+ ):
437
+ raise APIException("Failed to get presigned upload url: empty response")
438
+
439
+ # 2) Upload file to S3 (presigned URL)
440
+ _upload_file_to_presigned_url(
441
+ url=str(presign_upload.url),
442
+ file_path=local_path,
443
+ timeout=timeout,
353
444
  )
354
- raise exception
355
-
356
- if (
357
- presign_upload is None
358
- or presign_upload.url in (UNSET, None)
359
- or presign_upload.key in (UNSET, None)
360
- ):
361
- raise APIException("Failed to get presigned upload url: empty response")
362
-
363
- # 2) Upload file to S3 (presigned URL)
364
- _upload_file_to_presigned_url(
365
- url=str(presign_upload.url),
366
- file_path=local_path,
367
- timeout=timeout,
368
- )
369
445
 
370
- # 3) Get presigned download URL
371
- presign_download_obj = get_storage_presign_download.sync_detailed(
372
- client=self._client,
373
- key=str(presign_upload.key),
374
- )
375
- presign_download = presign_download_obj.parsed
376
- if isinstance(presign_download, ErrorResponse):
377
- status_code = (
378
- presign_download.code
379
- if presign_download.code != UNSET and presign_download.code != 0
380
- else presign_download_obj.status_code.value
446
+ # 3) Get presigned download URL
447
+ presign_download_obj = get_storage_presign_download.sync_detailed(
448
+ client=self._client,
449
+ key=str(presign_upload.key),
381
450
  )
382
- exception = handle_api_exception(
383
- Response(
384
- status_code=HTTPStatus(status_code),
385
- content=presign_download_obj.content,
386
- headers=presign_download_obj.headers,
387
- parsed=None,
451
+ presign_download = presign_download_obj.parsed
452
+ if isinstance(presign_download, ErrorResponse):
453
+ status_code = (
454
+ presign_download.code
455
+ if presign_download.code != UNSET and presign_download.code != 0
456
+ else presign_download_obj.status_code.value
388
457
  )
389
- )
390
- raise exception
458
+ exception = handle_api_exception(
459
+ Response(
460
+ status_code=HTTPStatus(status_code),
461
+ content=presign_download_obj.content,
462
+ headers=presign_download_obj.headers,
463
+ parsed=None,
464
+ )
465
+ )
466
+ raise exception
391
467
 
392
- if presign_download is None or presign_download.url in (UNSET, None):
393
- raise APIException(
394
- "Failed to get presigned download url: empty response"
395
- )
468
+ if presign_download is None or presign_download.url in (UNSET, None):
469
+ raise APIException("Failed to get presigned download url: empty response")
396
470
 
397
- # 4) Set env var (merge if user already provided environment)
398
- env: dict[str, str] = {}
399
- existing_env = request_kwargs.get("environment")
400
- if isinstance(existing_env, TaskSubmitRequestEnvironmentType0):
401
- env.update(existing_env.additional_properties)
402
- elif isinstance(existing_env, dict):
403
- env.update(existing_env)
404
-
405
- env["SYSTEM_DOWNLOAD_ARCHIVE_URL"] = str(presign_download.url)
406
- request_kwargs["environment"] = TaskSubmitRequestEnvironmentType0.from_dict(
407
- env
408
- )
471
+ # 4) Set env var (merge if user already provided environment)
472
+ env: dict[str, str] = {}
473
+ existing_env = request_kwargs.get("environment")
474
+ if isinstance(existing_env, TaskSubmitRequestEnvironmentType0):
475
+ env.update(existing_env.additional_properties)
476
+ elif isinstance(existing_env, dict):
477
+ env.update(existing_env)
478
+
479
+ env["SYSTEM_DOWNLOAD_ARCHIVE_URL"] = str(presign_download.url)
480
+ request_kwargs["environment"] = TaskSubmitRequestEnvironmentType0.from_dict(env)
481
+ finally:
482
+ cleanup()
409
483
 
410
484
  request = TaskSubmitRequest(**request_kwargs)
411
485
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mlops-python-sdk"
3
- version = "1.0.3"
3
+ version = "1.0.4"
4
4
  description = "MLOps Python SDK for XCloud Service API"
5
5
  authors = ["mlops <mlops@example.com>"]
6
6
  license = "MIT"