aipmodel 0.2.71__tar.gz → 0.2.73__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.71
3
+ Version: 0.2.73
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
@@ -120,6 +120,71 @@ reset). The SDK now **retries these automatically**, so `add_model`,
120
120
  worker) is fully protected; for multiple workers/replicas add a distributed
121
121
  lock (Mlops-Core/Redis).
122
122
 
123
+ ## Registry ↔ Storage Consistency (Integrity, Repair & Disaster Recovery)
124
+
125
+ A model lives in **two systems at once**: its metadata in the registry
126
+ (Mlops-Core) and its files in S3/Ceph. If either side changes behind the SDK's
127
+ back (someone deletes a folder in Ceph by hand, or the registry loses data),
128
+ you get a **zombie** (registry entry pointing at missing files) or an
129
+ **orphan** (files no registry entry references). The SDK detects, repairs, and
130
+ recovers all of these:
131
+
132
+ ### Recovery sidecar: `_meta.json`
133
+
134
+ Every `add_model` writes a small `_meta.json` next to each version's files
135
+ (model name, version, number, category, tags, owner, size, created, public).
136
+ Storage is therefore **self-describing** — a registry entry can be rebuilt from
137
+ S3 alone.
138
+
139
+ ### Detect
140
+
141
+ ```python
142
+ report = manager.verify_model_integrity(model_name="my_model")
143
+ # {"status": "ok" | "missing_files" | "orphan_files",
144
+ # "versions": [{"version": "v1.0", "files_exist": True, ...}],
145
+ # "orphan_folders": [...]}
146
+
147
+ manager.verify_all_models() # one report per model in your project
148
+ manager.find_orphan_models() # S3 model folders unknown to the registry
149
+ # (each flagged recoverable=True when _meta.json exists)
150
+ ```
151
+
152
+ ### Repair (each guarded against misuse)
153
+
154
+ ```python
155
+ # Zombie version (files deleted in Ceph) -> remove it from the registry.
156
+ # REFUSES if the files actually still exist (use delete_model for a real delete).
157
+ manager.remove_missing_version("my_model", "v1.0")
158
+
159
+ # Stray folders under a model that no version references -> delete them.
160
+ manager.purge_orphan_folders(model_name="my_model")
161
+
162
+ # S3 model folder the registry doesn't know -> delete it.
163
+ # REFUSES if the id IS registered.
164
+ manager.purge_orphan_model(model_id)
165
+ ```
166
+
167
+ ### Disaster recovery (registry data lost, S3 survives)
168
+
169
+ ```python
170
+ for orphan in manager.find_orphan_models():
171
+ if orphan["recoverable"]:
172
+ manager.recover_model_from_storage(orphan["model_id"])
173
+ # Rebuilds the registry entry (name, all versions, latest, sizes, category,
174
+ # tags) from the _meta.json sidecars. Files stay where they are.
175
+ ```
176
+
177
+ Consistency is also protected *proactively*: `add_model` rolls back files,
178
+ registry entry and ACL record together on failure, and `delete_model` updates
179
+ the registry **before** deleting S3 (a crash can leave a harmless orphan, never
180
+ a zombie). Versions added before the sidecar feature (< 0.2.69) have no
181
+ `_meta.json` and are reported as non-recoverable.
182
+
183
+ > **S3-compat note:** the client is configured to stay compatible with
184
+ > MinIO/Ceph RGW across boto3 versions (legacy `Content-MD5` checksums on
185
+ > newer boto3, with a per-object delete fallback if a batch delete is
186
+ > rejected) — no user action needed.
187
+
123
188
  ## Access Control (ACL)
124
189
 
125
190
  ACL is **off by default** and fully optional. Toggle it with one env var:
@@ -314,7 +379,32 @@ manager.delete_model(model_name="demo_model_hf")
314
379
  | **`set_latest_version`** | `model_name`, `version` | Updates the 'latest' pointer to a specific version without modifying files. |
315
380
  | **`set_model_public`** | `model_name`, `public` | Moves a model's files between the private and public buckets (transactional, parallel server-side copy). |
316
381
  | **`share_model`** | `model_name`, `target_user`, `access_level` | Grants another user access to a model. Requires `ACL_ENABLED=true`. |
