modelstudio-sdk 0.0.0.dev0__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,140 @@
1
+ """Split sub-resource with per-split operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+ from uuid import UUID
7
+
8
+ from modelstudio.models.annotations import AnnotationModel
9
+ from modelstudio.models.exports import ExportCocoModel, SegmentationExportModel
10
+ from modelstudio.models.images import DatasetImageModel
11
+ from modelstudio.models.imports import (
12
+ ImportQueuedModel,
13
+ PreImportValidationModel,
14
+ ValidationStartedModel,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from modelstudio._http import HttpTransport
19
+
20
+
21
+ class Split:
22
+ """Operations on a specific split within a dataset.
23
+
24
+ Access via ``client.dataset("ds-id").split("split-id")``.
25
+ """
26
+
27
+ def __init__(self, transport: HttpTransport, dataset_id: str, split_id: str) -> None:
28
+ self._t = transport
29
+ self._ds = dataset_id
30
+ self._sid = split_id
31
+ self._base = f"/api/v1/datasets/{dataset_id}/splits/{split_id}"
32
+
33
+ # ── CRUD ──────────────────────────────────────────────────────────
34
+
35
+ def patch_tao_id(self, tao_id: str | UUID) -> None:
36
+ """Update the TAO ID for this split."""
37
+ self._t.patch(self._base, json={"tao_id": str(tao_id)})
38
+
39
+ # ── Images ────────────────────────────────────────────────────────
40
+
41
+ def add_images(self, image_ids: list[str | UUID]) -> list[DatasetImageModel]:
42
+ """Add images to this split."""
43
+ data = self._t.post(
44
+ f"{self._base}/images",
45
+ json={"images": [{"image_id": str(i)} for i in image_ids]},
46
+ )
47
+ return [DatasetImageModel.model_validate(item) for item in data]
48
+
49
+ def remove_image(self, dataset_image_id: str | UUID) -> None:
50
+ """Remove an image from this split."""
51
+ self._t.delete(f"{self._base}/images/{dataset_image_id}")
52
+
53
+ def move_image(self, image_id: str | UUID, new_split_id: str | UUID) -> None:
54
+ """Move an image to another split."""
55
+ self._t.patch(
56
+ f"{self._base}/images/{image_id}/move",
57
+ json={"new_split_id": str(new_split_id)},
58
+ )
59
+
60
+ def list_images(self) -> list[DatasetImageModel]:
61
+ """List all images in this split."""
62
+ data = self._t.get(f"{self._base}/images")
63
+ return [DatasetImageModel.model_validate(item) for item in data]
64
+
65
+ # ── Annotations ─────────────────────────────────────────────────────
66
+
67
+ def list_annotations(self) -> list[AnnotationModel]:
68
+ """List all annotations in this split."""
69
+ data = self._t.get(f"{self._base}/annotations")
70
+ return [AnnotationModel.model_validate(item) for item in data]
71
+
72
+ # ── DataFrame helpers ───────────────────────────────────────────────
73
+
74
+ def images_df(self) -> Any:
75
+ """Return images as a pandas DataFrame."""
76
+ from modelstudio._pandas import to_dataframe
77
+
78
+ return to_dataframe(self.list_images())
79
+
80
+ def annotations_df(self) -> Any:
81
+ """Return annotations as a pandas DataFrame."""
82
+ from modelstudio._pandas import to_dataframe
83
+
84
+ return to_dataframe(self.list_annotations())
85
+
86
+ # ── Import ────────────────────────────────────────────────────────
87
+
88
+ def import_from_source(
89
+ self, source: str, type: str, config: dict[str, Any]
90
+ ) -> ImportQueuedModel:
91
+ """Start an import from a data source adapter (s3, labelbox, etc).
92
+
93
+ Args:
94
+ source: The data source (e.g. "s3", "labelbox").
95
+ type: The import type (e.g. "coco", "segmentation").
96
+ config: Source-specific configuration.
97
+ """
98
+ data = self._t.post_accepted(f"{self._base}/import/{source}/{type}", json=config)
99
+ return ImportQueuedModel.model_validate(data)
100
+
101
+ def import_segmentation(self, config: dict[str, Any]) -> SegmentationExportModel:
102
+ """Import segmentation masks from S3."""
103
+ data = self._t.post(f"{self._base}/import/s3/segmentation", json=config)
104
+ return SegmentationExportModel.model_validate(data)
105
+
106
+ def validate_import(self, config: dict[str, Any]) -> ValidationStartedModel:
107
+ """Start pre-import validation."""
108
+ data = self._t.post_accepted(f"{self._base}/import/s3/validate", json=config)
109
+ return ValidationStartedModel.model_validate(data)
110
+
111
+ def validation_status(self, validation_id: str) -> PreImportValidationModel:
112
+ """Check validation status."""
113
+ data = self._t.get(f"{self._base}/import/validate/{validation_id}")
114
+ return PreImportValidationModel.model_validate(data)
115
+
116
+ # ── Export ─────────────────────────────────────────────────────────
117
+
118
+ def export_coco(
119
+ self,
120
+ reindex_images: bool = True,
121
+ reindex_annotations: bool = True,
122
+ reindex_categories: bool = True,
123
+ starting_id: int = 1,
124
+ ) -> ExportCocoModel:
125
+ """Export this split as COCO JSON."""
126
+ data = self._t.post(
127
+ f"{self._base}/export/coco",
128
+ json={
129
+ "reindex_images": reindex_images,
130
+ "reindex_annotations": reindex_annotations,
131
+ "reindex_categories": reindex_categories,
132
+ "starting_id": starting_id,
133
+ },
134
+ )
135
+ return ExportCocoModel.model_validate(data)
136
+
137
+ def export_segmentation(self) -> SegmentationExportModel:
138
+ """Export segmentation masks for this split."""
139
+ data = self._t.post(f"{self._base}/export/segmentation")
140
+ return SegmentationExportModel.model_validate(data)
@@ -0,0 +1,513 @@
1
+ Metadata-Version: 2.4
2
+ Name: modelstudio-sdk
3
+ Version: 0.0.0.dev0
4
+ Summary: Python SDK for the Model Studio REST API
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: httpx<1.0,>=0.25.0
9
+ Requires-Dist: pydantic<3.0,>=2.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: mypy>=1.8; extra == 'dev'
12
+ Requires-Dist: pandas>=1.5.0; extra == 'dev'
13
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
14
+ Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
15
+ Requires-Dist: pytest>=7.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
17
+ Provides-Extra: pandas
18
+ Requires-Dist: pandas>=1.5.0; extra == 'pandas'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Model Studio Python SDK
22
+
23
+ Python SDK for the Model Studio REST API. Provides typed access to dataset management, annotation tooling, metrics, and ML workflow operations.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ # From wheel (in notebook containers, pre-installed)
29
+ pip install modelstudio-sdk
30
+
31
+ # Development install
32
+ git clone https://gitlab.com/orbitalinsight/elements/model-studio/modelstudio-sdk.git
33
+ cd modelstudio-sdk
34
+ pip install -e ".[dev]"
35
+
36
+ # With pandas support
37
+ pip install "modelstudio-sdk[pandas]"
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from modelstudio import ModelStudioClient
44
+
45
+ # Auto-configured inside notebooks (reads env vars)
46
+ client = ModelStudioClient.from_env()
47
+
48
+ # Or explicit
49
+ client = ModelStudioClient(
50
+ base_url="http://localhost:8081",
51
+ jwt_token="eyJhbG...",
52
+ )
53
+
54
+ # List datasets
55
+ for ds in client.datasets.list():
56
+ print(f"{ds.name} ({ds.dataset_type})")
57
+ ```
58
+
59
+ ## Environment Variables
60
+
61
+ | Variable | Required | Description |
62
+ |----------|----------|-------------|
63
+ | `MODEL_STUDIO_API_URL` | Yes | API base URL |
64
+ | `MODEL_STUDIO_JWT` | No | JWT authentication token |
65
+ | `MODEL_STUDIO_ORG` | No | Organization name |
66
+
67
+ ## Usage Examples
68
+
69
+ ### Dataset Operations
70
+
71
+ ```python
72
+ ds = client.dataset("dataset-uuid")
73
+
74
+ # Get overview statistics
75
+ overview = ds.overview()
76
+ print(f"Images: {overview.summary.total_images}")
77
+ print(f"Annotations: {overview.summary.total_annotations}")
78
+
79
+ # List splits
80
+ for split in ds.splits():
81
+ print(f"{split.name} ({split.split_type})")
82
+
83
+ # List categories
84
+ cats = ds.categories()
85
+ for cat in cats.categories:
86
+ print(f"{cat.name}: {cat.annotation_count} annotations")
87
+ ```
88
+
89
+ ### Split Operations
90
+
91
+ ```python
92
+ # Create algorithmic splits
93
+ ds.create_algorithmic_splits(
94
+ splits={"train": 0.7, "val": 0.15, "test": 0.15},
95
+ seed=42,
96
+ )
97
+
98
+ # Smart redistribution
99
+ result = ds.smart_redistribute(
100
+ ratios={"train": 0.8, "val": 0.2},
101
+ prevent_tile_leakage=True,
102
+ )
103
+
104
+ # Check for data leakage
105
+ leakage = ds.check_leakage()
106
+ if leakage.has_leakage:
107
+ print(f"Found {leakage.leakage_count} leaked images")
108
+ ```
109
+
110
+ ### Category Management
111
+
112
+ ```python
113
+ # Merge categories
114
+ ds.merge_categories(
115
+ source_categories=[1, 2, 3],
116
+ target_category="vehicle",
117
+ )
118
+
119
+ # Rename a category
120
+ ds.rename_category(category_id=5, new_name="truck")
121
+
122
+ # Remove a category
123
+ ds.remove_category(category_id=10)
124
+
125
+ # Consolidate labels
126
+ ds.consolidate_labels({"car": "vehicle", "van": "vehicle"})
127
+ ```
128
+
129
+ ### Working with Split Data
130
+
131
+ ```python
132
+ split = ds.split("split-uuid")
133
+
134
+ # List images
135
+ images = split.list_images()
136
+
137
+ # List annotations
138
+ annotations = split.list_annotations()
139
+
140
+ # Import from S3
141
+ queued = split.import_from_source("s3", {
142
+ "connection_id": "conn-uuid",
143
+ "bucket": "my-bucket",
144
+ "prefix": "datasets/coco/",
145
+ })
146
+
147
+ # Wait for import to complete
148
+ poller = split.import_poller(interval=5.0)
149
+ result = poller.wait(callback=lambda r: print(f"Progress: {r.get('progress')}%"))
150
+ ```
151
+
152
+ ### Cloning & Async Operations
153
+
154
+ ```python
155
+ # Clone a dataset
156
+ cloned = ds.clone(name="My Clone")
157
+
158
+ # Poll until complete
159
+ poller = ds.clone_poller(interval=2.0, max_wait=300.0)
160
+ result = poller.wait()
161
+ print(f"Clone status: {result['clone_status']}")
162
+ ```
163
+
164
+ ### Validation & Quality
165
+
166
+ ```python
167
+ # Validate dataset
168
+ result = ds.validate()
169
+ print(f"Valid: {result.valid}")
170
+
171
+ # Check for duplicates
172
+ dupes = ds.check_duplicates()
173
+ if dupes.has_duplicates:
174
+ print(f"{dupes.total_duplicate_images} duplicate images found")
175
+
176
+ # Detect temporal conflicts
177
+ conflicts = ds.detect_temporal_conflicts()
178
+ ```
179
+
180
+ ### Export
181
+
182
+ ```python
183
+ # Export as COCO JSON
184
+ coco = ds.export_coco()
185
+ print(f"Exported {coco.image_count} images, {coco.annotation_count} annotations")
186
+
187
+ # Per-split export
188
+ split_coco = split.export_coco()
189
+ ```
190
+
191
+ ### Filtering
192
+
193
+ ```python
194
+ from modelstudio.models.filters import DatasetFilterRequest, CategoryFilter
195
+
196
+ # Filter to specific categories
197
+ result = ds.filter(DatasetFilterRequest(
198
+ category_filter=CategoryFilter(keep_categories=["car", "truck"]),
199
+ new_dataset_name="filtered-cars",
200
+ ))
201
+ print(f"New dataset: {result.new_dataset_id}")
202
+ ```
203
+
204
+ ### Few-Shot & Oversampling
205
+
206
+ ```python
207
+ from modelstudio.models.few_shot import FewShotRequest
208
+ from modelstudio.models.oversample import OversampleRequest
209
+
210
+ # Create few-shot dataset
211
+ result = ds.few_shot_create(FewShotRequest(
212
+ num_images=100,
213
+ method="MOST_CLASSES",
214
+ new_dataset_name="few-shot-100",
215
+ ))
216
+
217
+ # Oversample minority classes
218
+ result = ds.oversample_execute(OversampleRequest(
219
+ target_ratio=0.5,
220
+ strategy="PREFER_ANNOTATED",
221
+ ))
222
+ ```
223
+
224
+ ### Dataset Merge
225
+
226
+ ```python
227
+ from modelstudio.models.merge import MergeDatasetRequest
228
+
229
+ # Analyze conflicts before merging
230
+ analysis = client.datasets.merge_analyze(["ds-1", "ds-2"])
231
+
232
+ # Merge datasets
233
+ result = client.datasets.merge(MergeDatasetRequest(
234
+ source_dataset_ids=["ds-1", "ds-2"],
235
+ target_name="merged-dataset",
236
+ ))
237
+ ```
238
+
239
+ ### Undo/Redo
240
+
241
+ ```python
242
+ # View history
243
+ history = ds.history()
244
+ for entry in history.changes:
245
+ print(f"{entry.operation_type}: {entry.short_description}")
246
+
247
+ # Undo
248
+ result = ds.undo()
249
+ print(f"Undid: {result.description}")
250
+ ```
251
+
252
+ ### DataFrame Integration
253
+
254
+ ```python
255
+ # Requires: pip install "modelstudio-sdk[pandas]"
256
+
257
+ # Images as DataFrame
258
+ df = split.images_df()
259
+
260
+ # Annotations as DataFrame
261
+ df = split.annotations_df()
262
+
263
+ # Categories as DataFrame
264
+ df = ds.categories_df()
265
+
266
+ # Class distribution as DataFrame
267
+ df = ds.class_distribution_df()
268
+ ```
269
+
270
+ ## Error Handling
271
+
272
+ ```python
273
+ from modelstudio.exceptions import NotFoundError, ConflictError, BadRequestError
274
+
275
+ try:
276
+ ds = client.dataset("nonexistent")
277
+ ds.overview()
278
+ except NotFoundError as e:
279
+ print(f"Dataset not found: {e.message}")
280
+ except ConflictError as e:
281
+ print(f"Operation conflict: {e.message}")
282
+ except BadRequestError as e:
283
+ print(f"Invalid request: {e.message}")
284
+ ```
285
+
286
+ ## Architecture
287
+
288
+ ### Related Repositories
289
+
290
+ | Repo | Purpose |
291
+ |------|---------|
292
+ | [`model-studio-sdk`](https://gitlab.com/orbitalinsight/elements/model-studio/modelstudio-sdk) | This repo — Python SDK + Jupyter Server Docker |
293
+ | [`frontend`](https://gitlab.com/orbitalinsight/frontend-2.0) | Model Studio React frontend (custom notebook UI lives here) |
294
+ | [`model-studio-api`](https://gitlab.com/orbitalinsight/elements/model-studio/model-studio-api) | Backend REST API the SDK wraps |
295
+ | [`model-studio-notebooks`](https://gitlab.com/orbitalinsight/elements/model-studio/model-studio-notebooks) | JupyterHub + KubeSpawner Helm chart (production multi-user) |
296
+ | [`model-studio-agent`](https://gitlab.com/orbitalinsight/elements/model-studio/model-studio-agent) | Agent chat backend |
297
+ | [`keycloak-config`](https://gitlab.com/orbitalinsight/elements/keycloak-config) | Keycloak realm/client configuration |
298
+
299
+ ### Local Development Architecture
300
+
301
+ ```
302
+ ┌─────────────────────────────────────────────────────────────┐
303
+ │ Browser (http://localhost:5173) │
304
+ │ │
305
+ │ ┌───────────────────────────────────────────────────────┐ │
306
+ │ │ Model Studio Frontend (Vite) │ │
307
+ │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │
308
+ │ │ │ Dataset Pages │ │ Notebook │ │ Agent Chat │ │ │
309
+ │ │ │ │ │ Panel │ │ Panel │ │ │
310
+ │ │ └──────────────┘ └──────┬───────┘ └─────┬──────┘ │ │
311
+ │ └───────────────────────────┼────────────────┼──────────┘ │
312
+ │ │ │ │
313
+ │ Vite Dev Server Proxies: │ │ │
314
+ │ /jupyter/* ─────────────────┘ │ │
315
+ │ /agent/* ────────────────────────────────────┘ │
316
+ └──────────────────────────────┼────────────────┼─────────────┘
317
+ │ │
318
+ ┌────────────────┘ │
319
+ ▼ ▼
320
+ ┌──────────────────────────┐ ┌──────────────────────────┐
321
+ │ Jupyter Server (Docker) │ │ Agent API │
322
+ │ localhost:8889 │ │ localhost:8080 │
323
+ │ │ │ (model-studio-agent) │
324
+ │ ┌────────────────────┐ │ └──────────────────────────┘
325
+ │ │ Python 3.10 Kernel │ │
326
+ │ │ + Model Studio SDK │ │ ▲
327
+ │ └────────┬───────────┘ │ │
328
+ └───────────┼──────────────┘ │
329
+ │ │
330
+ ▼ │
331
+ ┌──────────────────────────────────────────────────────────┐
332
+ │ Model Studio API │
333
+ │ https://model-studio-api.model-studio.privateer-dev.com │
334
+ │ (model-studio-api repo) │
335
+ └──────────────────────────────────────────────────────────┘
336
+ ```
337
+
338
+ **Data flow**: User writes Python in the Notebook Panel → CodeMirror editor sends code via WebSocket to Jupyter kernel → kernel executes using the SDK → SDK calls Model Studio API → results render in the panel.
339
+
340
+ ### Production Architecture
341
+
342
+ ```
343
+ ┌──────────────────────────────────────────────────────────┐
344
+ │ Browser │
345
+ │ Model Studio Frontend (static build on CDN/Nginx) │
346
+ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
347
+ │ │ Dataset Pages │ │ Notebook │ │ Agent Chat │ │
348
+ │ │ │ │ Panel │ │ Panel │ │
349
+ │ └──────────────┘ └──────┬───────┘ └────────────────┘ │
350
+ └────────────────────────────┼─────────────────────────────┘
351
+
352
+
353
+ ┌──────────────────────────────────────────────────────────┐
354
+ │ JupyterHub (model-studio-notebooks repo) │
355
+ │ - Keycloak OIDC auth │
356
+ │ - KubeSpawner → per-user Jupyter Server pods │
357
+ │ - Helm chart for K8s deployment │
358
+ │ │
359
+ │ ┌──────────────────────────────────────────────────┐ │
360
+ │ │ Per-User Jupyter Server (K8s Pod) │ │
361
+ │ │ ┌────────────────────┐ │ │
362
+ │ │ │ Python 3.10 Kernel │ │ │
363
+ │ │ │ + Model Studio SDK │ │ │
364
+ │ │ └────────┬───────────┘ │ │
365
+ │ └───────────┼──────────────────────────────────────┘ │
366
+ └──────────────┼───────────────────────────────────────────┘
367
+
368
+
369
+ ┌──────────────────────────────────────────────────────────┐
370
+ │ Model Studio API (K8s service) │
371
+ └──────────────────────────────────────────────────────────┘
372
+ ```
373
+
374
+ **Key difference**: In production, JupyterHub (from `model-studio-notebooks` repo) manages multi-user server lifecycle, auth, and resource limits. The custom notebook UI replaces JupyterLab's frontend but JupyterHub still manages server spawning.
375
+
376
+ ---
377
+
378
+ ## Development
379
+
380
+ ### Prerequisites
381
+
382
+ - [Miniconda](https://docs.conda.io/en/latest/miniconda.html) or Anaconda
383
+ - Docker + Docker Compose (for notebook server)
384
+ - `jq` and `curl` (for Keycloak token fetch)
385
+
386
+ ### Quick Start — SDK Development
387
+
388
+ ```bash
389
+ ./develop.sh --mode setup # Create conda env, install deps
390
+ ./develop.sh --mode test # Run unit tests
391
+ ./develop.sh --mode test-integration # Fetch Keycloak token + run integration tests
392
+ ./develop.sh --mode lint # Run ruff + mypy
393
+ ```
394
+
395
+ This creates a `model-studio-sdk` conda environment with Python 3.10 and installs the SDK in editable mode with all dev dependencies.
396
+
397
+ ### Quick Start — Custom Notebook UI
398
+
399
+ The notebook UI spans two repos: the Jupyter Server backend (this repo) and the React frontend (`frontend` repo).
400
+
401
+ **Terminal 1 — Start Jupyter Server:**
402
+
403
+ ```bash
404
+ # From this repo (model-studio-sdk)
405
+ ./develop.sh --mode notebook-server
406
+ ```
407
+
408
+ This prompts for Keycloak credentials, starts a headless Jupyter Server on port 8889 with the SDK pre-installed. The `src/` and `notebooks/` directories are volume-mounted for live reloading.
409
+
410
+ **Terminal 2 — Start frontend:**
411
+
412
+ ```bash
413
+ # From the frontend repo
414
+ cd ../frontend
415
+ source .go-privateer-dev.env
416
+ yarn build-consts
417
+
418
+ # First time only — install CodeMirror dependencies:
419
+ yarn add @codemirror/view @codemirror/state @codemirror/commands @codemirror/lang-python @codemirror/theme-one-dark
420
+
421
+ yarn start
422
+ ```
423
+
424
+ **Verify the setup:**
425
+
426
+ 1. Open http://localhost:5173
427
+ 2. Click the **Notebook** button in the AppBar (next to Agent)
428
+ 3. Type `print('hello')` in the cell and press **Shift+Enter**
429
+ 4. Output should appear below the cell
430
+
431
+ **How the proxy works**: The frontend's `vite.config.ts` proxies `/jupyter/*` requests to `localhost:8889` (the Docker Jupyter Server). This includes both REST API calls and WebSocket connections for kernel communication. No environment variables are needed — the proxy is configured in code.
432
+
433
+ ### JupyterLab Mode (Full Lab UI)
434
+
435
+ If you need the traditional JupyterLab interface (e.g., for notebook authoring):
436
+
437
+ ```bash
438
+ ./develop.sh --mode docker # Starts full JupyterLab on port 8888
439
+ # or
440
+ make docker
441
+ ```
442
+
443
+ Open http://localhost:8888 for the JupyterLab UI. Notebooks are in the `notebooks/` directory.
444
+
445
+ ### Integration Tests
446
+
447
+ Integration tests run against the live dev API and require a Keycloak JWT:
448
+
449
+ ```bash
450
+ ./develop.sh --mode test-integration
451
+ ```
452
+
453
+ This will prompt for your Keycloak credentials (same as your Model Studio login), fetch a JWT, and run the integration test suite.
454
+
455
+ To skip the auth prompt (e.g. if you already have a token):
456
+
457
+ ```bash
458
+ export MODEL_STUDIO_JWT="eyJhbG..."
459
+ ./develop.sh --mode test-integration --skip-auth
460
+ ```
461
+
462
+ Or set credentials as env vars to skip the interactive prompts:
463
+
464
+ ```bash
465
+ export KEYCLOAK_USERNAME="you"
466
+ export KEYCLOAK_PASSWORD="secret"
467
+ ./develop.sh --mode test-integration
468
+ ```
469
+
470
+ ### Make Targets
471
+
472
+ If you prefer to manage your own environment, the Makefile targets still work:
473
+
474
+ ```bash
475
+ make install # pip install -e ".[dev]"
476
+ make test # Unit tests with coverage
477
+ make test-unit # Unit tests only (no integration)
478
+ make lint # ruff + mypy
479
+ make build # Build wheel
480
+ make notebook-server # Start headless Jupyter Server (port 8889)
481
+ make notebook-server-down # Stop Jupyter Server
482
+ make docker # Start full JupyterLab (port 8888)
483
+ make docker-down # Stop JupyterLab
484
+ ```
485
+
486
+ For integration tests without `develop.sh`:
487
+
488
+ ```bash
489
+ eval "$(scripts/get-token.sh)" && make test-integration
490
+ ```
491
+
492
+ ### Docker Services
493
+
494
+ The `docker/docker-compose.yml` defines two services:
495
+
496
+ | Service | Port | Purpose |
497
+ |---------|------|---------|
498
+ | `notebook` | 8888 | Full JupyterLab with Lab UI (for notebook authoring) |
499
+ | `jupyter-server` | 8889 | Headless Jupyter Server (for custom notebook UI backend) |
500
+
501
+ Both use the same `Dockerfile.dev` base image (`jupyter/scipy-notebook:python-3.10`) with the SDK installed in editable mode. The `src/` directory is volume-mounted so SDK changes are picked up without rebuilding.
502
+
503
+ The `jupyter-server` service additionally configures:
504
+ - CORS headers for `http://localhost:5173` (Vite dev server)
505
+ - Disabled XSRF checks (local dev only — production uses JupyterHub auth)
506
+ - No authentication token (local dev only)
507
+
508
+ ## Requirements
509
+
510
+ - Python >= 3.10
511
+ - httpx >= 0.25.0
512
+ - pydantic >= 2.0
513
+ - pandas >= 1.5.0 (optional)
@@ -0,0 +1,32 @@
1
+ modelstudio/__init__.py,sha256=fUUWQWqRNr_Cr0m9ZcewVMnLAiQknH4N5dd09z_u5VE,520
2
+ modelstudio/_http.py,sha256=hh60cpb4P8bOV-Uth3PebvLzgA8r3ys2poWSjjUhg2M,3640
3
+ modelstudio/_pandas.py,sha256=hR1SrguXj24o0hCALscVS1TphT5u5dwIzXcxFj3ZcCg,803
4
+ modelstudio/_polling.py,sha256=v736GSpkqW_JURx5cYqexy_fTJpVXFV1_XtVGS0wgUM,1974
5
+ modelstudio/_version.py,sha256=VVFmX0b3faB1pUjdw28IIgO-vzaO6x4Y9leJ6S4ePT4,27
6
+ modelstudio/client.py,sha256=jKkt6YYSodIuDFPSbh2V7lIx4PElZQs6isWJYaOEpeQ,3245
7
+ modelstudio/exceptions.py,sha256=Fl35fjajCFIxq7CpBYcjrqoURz3YRU-93Ds65j1GnME,2563
8
+ modelstudio/models/__init__.py,sha256=2jmDWsD0IW54hSuXa3iUGwg-7wE8MiJWk0p-Tno9bpE,2023
9
+ modelstudio/models/annotations.py,sha256=3QU0fUIQhs3epj1zJmONfPV2yOP7EZYISOM051efWDQ,2002
10
+ modelstudio/models/categories.py,sha256=mnYa5zhScskfYklKve6kry1AxHycWeivEH70mZQVg-8,2938
11
+ modelstudio/models/common.py,sha256=qEyYSK9oTISvrWLZJpv5cs2rEL-q5u2E8raSSFrXF4w,409
12
+ modelstudio/models/datasets.py,sha256=07JWd-OwfgtYjgAENVZqbYDsFlN3Pn5moT95xcp45Eg,1504
13
+ modelstudio/models/deletion.py,sha256=T9QbSvEaK6FzvDjo69LJcZapmaaiPCClwUXL3XHZveg,659
14
+ modelstudio/models/exports.py,sha256=C_6qmts07B31gdZVApEmhHyD4XC916-XhCbms1oPLvk,897
15
+ modelstudio/models/few_shot.py,sha256=BSpvRNODt26LsMe2mZcQHj-46n9NqaPDqDAaEP5EkIY,1084
16
+ modelstudio/models/filters.py,sha256=ERE197Sla_lsHMEA5J9T0N_kp_6Ha_WStVQrfHLhHhM,2353
17
+ modelstudio/models/history.py,sha256=OpcYyAJMRdzlPvsiTNjiXNNRs07SMcjugdtvslWcWfc,1476
18
+ modelstudio/models/images.py,sha256=7UW0QY19j6Eio7TwNNCs2aFDnn5dchIcc5v4T6k35kk,2462
19
+ modelstudio/models/imports.py,sha256=VinLPsOCsmvmDFCkrKRw1aV6Wb42_PDRWlcnS-QIfQI,3934
20
+ modelstudio/models/media.py,sha256=htzbaxMZ3-h7OiuEykg8LIkwzBQNKdty_-OvwrORRts,843
21
+ modelstudio/models/merge.py,sha256=OTYqQRCnAg6-80i8R3mnOXuJSDGwwamIr3XtkzeY6SE,1372
22
+ modelstudio/models/metrics.py,sha256=PzyOfuChqf7uK6qfvMr9BQ1nos522AgrMSXzkd29fE4,5018
23
+ modelstudio/models/oversample.py,sha256=JwdfTXYmIs6XqzPt_ltRiY3nyPWCWwSD1tNy1dUFY2M,1044
24
+ modelstudio/models/splits.py,sha256=3LtY9JZq7YozJxBTH1sDNfCGaBIH3lrNWK0GpNNEOqg,3107
25
+ modelstudio/models/validation.py,sha256=ptfQj_dd4VJXASTs0cGj-DvVY0Vq_TWusaCVZce3yPA,2254
26
+ modelstudio/resources/__init__.py,sha256=iAw4TV6x37px7HavYD8_l4R-j4giAnO3MBRYWjEUwGI,50
27
+ modelstudio/resources/dataset.py,sha256=CPW5keep_2l_lT77XRqLr0XGmABT3xqz-TbiKuC_DLk,30748
28
+ modelstudio/resources/split.py,sha256=Rh8WVUoKG-kVkxcTB0Mi0FRjypWBrbhpmBBs_gW9mkU,6087
29
+ modelstudio_sdk-0.0.0.dev0.dist-info/METADATA,sha256=2rVFmj7mthxGcN6NIpyVBjNVefJ5d-5TlBhJ2BledMg,18781
30
+ modelstudio_sdk-0.0.0.dev0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
31
+ modelstudio_sdk-0.0.0.dev0.dist-info/licenses/LICENSE,sha256=WjcHhhxFV5wPppVsbAdR0m2aq5qxESD4J3k2EZiK0cw,1072
32
+ modelstudio_sdk-0.0.0.dev0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any