aipmodel 0.2.65__tar.gz → 0.2.66__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: aipmodel
3
- Version: 0.2.65
3
+ Version: 0.2.66
4
4
  Summary: SDK for model registration, versioning, and storage
5
5
  Home-page: https://github.com/AIP-MLOPS/model-registry
6
6
  Author: AIP MLOPS Team
@@ -51,6 +51,12 @@ S3_MAX_CONCURRENCY="10"
51
51
  S3_UPLOAD_WORKERS="4"
52
52
  S3_DOWNLOAD_WORKERS="4"
53
53
 
54
+ # Auto-retry for transient connection drops during large transfers
55
+ S3_MAX_RETRIES="5" # per-file retries before giving up
56
+ S3_RETRY_BACKOFF_SECONDS="2" # base backoff, grows exponentially per attempt
57
+ S3_RETRY_MAX_DELAY_SECONDS="30" # cap on a single backoff sleep
58
+ S3_BOTO_MAX_ATTEMPTS="10" # botocore request-level retry attempts (adaptive)
59
+
54
60
  ```
55
61
 
56
62
  > **Note:** `CLEARML_WEB_HOST` is new. If not provided, the SDK will default to the API host, which might result in incorrect clickable links in the output.
@@ -68,6 +74,45 @@ S3_DOWNLOAD_WORKERS="4"
68
74
 
69
75
  Nothing is ever logged to a file or hidden — all output goes to stdout via `print()`, so it's visible wherever the process runs.
70
76
 
77
+ ## Resilient Transfers (Auto-Retry)
78
+
79
+ Large multi-GB uploads/downloads over Ceph/RGW occasionally hit a **transient**
80
+ connection drop mid-stream (`IncompleteRead` / `ProtocolError` / connection
81
+ reset). The SDK now **retries these automatically**, so `add_model`,
82
+ `get_model` and `download` complete without manual intervention:
83
+
84
+ - Each **individual file** transfer is retried on a transient error with
85
+ exponential backoff (`S3_RETRY_BACKOFF_SECONDS`, capped by
86
+ `S3_RETRY_MAX_DELAY_SECONDS`), up to `S3_MAX_RETRIES` times. Downloads reset
87
+ the partial file before each retry so no corrupt bytes remain.
88
+ - The underlying boto3 client also uses botocore's **adaptive** request-level
89
+ retries (`S3_BOTO_MAX_ATTEMPTS`).
90
+ - **Non-transient** errors (bad input, auth, not-found) are **not** retried —
91
+ they fail immediately as before.
92
+ - Fully backward compatible: method signatures, return values and the final
93
+ `ValueError` raised on ultimate failure are unchanged. All knobs are
94
+ `.env`-overridable with sane defaults.
95
+
96
+ ## Concurrent Adds
97
+
98
+ `add_model` is **concurrency-safe** — you can run many adds at the same time:
99
+
100
+ - Adds with **different model names** run **fully in parallel** (own ClearML id,
101
+ own S3 prefix — no interference).
102
+ - Adds with the **same `(project, model_name)`** are **serialized** by a
103
+ process-wide lock. Otherwise two simultaneous same-name adds would race on the
104
+ existence check + auto version-number computation and could create a
105
+ **duplicate model** or **overwrite a version**. With the lock the second add
106
+ waits for the first, then correctly continues to the next version
107
+ (`_v2` → `_v3`, …) — no duplicates, no lost versions.
108
+ - Uploads are **staged**: files go to a temp S3 path first and are only moved to
109
+ the final version path once the whole upload succeeds; any failure rolls back
110
+ (deletes the new model if it was new + the temp folder) and re-raises, so a
111
+ crashed/aborted add never leaves a half-written version.
112
+ - The lock is **per-process**. One server process (e.g. a single `uvicorn`
113
+ worker) is fully protected; for multiple workers/replicas add a distributed
114
+ lock (ClearML/Redis).
115
+
71
116
  ## Access Control (ACL)
72
117
 
73
118
  ACL is **off by default** and fully optional. Toggle it with one env var:
@@ -6,5 +6,5 @@ check_latest_version("aipmodel")
6
6
 
7
7
  __all__ = ["MLOpsManager", "CephS3Manager"]
8
8
 
9
- __version__ = "0.2.65"
9
+ __version__ = "0.2.66"
10
10
  __description__ = "SDK for model registration, versioning, and storage"
@@ -10,6 +10,7 @@ from huggingface_hub import snapshot_download
10
10
  from typing import Optional, List, Dict, Any
11
11
  import json
12
12
  import logging
13
+ import threading
13
14
  import time
14
15
  import re
15
16
  from tqdm import tqdm
@@ -22,6 +23,25 @@ load_dotenv()
22
23
 
23
24
  logger = logging.getLogger(__name__)
24
25
 
26
+ # Process-wide locks that serialize concurrent add_model calls targeting the SAME
27
+ # (project, model_name). Two simultaneous adds of the same name would otherwise
28
+ # race on the existence check + version-number computation and could create a
29
+ # duplicate model or clobber a version. Different names lock on different keys and
30
+ # still run fully in parallel. NOTE: per-process — with multiple API workers /
31
+ # replicas a distributed lock would be required for the same guarantee.
32
+ _ADD_MODEL_LOCKS = {}
33
+ _ADD_MODEL_LOCKS_GUARD = threading.Lock()
34
+
35
+
36
+ def _get_add_model_lock(project_id, model_name):
37
+ key = (project_id, model_name)
38
+ with _ADD_MODEL_LOCKS_GUARD:
39
+ lock = _ADD_MODEL_LOCKS.get(key)
40
+ if lock is None:
41
+ lock = threading.Lock()
42
+ _ADD_MODEL_LOCKS[key] = lock
43
+ return lock
44
+
25
45
  class ProjectsAPI:
26
46
  def __init__(self, post, verbose):
27
47
  self.verbose = verbose
@@ -670,6 +690,28 @@ class MLOpsManager:
670
690
  def add_model(self, source_type, model_name=None, version=None, source_path=None, code_path=None, category=None, tags=None,
671
691
  public=False, external_ceph_endpoint_url=None, external_ceph_bucket_name=None, external_ceph_access_key=None, external_ceph_secret_key=None,
672
692
  model_folder_name=None):
693
+ """
694
+ Register/add a model version. Concurrency-safe: adds that target the SAME
695
+ (project, model_name) are serialized by a process-wide lock so simultaneous
696
+ same-name adds cannot create a duplicate model or collide on the auto
697
+ version number; adds with different names run fully in parallel. All logic
698
+ lives in _add_model_impl — signature, behavior and return value are
699
+ unchanged (this wrapper only adds the lock).
700
+ """
701
+ with _get_add_model_lock(self.project_id, model_name):
702
+ return self._add_model_impl(
703
+ source_type, model_name=model_name, version=version, source_path=source_path,
704
+ code_path=code_path, category=category, tags=tags, public=public,
705
+ external_ceph_endpoint_url=external_ceph_endpoint_url,
706
+ external_ceph_bucket_name=external_ceph_bucket_name,
707
+ external_ceph_access_key=external_ceph_access_key,
708
+ external_ceph_secret_key=external_ceph_secret_key,
709
+ model_folder_name=model_folder_name,
710
+ )
711
+
712
+ def _add_model_impl(self, source_type, model_name=None, version=None, source_path=None, code_path=None, category=None, tags=None,
713
+ public=False, external_ceph_endpoint_url=None, external_ceph_bucket_name=None, external_ceph_access_key=None, external_ceph_secret_key=None,
714
+ model_folder_name=None):
673
715
 
674
716
  if self.verbose:
675
717
  print(f"[SDK_INFO] Adding model: {model_name} (Source: {source_type}, Public: {public})")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aipmodel
3
- Version: 0.2.65
3
+ Version: 0.2.66
4
4
  Summary: SDK for model registration, versioning, and storage
5
5
  Home-page: https://github.com/AIP-MLOPS/model-registry
6
6
  Author: AIP MLOPS Team
@@ -31,7 +31,7 @@ version, description = read_meta()
31
31
 
32
32
  setup(
33
33
  name="aipmodel",
34
- version="0.2.65",
34
+ version="0.2.66",
35
35
  description=description,
36
36
  author="AIP MLOPS Team",
37
37
  author_email="mohmmadweb@gmail.com",
File without changes
File without changes
File without changes