317
- | **`delete_model`** | `model_name`, `version` | If `version` provided: deletes that version (and rolls "latest" back to the next one if it was the latest). If that was the *last* version, the model container is removed too. If `version` is omitted: deletes the entire model in one call — this already is "delete all versions", no need to delete one by one. |
382
+ | **`delete_model`** | `model_name`, `version` | If `version` provided: deletes that version (and rolls "latest" back to the next one if it was the latest). If that was the *last* version, the model container is removed too. If `version` is omitted: deletes the entire model in one call — this already is "delete all versions", no need to delete one by one. Registry metadata is updated **before** S3 files are removed (crash-safe ordering). |
383
+ | **`verify_model_integrity`** | `model_name` or `model_id` | Cross-checks one model between registry and S3. Returns `{status: ok\|missing_files\|orphan_files, versions: [...], orphan_folders: [...]}`. |
384
+ | **`verify_all_models`** | — | Runs `verify_model_integrity` over every model in your project; returns a list of reports. |
385
+ | **`find_orphan_models`** | — | Lists S3 model folders the registry doesn't know about, each flagged `recoverable` when a `_meta.json` sidecar exists. |
386
+ | **`remove_missing_version`** | `model_name`, `version` | Sync-repair for a zombie version whose S3 files were deleted: removes it from the registry. Refuses if the files still exist. |
387
+ | **`purge_orphan_folders`** | `model_name` or `model_id` | Deletes S3 folders under a model that no registry version references. Returns the purged folder names. |
388
+ | **`purge_orphan_model`** | `model_id` | Deletes the S3 storage of a model unknown to the registry. Refuses for registered ids. |
389
+ | **`recover_model_from_storage`** | `model_id` | Disaster recovery: rebuilds the registry entry (all versions, latest, category, tags) from the `_meta.json` sidecars in S3. Returns the new registry id. |
390
+
391
+ ### Backward compatibility
392
+
393
+ The ClearML → Mlops-Core rename kept the old public names working, so code
394
+ written against the pre-0.2.65 SDK does not break:
395
+
396
+ - **Health-check methods** — `check_clearml_service()` and `check_clearml_auth()`
397
+ still exist as thin aliases of `check_mlops_core_service()` /
398
+ `check_mlops_core_auth()`.
399
+ - **Instance attributes** — `mgr.CLEARML_API_HOST`, `CLEARML_WEB_HOST`,
400
+ `CLEARML_USERNAME`, `CLEARML_ACCESS_KEY`, `CLEARML_SECRET_KEY` are kept as
401
+ aliases of the canonical `MLOPS_CORE_*` attributes.
402
+ - **Constructor / env** — the `CLEARML_API_HOST` / `CLEARML_WEB_HOST` kwargs and
403
+ env vars are accepted as deprecated fallbacks (the `MLOPS_CORE_*` names win if
404
+ both are set).
405
+ - **Method signatures are additive only** — every new parameter (`owner`,
406
+ `model_folder_name`, the `list_models` filters) is optional with a default
407
+ that reproduces the old behavior; nothing was removed or reordered.
318
408
 
319
409
  ---
320
410
 
@@ -11,5 +11,5 @@ if _os.environ.get("AIPMODEL_UPDATE_CHECK", "").lower() in ("1", "true", "yes"):
11
11
 
12
12
  __all__ = ["MLOpsManager", "CephS3Manager"]
13
13
 
14
- __version__ = "0.2.71"
14
+ __version__ = "0.2.73"
15
15
  __description__ = "SDK for model registration, versioning, and storage"
@@ -434,7 +434,17 @@ class MLOpsManager:
434
434
  # from the user-management wire format — do not rename them.
435
435
  self.MLOPS_CORE_ACCESS_KEY = user_info["clearml_access_key"]
436
436
  self.MLOPS_CORE_SECRET_KEY = user_info["clearml_secret_key"]
437
-
437
+
438
+ # Backward-compatibility attribute aliases (pre-0.2.65 names). Callers
439
+ # that read mgr.CLEARML_API_HOST / CLEARML_WEB_HOST / CLEARML_USERNAME /
440
+ # CLEARML_ACCESS_KEY / CLEARML_SECRET_KEY keep working after the
441
+ # Mlops-Core rename — the new MLOPS_CORE_* names are canonical.
442
+ self.CLEARML_API_HOST = self.MLOPS_CORE_API_HOST
443
+ self.CLEARML_WEB_HOST = self.MLOPS_CORE_WEB_HOST
444
+ self.CLEARML_USERNAME = self.MLOPS_CORE_USERNAME
445
+ self.CLEARML_ACCESS_KEY = self.MLOPS_CORE_ACCESS_KEY
446
+ self.CLEARML_SECRET_KEY = self.MLOPS_CORE_SECRET_KEY
447
+
438
448
  if not skip_connection_check:
