aipmodel 0.2.64__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,23 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aipmodel
3
- Version: 0.2.64
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
7
7
  Author-email: mohmmadweb@gmail.com
8
8
  Requires-Python: >=3.6
9
9
  Requires-Dist: boto3==1.34.0
10
- Requires-Dist: clearml==2.0.2
11
- Requires-Dist: fastapi==0.128.2
12
- Requires-Dist: hf_xet==1.2.0
13
10
  Requires-Dist: huggingface-hub==0.36.2
14
- Requires-Dist: pydantic==2.12.5
15
11
  Requires-Dist: python-dotenv==1.2.1
16
12
  Requires-Dist: requests==2.32.5
17
- Requires-Dist: tabulate==0.9.0
18
13
  Requires-Dist: tqdm==4.67.1
19
- Requires-Dist: ruff==0.14.7
20
- Requires-Dist: uvicorn[standard]==0.38.0
21
14
  Dynamic: author
22
15
  Dynamic: author-email
23
16
  Dynamic: home-page
@@ -0,0 +1,371 @@
1
+ # AIP Model SDK
2
+
3
+ This SDK provides a robust interface for registering, uploading, downloading, listing, and deleting machine learning models using ClearML and S3 (Ceph) as storage. It supports **automatic versioning**, **categorization**, **tagging**, **metadata transparency**, **web link generation**, and fail-fast connectivity checks.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ Install from PyPI:
10
+
11
+ ```bash
12
+ pip install aipmodel
13
+
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Configuration & Authentication
19
+
20
+ To use the SDK, you must set up your environment variables. You can create a `.env` file in your project root:
21
+
22
+ ```env
23
+ # ClearML API Credentials
24
+ CLEARML_API_HOST="http://your-clearml-server:8008"
25
+ CLEARML_WEB_HOST="http://your-clearml-web-ui:8080" # Required for generating model web links
26
+ CLEARML_ACCESS_KEY="your_access_key"
27
+ CLEARML_SECRET_KEY="your_secret_key"
28
+
29
+ # Ceph / S3 Storage
30
+ CEPH_ENDPOINT_URL="https://your-ceph-endpoint.com"
31
+
32
+ # User Management (Internal)
33
+ USER_MANAGEMENT_API="http://your-user-manager:30009"
34
+ USER_TOKEN="your_bearer_token" # Or pass explicitly in code
35
+
36
+ # Public bucket (used by public=True / set_model_public)
37
+ CEPH_PUBLIC_BUCKET_NAME="aip-public"
38
+ CEPH_ADMIN_ACCESS_KEY="your_public_bucket_access_key"
39
+ CEPH_ADMIN_SECRET_KEY="your_public_bucket_secret_key"
40
+
41
+ # ACL (Access Control) - optional, see "Access Control (ACL)" below
42
+ ACL_ENABLED="false"
43
+ ACL_API_HOST="http://your-acl-server:30011"
44
+
45
+ # Optional overrides (all have sane defaults if omitted)
46
+ SDK_REQUEST_TIMEOUT="30"
47
+ MODEL_ALLOWED_CATEGORIES="Text Generation,Image Classification"
48
+ S3_MULTIPART_THRESHOLD_MB="64"
49
+ S3_MULTIPART_CHUNKSIZE_MB="16"
50
+ S3_MAX_CONCURRENCY="10"
51
+ S3_UPLOAD_WORKERS="4"
52
+ S3_DOWNLOAD_WORKERS="4"
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
+
60
+ ```
61
+
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.
63
+
64
+ > Every constant the SDK relies on (timeouts, categories, transfer tuning, ACL) is overridable via `.env` — nothing important is hardcoded.
65
+
66
+ ---
67
+
68
+ ## Logging Verbosity
69
+
70
+ `MLOpsManager(..., verbose=False)` — **`False` is the default.**
71
+
72
+ - `verbose=False`: only essential output is printed — operation start/result, successes, failures, and warnings that affect state (e.g. overwriting a version, falling back to a new "latest" version).
73
+ - `verbose=True`: every internal step is also printed (`[SDK_DEBUG]`/`[SDK_INFO]` narration), including the full raw metadata for transparency.
74
+
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.
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
+
116
+ ## Access Control (ACL)
117
+
118
+ ACL is **off by default** and fully optional. Toggle it with one env var:
119
+
120
+ ```env
121
+ ACL_ENABLED="true"
122
+ ACL_API_HOST="http://your-acl-server:30011"
123
+ ```
124
+
125
+ - `ACL_ENABLED=false` (default): identical behavior to a build with no ACL at all — every call bypasses permission checks.
126
+ - `ACL_ENABLED=true`: `add_model`, `get_model`, `get_model_info`, `delete_model`, `set_model_public`, `set_latest_version`, and `list_models` all enforce permission checks against the ACL service, and `add_model`/`delete_model`/`set_model_public` keep the ACL service's record of each model in sync.
127
+
128
+ ### Sharing a model
129
+
130
+ ```python
131
+ manager.share_model(model_name="demo_model_local", target_user="colleague@example.com", access_level="write")
132
+ # access_level: "read" | "write" | "delete" | "admin" | "owner"
133
+ ```
134
+
135
+ Requires `ACL_ENABLED=true` and that you have `admin`-level access on the model. **Note:** the underlying ACL endpoint contract for sharing was inferred from the existing ACL client conventions and hasn't been verified against the live ACL service — confirm the request shape before relying on it in production.
136
+
137
+ ---
138
+
139
+ ## Cross-User Visibility (Public Models)
140
+
141
+ Every model lives in its owner's own ClearML project (`project_<owner_username>`) — that part hasn't changed. What changed is **how `list_models`/`get_model_id_by_name`/`get_model_info`/`get_model` look things up**:
142
+
143
+ - **`list_models()`** (default `include_public=True`): returns your own models (private + public) **plus** every other user's model that is marked `public=True`. Other users' **private** models are never returned, under any setting. Each entry's `owner` field is resolved from the model's actual project, so models from other users show their real owner, not yours.
144
+ - Pass `include_public=False` to get the exact pre-existing behavior: your own project only, nothing from other users.
145
+ - `public_only=True` / `private_only=True` now filter across this combined set too.
146
+ - **`get_model_id_by_name(name, owner=None)`**, **`get_model(model_name, local_dest, version=None, owner=None)`**, **`get_model_info(identifier, owner=None)`**: all gained an optional `owner` argument. Omit it and behavior is identical to before (your own project only). Pass `owner="some_username"` to resolve a model by name from *that* user's project — useful once `list_models()` has shown you a public model that isn't yours.
147
+ - **`get_model_link`** now resolves the link using the model's *actual* owning project (previously it always used your own project ID, which silently produced a wrong link for any model you didn't own — this never came up before because nothing could return another user's model anyway).
148
+ - Write operations (`add_model`, `delete_model`, `set_model_public`, `set_latest_version`) are **unchanged** — they still only ever operate within your own project by name. To act on a model you don't own (e.g. as an ACL-granted admin), pass its `model_id` directly (already supported) rather than its name.
149
+
150
+ **Performance note:** when `include_public=True` (the default), `list_models()` makes one extra unscoped ClearML query (`models.get_all` with no project filter) to find other users' public models — on a very large ClearML deployment this returns every model company-wide before filtering down to public ones. If that ever becomes slow, set `include_public=False` and only fetch other users' models on demand via `owner=`.
151
+
152
+ **Backward compatibility:** `get_model_id_by_name`, `get_model`, `get_model_info` keep their exact old signatures plus one new optional trailing argument — no existing call site breaks. `list_models()`'s default *output* now includes more rows than before (other users' public models) since that was the explicit point of this change; pass `include_public=False` if your code depends on the exact old row set.
153
+
154
+ ---
155
+
156
+ ## Example Usage
157
+
158
+ This example demonstrates how to use the new features: **Auto-Versioning**, **Categories**, **Tags**, and **Web Links**.
159
+
160
+ ```python
161
+ import os
162
+ from dotenv import load_dotenv
163
+ from aipmodel.model_registry import MLOpsManager
164
+
165
+ load_dotenv()
166
+
167
+ # --- STEP 1: Initialize MLOps Manager ---
168
+ print("\n[STEP 1] Initialize MLOps Manager")
169
+ # verbose=True enables detailed logging and transparency mode.
170
+ manager = MLOpsManager(
171
+ user_token=os.getenv("USER_TOKEN"),
172
+ CLEARML_API_HOST=os.getenv("CLEARML_API_HOST"),
173
+ CLEARML_WEB_HOST=os.getenv("CLEARML_WEB_HOST"), # For generating UI links
174
+ CEPH_ENDPOINT_URL=os.getenv("CEPH_ENDPOINT_URL"),
175
+ USER_MANAGEMENT_API=os.getenv("USER_MANAGEMENT_API"),
176
+ verbose=True
177
+ )
178
+
179
+ # --- STEP 2: Add Private Local Model (Explicit Version) ---
180
+ print("\n[STEP 2] Add Private Model (Explicit Version + Metadata)")
181
+ local_model_id = manager.add_model(
182
+ source_type="local",
183
+ model_name="demo_model_local",
184
+ version="v1.0",
185
+ source_path="./my_models/v1",
186
+ code_path="./my_models/train.py",
187
+ category="Text Generation",
188
+ tags=["production", "base-model"],
189
+ public=False # Default is False (Private Bucket)
190
+ )
191
+ print(f" -> Registered ID: {local_model_id}")
192
+
193
+ # --- STEP 3: Add Auto-Versioned Model ---
194
+ print("\n[STEP 3] Add Local Model (Auto-Versioning)")
195
+ # Passing version=None triggers auto-generation (e.g., _v2, _v3 based on history)
196
+ auto_ver_id = manager.add_model(
197
+ source_type="local",
198
+ model_name="demo_model_local",
199
+ version=None,
200
+ source_path="./my_models/v2_finetuned",
201
+ tags=["experimental", "auto-versioned"]
202
+ )
203
+ print(f" -> Registered ID: {auto_ver_id}")
204
+
205
+ # --- STEP 4: Add Public Model (Direct to Public Bucket) ---
206
+ print("\n[STEP 4] Add Public Model")
207
+ # Setting public=True uploads directly to the shared/public bucket
208
+ public_model_id = manager.add_model(
209
+ source_type="local",
210
+ model_name="demo_model_public",
211
+ version="v1.0",
212
+ source_path="./my_models/public_release",
213
+ tags=["community", "public"],
214
+ public=True
215
+ )
216
+ print(f" -> Registered ID: {public_model_id}")
217
+
218
+ # --- STEP 5: Add Hugging Face Model ---
219
+ print("\n[STEP 5] Add Model from Hugging Face")
220
+ hf_model_id = manager.add_model(
221
+ source_type="hf",
222
+ model_name="demo_model_hf",
223
+ version="v1",
224
+ source_path="facebook/wav2vec2-base-960h",
225
+ category="Image Classification",
226
+ tags=["imported", "audio"]
227
+ )
228
+ print(f" -> Registered ID: {hf_model_id}")
229
+
230
+ # --- STEP 6: Get Detailed Info ---
231
+ print("\n[STEP 6] Get Detailed Model Info")
232
+ # Displays ID, Public status, Total Versions, Size, and ClearML Web Link
233
+ manager.get_model_info("demo_model_local")
234
+
235
+ # --- STEP 7: List All Models ---
236
+ print("\n[STEP 7] List All Models")
237
+ # Check the 'Public' column (T/F) and 'Latest' version column.
238
+ # By default this also includes OTHER users' public models (each tagged with
239
+ # its real 'owner') - pass include_public=False to see only your own, like before.
240
+ manager.list_models(verbose=False)
241
+ manager.list_models(category="Text Generation") # filter by category
242
+ manager.list_models(tag="production") # filter by tag
243
+ manager.list_models(public_only=True) # only public models (yours + others')
244
+ manager.list_models(include_public=False) # only your own project, old behavior
245
+
246
+ # --- STEP 7B: Look Up a Model You Don't Own ---
247
+ print("\n[STEP 7B] Inspect/Download a Public Model From Another User")
248
+ # Suppose list_models() above showed a public model named "shared_model" owned by "colleague_username"
249
+ # other_info = manager.get_model_info("shared_model", owner="colleague_username")
250
+ # manager.get_model(model_name="shared_model", local_dest="./downloads/shared", owner="colleague_username")
251
+
252
+ # --- STEP 8: Management & Visibility Operations ---
253
+ print("\n[STEP 8] Management Operations")
254
+
255
+ # A. Change Visibility (Private -> Public)
256
+ # Moves files physically from User Bucket to Public Bucket
257
+ manager.set_model_public("demo_model_local", public=True)
258
+
259
+ # B. Get Latest Version Name
260
+ # Returns the string tag of the latest version (e.g., "_v2")
261
+ latest_ver = manager.get_latest_version("demo_model_local")
262
+ print(f"Latest version is: {latest_ver}")
263
+
264
+ # C. Set Manual Latest Version
265
+ # Points the 'latest' tag to a specific version
266
+ manager.set_latest_version(model_name="demo_model_local", version="v1.0")
267
+
268
+ # --- STEP 9: Download Operations ---
269
+ print("\n[STEP 9] Download Model")
270
+
271
+ # Download latest version
272
+ # NOTE: The SDK automatically appends the Model ID to the path.
273
+ # Destination will be: ./downloads/latest/<MODEL_ID>/<VERSION_NAME>/
274
+ manager.get_model(
275
+ model_name="demo_model_local",
276
+ local_dest="./downloads/latest"
277
+ )
278
+
279
+ # --- STEP 10: Deletion ---
280
+ print("\n[STEP 10] Deletion")
281
+
282
+ # Delete a specific version (Removes files + Metadata entry)
283
+ manager.delete_model(model_name="demo_model_local", version="v1.0")
284
+
285
+ # Delete the entire model (Removes all versions + ClearML ID)
286
+ manager.delete_model(model_name="demo_model_hf")
287
+
288
+ ```
289
+
290
+ ---
291
+
292
+ ## API Reference
293
+
294
+ ### `MLOpsManager` Methods
295
+
296
+ | Function | Input Arguments | Description |
297
+ | --- | --- | --- |
298
+ | **`__init__`** | `user_token`, `CLEARML_API_HOST`, `CLEARML_WEB_HOST`, `CEPH_ENDPOINT_URL`, `USER_MANAGEMENT_API`, `verbose`, `ACL_API_HOST`, `ACL_ENABLED` | Initializes connections. Performs fail-fast health checks. `ACL_*` args (or env vars) toggle ACL enforcement (default: disabled). |
299
+ | **`add_model`** | `source_type`, `model_name`, `version`, `source_path`, `code_path`, `category`, `tags`, `external_ceph_*`, `model_folder_name` | Uploads a model. `version`: if `None`, generates `_v{n}` automatically. `category`: must be one of `get_allowed_categories()` (defaults to "Text Generation"/"Image Classification", overridable via `MODEL_ALLOWED_CATEGORIES`). `tags`: list of strings. `model_folder_name`: optional explicit folder name for the uploaded model; if omitted, it's derived automatically as before (backward compatible). |
300
+ | **`get_model`** | `model_name`, `local_dest`, `version`, `owner` | Downloads model files. If `version` is omitted, downloads the version marked as 'latest'. `owner` (optional): resolve `model_name` from another user's project, e.g. to download a public model that isn't yours. |
301
+ | **`get_model_info`** | `identifier` (Name or ID), `owner` | Prints detailed info including **Owner**, **Web Link**, Tags, Category, and Version History. `owner` (optional): resolve a name-based `identifier` from another user's project. |
302
+ | **`get_allowed_categories`** | *(none)* | Returns the list of valid `category` values for `add_model`. |
303
+ | **`list_models`** | `verbose`, `category`, `tag`, `public_only`, `private_only`, `include_public` | Lists your own models **plus** (by default, `include_public=True`) every other user's `public=True` model — never another user's private model. Each entry includes a real `owner` field. `verbose=True` shows full JSON metadata. `category`/`tag` filter by exact match; `public_only`/`private_only` filter by visibility, applied across the combined set. `include_public=False` restores the pre-existing own-project-only behavior. When ACL is enabled, results are additionally filtered to models you can access (plus public ones). See "Cross-User Visibility" above. |
304
+ | **`get_model_id_by_name`** | `name`, `owner` | Resolves a model name to its ClearML ID. `owner` (optional): look it up in another user's project instead of your own. |
305
+ | **`get_model_name_by_id`** | `model_id` | Resolves a ClearML ID back to its model name (works for any model, regardless of owner). |
306
+ | **`get_latest_version`** | `model_identifier` (Name or ID) | Returns the version tag currently marked 'latest'. |
307
+ | **`set_latest_version`** | `model_name`, `version` | Updates the 'latest' pointer to a specific version without modifying files. |
308
+ | **`set_model_public`** | `model_name`, `public` | Moves a model's files between the private and public buckets (transactional, parallel server-side copy). |
309
+ | **`share_model`** | `model_name`, `target_user`, `access_level` | Grants another user access to a model. Requires `ACL_ENABLED=true`. |
310
+ | **`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. |
311
+
312
+ ---
313
+
314
+ ## HTTP API
315
+
316
+ The SDK ships a companion **HTTP API** service (`api/model-registry-api`) that exposes every SDK operation over HTTP for non-Python clients.
317
+
318
+ Full documentation for that service — endpoints, auth, request/response shapes, ACL toggle, curl examples — lives in its own repo:
319
+
320
+ > **`api/model-registry-api/README.md`**
321
+
322
+ The short version: run `uvicorn main:app --host 0.0.0.0 --port 8080`, pass `Authorization: Bearer <token>` on every call, and toggle ACL with `ACL_ENABLED=true/false` in the API service's `.env`.
323
+
324
+ ---
325
+
326
+ ## Constraints & Validations
327
+
328
+ 1. **Model Names:** Cannot start with `tmp_`, `_tmp_`, or `#`.
329
+ 2. **Version Names:**
330
+ * Manual versions cannot start with `_` (reserved for auto-generated versions).
331
+ * Auto-generated versions follow the pattern `_v1`, `_v2`, etc.
332
+
333
+
334
+ 3. **Categories:** Only categories returned by `manager.get_allowed_categories()` are allowed. Defaults to:
335
+ * `Text Generation`
336
+ * `Image Classification`
337
+
338
+ Override the full list via `MODEL_ALLOWED_CATEGORIES` in `.env` (comma-separated).
339
+
340
+
341
+
342
+ ---
343
+
344
+ ## Testing
345
+
346
+ - `test_model_registry.py` / `test_ceph_s3_manager.py`: automated unit tests (mock the ClearML/S3 boundary, no live infra needed). Run with:
347
+ ```bash
348
+ pip install pytest
349
+ pytest test_model_registry.py test_ceph_s3_manager.py -v
350
+ ```
351
+ - `test.py`: a manual/live-integration demo script covering every scenario above against **real** ClearML/Ceph/ACL services — everything is commented out by default; uncomment only what you intend to run.
352
+
353
+ ---
354
+
355
+ ## Admin Instructions: Auto-Publishing to PyPI
356
+
357
+ This SDK uses a GitHub Actions workflow (`.github/workflows/publish.yaml`) for automatic versioning and PyPI publishing.
358
+
359
+ ### Trigger Conditions
360
+
361
+ * Push to `main` branch.
362
+ * Commit message contains `pipy commit -push`.
363
+ * GitHub variable `PUBLISH_TO_PYPI` is set to `true`.
364
+
365
+ ### Commit Message Format
366
+
367
+ | Keyword | Action |
368
+ | --- | --- |
369
+ | `pipy commit -push major` | Increments **Major** version (e.g., 1.0.0 -> 2.0.0) |
370
+ | `pipy commit -push minor` | Increments **Minor** version (e.g., 0.1.0 -> 0.2.0) |
371
+ | `pipy commit -push` | Increments **Patch** version (e.g., 0.1.1 -> 0.1.2) |