439
449
  if self.verbose:
440
450
  print("[SDK_INFO] Performing health checks...")
@@ -608,6 +618,16 @@ class MLOpsManager:
608
618
  print(f"[SDK_FAIL] Mlops-Core Auth Check Failed: {e}")
609
619
  return False
610
620
 
621
+ # Backward-compatibility aliases. These methods were renamed
622
+ # check_clearml_* -> check_mlops_core_* as part of the Mlops-Core rename;
623
+ # the old names are kept as thin forwarders so pre-0.2.65 callers that do
624
+ # mgr.check_clearml_service() / mgr.check_clearml_auth() keep working.
625
+ def check_clearml_service(self):
626
+ return self.check_mlops_core_service()
627
+
628
+ def check_clearml_auth(self):
629
+ return self.check_mlops_core_auth()
630
+
611
631
  def get_model_id_by_name(self, name, owner=None):
612
632
  """
613
633
  Resolves a model name to its Mlops-Core ID.
@@ -1657,7 +1677,8 @@ class MLOpsManager:
1657
1677
 
1658
1678
  Returns:
1659
1679
  {
1660
- "model_id", "model_name", "status": "ok"|"missing_files"|"orphan_files",
1680
+ "model_id", "model_name",
1681
+ "status": "ok"|"missing_files"|"orphan_files"|"empty",
1661
1682
  "versions": [{"version","path","files_exist","status"}...],
1662
1683
  "orphan_folders": [<S3 folders under the model not referenced by any version>],
1663
1684
  }
@@ -1713,6 +1734,15 @@ class MLOpsManager:
1713
1734
  report["status"] = "missing_files"
1714
1735
  elif report["orphan_folders"]:
1715
1736
  report["status"] = "orphan_files"
1737
+ elif not versions_map:
1738
+ # Registry holds the model container but NO versions and NO files.
1739
+ # This is the shell an interrupted add leaves behind when the process
1740
+ # is hard-killed mid-upload (pod rollout, OOM, node eviction) — the
1741
+ # in-process rollback in add_model never runs in that case. Surface it
1742
+ # (it was previously reported "ok") so it can be cleaned up or the add
1743
+ # retried. Note: a version add still in flight shows its temp folder as
1744
+ # an orphan_folder above, so it reports "orphan_files", not "empty".
1745
+ report["status"] = "empty"
1716
1746
  return report
1717
1747
 
1718
1748
  def verify_all_models(self):
@@ -1793,6 +1823,23 @@ class MLOpsManager:
1793
1823
  self.delete_model(model_name=model_name, version=version)
1794
1824
  print(f"[SDK_SUCCESS] Synced: removed zombie version '{version}' of '{model_name}' from registry.")
1795
1825
 
1826
+ def remove_empty_model(self, model_name=None, model_id=None):
1827
+ """
1828
+ Sync-repair for an EMPTY model container: the registry holds the model
1829
+ but it has no versions and no stored files — the shell an interrupted /
1830
+ hard-killed add leaves behind. Deletes the container so the registry is
1831
+ clean. Refuses to run if the model actually has versions or files (so a
1832
+ real model, or an add still in flight, is never removed by mistake).
1833
+ """
1834
+ report = self.verify_model_integrity(model_name=model_name, model_id=model_id)
1835
+ if report["status"] != "empty":
1836
+ raise ValueError(
1837
+ f"Model '{report['model_name']}' is not empty (status={report['status']}); "
1838
+ f"refusing to remove it. Use delete_model for a real delete."
1839
+ )
1840
+ self.delete_model(model_id=report["model_id"])
1841
+ print(f"[SDK_SUCCESS] Removed empty model shell '{report['model_name']}'.")
1842
+
1796
1843
  def purge_orphan_folders(self, model_name=None, model_id=None):
1797
1844
  """
1798
1845
  Sync-repair for ORPHAN folders inside a registered model: S3 folders
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aipmodel
3
- Version: 0.2.71
3
+ Version: 0.2.73
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.71",
34
+ version="0.2.73",
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