modelzone-sdk 0.2.0.dev0__tar.gz → 0.3.0__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 (29) hide show
  1. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/PKG-INFO +12 -9
  2. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/README.md +8 -8
  3. modelzone_sdk-0.3.0/modelzone/__init__.py +19 -0
  4. modelzone_sdk-0.3.0/modelzone/azureml/__init__.py +27 -0
  5. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/azureml/backend.py +41 -66
  6. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/cli.py +51 -80
  7. modelzone_sdk-0.3.0/modelzone/core/__init__.py +8 -0
  8. modelzone_sdk-0.3.0/modelzone/core/constants.py +9 -0
  9. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/core/model.py +12 -14
  10. modelzone_sdk-0.3.0/modelzone/core/model_artifact.py +63 -0
  11. modelzone_sdk-0.3.0/modelzone/core/predict_context.py +41 -0
  12. modelzone_sdk-0.3.0/modelzone/core/protocols.py +25 -0
  13. modelzone_sdk-0.2.0.dev0/modelzone/training/context.py → modelzone_sdk-0.3.0/modelzone/core/training_context.py +27 -27
  14. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/core/training_result.py +3 -7
  15. modelzone_sdk-0.3.0/modelzone/project_config.py +73 -0
  16. modelzone_sdk-0.3.0/modelzone/training/__init__.py +19 -0
  17. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/training/backend.py +26 -72
  18. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/training/db.py +9 -9
  19. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/pyproject.toml +13 -19
  20. modelzone_sdk-0.2.0.dev0/modelzone/__init__.py +0 -27
  21. modelzone_sdk-0.2.0.dev0/modelzone/azureml/__init__.py +0 -35
  22. modelzone_sdk-0.2.0.dev0/modelzone/core/__init__.py +0 -20
  23. modelzone_sdk-0.2.0.dev0/modelzone/core/constants.py +0 -6
  24. modelzone_sdk-0.2.0.dev0/modelzone/core/predict_context.py +0 -47
  25. modelzone_sdk-0.2.0.dev0/modelzone/core/record.py +0 -37
  26. modelzone_sdk-0.2.0.dev0/modelzone/core/trained_model.py +0 -86
  27. modelzone_sdk-0.2.0.dev0/modelzone/predict.py +0 -41
  28. modelzone_sdk-0.2.0.dev0/modelzone/training/__init__.py +0 -32
  29. {modelzone_sdk-0.2.0.dev0 → modelzone_sdk-0.3.0}/modelzone/training/delta_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelzone-sdk
3
- Version: 0.2.0.dev0
3
+ Version: 0.3.0
4
4
  Summary: Modelzone SDK – a slim model training and serving toolkit
5
5
  License-Expression: Apache-2.0
6
6
  Author: Team Enigma
@@ -14,8 +14,11 @@ Provides-Extra: azureml
14
14
  Provides-Extra: training
15
15
  Requires-Dist: azure-ai-ml ; extra == "azureml"
16
16
  Requires-Dist: azure-identity ; extra == "azureml"
17
+ Requires-Dist: deltalake (>=1) ; extra == "azureml"
17
18
  Requires-Dist: deltalake (>=1) ; extra == "training"
18
19
  Requires-Dist: mlflow (>=3) ; extra == "azureml"
20
+ Requires-Dist: pandas (<3) ; extra == "azureml"
21
+ Requires-Dist: pandas (<3) ; extra == "training"
19
22
  Description-Content-Type: text/markdown
20
23
 
21
24
  # Modelzone SDK
@@ -42,22 +45,22 @@ pip install modelzone-sdk[azureml]
42
45
 
43
46
  ## Quick start
44
47
 
45
- Define a model by subclassing `Model` and implementing `train` and `predict`:
48
+ Define a model by subclassing `ModelDefinition` and implementing `train` and `predict`:
46
49
 
47
50
  ```python
48
- from modelzone.core import Model, PredictContext, TrainedModel
51
+ from modelzone.core import ModelDefinition, PredictContext, ModelArtifact
49
52
  from modelzone.training import TrainingContext
50
53
 
51
54
 
52
- class MyModel(Model):
53
- def train(self, ctx: TrainingContext) -> TrainedModel:
55
+ class MyModel(ModelDefinition):
56
+ def train(self, ctx: TrainingContext) -> ModelArtifact:
54
57
  ctx.print("Training started")
55
58
 
56
59
  # … your training logic …
57
60
  fitted = train_something(seed=ctx.seed)
58
61
 
59
62
  ctx.log_metric("accuracy", 0.95)
60
- return TrainedModel(model=fitted, features=["feature_a", "feature_b"])
63
+ return ModelArtifact(model=fitted, features=["feature_a", "feature_b"])
61
64
 
62
65
  def predict(self, ctx: PredictContext):
63
66
  df = ctx.db.query("input_table", ctx.time_interval)
@@ -91,10 +94,10 @@ result = backend.run(MyModel(), seed=42)
91
94
  ### Loading a trained model for prediction
92
95
 
93
96
  ```python
94
- from modelzone.predict import load_model
97
+ from modelzone.predict import load_model_artifact
95
98
 
96
- trained_model = load_model(".model")
97
- print(trained_model.features)
99
+ model_artifact = load_model_artifact(".model")
100
+ print(model_artifact.features)
98
101
  ```
99
102
 
100
103
  ## CLI
@@ -22,22 +22,22 @@ pip install modelzone-sdk[azureml]
22
22
 
23
23
  ## Quick start
24
24
 
25
- Define a model by subclassing `Model` and implementing `train` and `predict`:
25
+ Define a model by subclassing `ModelDefinition` and implementing `train` and `predict`:
26
26
 
27
27
  ```python
28
- from modelzone.core import Model, PredictContext, TrainedModel
28
+ from modelzone.core import ModelDefinition, PredictContext, ModelArtifact
29
29
  from modelzone.training import TrainingContext
30
30
 
31
31
 
32
- class MyModel(Model):
33
- def train(self, ctx: TrainingContext) -> TrainedModel:
32
+ class MyModel(ModelDefinition):
33
+ def train(self, ctx: TrainingContext) -> ModelArtifact:
34
34
  ctx.print("Training started")
35
35
 
36
36
  # … your training logic …
37
37
  fitted = train_something(seed=ctx.seed)
38
38
 
39
39
  ctx.log_metric("accuracy", 0.95)
40
- return TrainedModel(model=fitted, features=["feature_a", "feature_b"])
40
+ return ModelArtifact(model=fitted, features=["feature_a", "feature_b"])
41
41
 
42
42
  def predict(self, ctx: PredictContext):
43
43
  df = ctx.db.query("input_table", ctx.time_interval)
@@ -71,10 +71,10 @@ result = backend.run(MyModel(), seed=42)
71
71
  ### Loading a trained model for prediction
72
72
 
73
73
  ```python
74
- from modelzone.predict import load_model
74
+ from modelzone.predict import load_model_artifact
75
75
 
76
- trained_model = load_model(".model")
77
- print(trained_model.features)
76
+ model_artifact = load_model_artifact(".model")
77
+ print(model_artifact.features)
78
78
  ```
79
79
 
80
80
  ## CLI
@@ -0,0 +1,19 @@
1
+ """Modelzone SDK – structured model training and serving.
2
+
3
+ Subpackages
4
+ -----------
5
+ - :mod:`modelzone.core` – ``ModelDefinition``, ``ModelArtifact``,
6
+ ``TrainingResult`` and friends (zero extras, safe everywhere).
7
+ - :mod:`modelzone.training` – ``LocalBackend``, ``TrainingContext``,
8
+ ``ModelzoneDatabase`` (training environment).
9
+ - :mod:`modelzone.azureml` – ``AzureMLBackend`` (requires ``azureml``
10
+ extra).
11
+ """
12
+
13
+ from modelzone import project_config # noqa: F401
14
+ from modelzone.core import ( # noqa: F401
15
+ ModelArtifact,
16
+ ModelDefinition,
17
+ PredictContext,
18
+ TrainingResult,
19
+ )
@@ -0,0 +1,27 @@
1
+ """Modelzone AzureML backend – tracked experiment runs (SDK v2 + MLflow).
2
+
3
+ Requires ``azure-ai-ml``, ``azure-identity``, and ``mlflow``.
4
+ Install with::
5
+
6
+ pip install modelzone-sdk[azureml]
7
+
8
+ Usage::
9
+
10
+ from modelzone.azureml import AzureMLBackend
11
+
12
+ backend = AzureMLBackend(
13
+ subscription_id="...",
14
+ resource_group="...",
15
+ workspace_name="...",
16
+ experiment_name="my_experiment",
17
+ )
18
+ result = backend.run(model, seed=42, db=db)
19
+ """
20
+
21
+ try:
22
+ from modelzone.azureml.backend import AzureMLBackend # noqa: F401
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "AzureMLBackend requires the 'azureml' extra. "
26
+ "Install it with: pip install modelzone-sdk[azureml]"
27
+ ) from e
@@ -17,12 +17,17 @@ import shutil
17
17
  import tempfile
18
18
  from typing import Any
19
19
 
20
- import datamazing.pandas as pdz
21
20
  import requests
22
21
 
23
- from modelzone.core.constants import RECORD_FILENAME, TRAINED_MODEL_FILENAME
24
- from modelzone.core import Model, RunRecord, TrainedModel, TrainingResult
25
- from modelzone.training import Backend, TrainingContext
22
+ from modelzone.core import (
23
+ ModelArtifact,
24
+ ModelDefinition,
25
+ TrainingContext,
26
+ TrainingResult,
27
+ )
28
+ from modelzone.core.constants import TRAINED_MODEL_FILENAME
29
+ from modelzone.core.protocols import Database
30
+ from modelzone.training import Backend
26
31
 
27
32
  try:
28
33
  import mlflow
@@ -84,20 +89,12 @@ class AzureMLBackend(Backend):
84
89
  self.resource_group = resource_group
85
90
  self.workspace_name = workspace_name
86
91
  self.experiment_name = experiment_name
87
- self._ml_client: MLClient | None = None
88
-
89
- @property
90
- def ml_client(self) -> MLClient:
91
- """Lazily connect to the AzureML workspace."""
92
- if self._ml_client is None:
93
- credential = DefaultAzureCredential()
94
- self._ml_client = MLClient(
95
- credential=credential,
96
- subscription_id=self.subscription_id,
97
- resource_group_name=self.resource_group,
98
- workspace_name=self.workspace_name,
99
- )
100
- return self._ml_client
92
+ self.ml_client = MLClient(
93
+ credential=DefaultAzureCredential(),
94
+ subscription_id=subscription_id,
95
+ resource_group_name=resource_group,
96
+ workspace_name=workspace_name,
97
+ )
101
98
 
102
99
  def _configure_mlflow(self) -> None:
103
100
  """Point MLflow at the AzureML workspace tracking URI.
@@ -240,12 +237,12 @@ class AzureMLBackend(Backend):
240
237
 
241
238
  def run(
242
239
  self,
243
- model: Model,
240
+ model: ModelDefinition,
244
241
  *,
245
242
  seed: int | None = None,
246
243
  params: dict[str, Any] | None = None,
247
244
  tags: dict[str, str] | None = None,
248
- db: pdz.Database | None = None,
245
+ db: Database | None = None,
249
246
  source_dir: str | None = None,
250
247
  ) -> TrainingResult:
251
248
  """Execute a training run tracked by AzureML via MLflow."""
@@ -283,8 +280,7 @@ class AzureMLBackend(Backend):
283
280
  print(f"\n AzureML run: {portal_url}")
284
281
 
285
282
  return TrainingResult(
286
- trained_model=result.trained_model,
287
- record=result.record,
283
+ model_artifact=result.model_artifact,
288
284
  run_id=run_id,
289
285
  )
290
286
 
@@ -293,7 +289,7 @@ class AzureMLBackend(Backend):
293
289
  run_dir: str,
294
290
  seed: int,
295
291
  params: dict[str, Any] | None,
296
- db: pdz.Database | None,
292
+ db: Database | None,
297
293
  ) -> _AzureMLTrainingContext:
298
294
  """Return an MLflow-aware context and log params/seed eagerly."""
299
295
  if params:
@@ -328,16 +324,12 @@ class AzureMLBackend(Backend):
328
324
  )
329
325
 
330
326
  with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
331
- trained_model: TrainedModel = pickle.load(f) # noqa: S301
332
-
333
- with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
334
- record: RunRecord = pickle.load(f) # noqa: S301
327
+ model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
335
328
 
336
329
  shutil.rmtree(download_dir)
337
330
 
338
331
  return TrainingResult(
339
- trained_model=trained_model,
340
- record=record,
332
+ model_artifact=model_artifact,
341
333
  run_id=run_id,
342
334
  )
343
335
 
@@ -348,7 +340,7 @@ class AzureMLBackend(Backend):
348
340
  def register(
349
341
  self,
350
342
  run_id: str,
351
- model_name: str,
343
+ registered_name: str,
352
344
  *,
353
345
  description: str | None = None,
354
346
  tags: dict[str, str] | None = None,
@@ -365,7 +357,7 @@ class AzureMLBackend(Backend):
365
357
  Args:
366
358
  run_id: The MLflow run ID whose ``run_output`` artifacts
367
359
  will back the registered model.
368
- model_name: Name for the model in the AzureML Model Registry.
360
+ registered_name: Name for the model in the AzureML Model Registry.
369
361
  description: Optional human-readable description.
370
362
  tags: Optional key/value tags stored on the model version.
371
363
 
@@ -379,14 +371,14 @@ class AzureMLBackend(Backend):
379
371
  merged_tags.update(tags)
380
372
 
381
373
  try:
382
- existing = self.ml_client.models.list(name=model_name)
374
+ existing = self.ml_client.models.list(name=registered_name)
383
375
  versions = [int(m.version) for m in existing if m.version.isdigit()]
384
376
  except Exception:
385
377
  versions = []
386
378
  version = str(max(versions) + 1) if versions else "1"
387
379
 
388
380
  model = AzureMLModel(
389
- name=model_name,
381
+ name=registered_name,
390
382
  version=version,
391
383
  path=f"azureml://jobs/{run_id}/outputs/artifacts/run_output",
392
384
  type=AssetTypes.CUSTOM_MODEL,
@@ -398,67 +390,51 @@ class AzureMLBackend(Backend):
398
390
 
399
391
  def download_registered(
400
392
  self,
401
- model_name: str,
402
- version: str | None = None,
393
+ registered_name: str,
394
+ version: str,
403
395
  ) -> tuple[TrainingResult, str, str]:
404
396
  """Download a registered model, returning the result and the local path.
405
397
 
406
398
  The local path points to the downloaded model directory which
407
- contains ``trained_model.pkl``, ``record.pkl``, and ``code/``.
399
+ contains ``trained_model.pkl``, ``run_info.json``, and ``code/``.
408
400
  This is useful at build time to export the model into a
409
401
  :class:`~modelzone.training.LocalBackend` via
410
402
  :meth:`~modelzone.training.LocalBackend.save`.
411
403
 
412
404
  Args:
413
- model_name: Name of the model in the AzureML Model Registry.
414
- version: Version to download. When *None* the latest
415
- version is used.
405
+ registered_name: Name of the model in the AzureML Model Registry.
406
+ version: Version to download.
416
407
 
417
408
  Returns:
418
- A tuple of ``(TrainingResult, model_dir_path)``.
409
+ A tuple of ``(TrainingResult, model_dir_path, download_dir)``.
419
410
  """
420
- if version is None:
421
- try:
422
- existing = self.ml_client.models.list(name=model_name)
423
- versions = [m for m in existing if m.version.isdigit()]
424
- except Exception:
425
- versions = []
426
- if not versions:
427
- raise ValueError(f"No versions found for model '{model_name}'")
428
- latest = max(versions, key=lambda m: int(m.version))
429
- version = latest.version
430
-
431
411
  download_dir = tempfile.mkdtemp(prefix="modelzone_registered_")
432
412
  self.ml_client.models.download(
433
- name=model_name,
413
+ name=registered_name,
434
414
  version=version,
435
415
  download_path=download_dir,
436
416
  )
437
417
 
438
- local_path = os.path.join(download_dir, model_name, "run_output")
418
+ local_path = os.path.join(download_dir, registered_name, "run_output")
439
419
 
440
420
  with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
441
- trained_model: TrainedModel = pickle.load(f) # noqa: S301
442
-
443
- with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
444
- record: RunRecord = pickle.load(f) # noqa: S301
421
+ model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
445
422
 
446
423
  run_id = ""
447
- model_entity = self.ml_client.models.get(name=model_name, version=version)
424
+ model_entity = self.ml_client.models.get(name=registered_name, version=version)
448
425
  if model_entity.tags:
449
426
  run_id = model_entity.tags.get("run_id", "")
450
427
 
451
428
  result = TrainingResult(
452
- trained_model=trained_model,
453
- record=record,
429
+ model_artifact=model_artifact,
454
430
  run_id=run_id,
455
431
  )
456
432
  return result, local_path, download_dir
457
433
 
458
434
  def load_registered(
459
435
  self,
460
- model_name: str,
461
- version: str | None = None,
436
+ registered_name: str,
437
+ version: str,
462
438
  ) -> TrainingResult:
463
439
  """Download a registered model and reconstruct a TrainingResult.
464
440
 
@@ -466,13 +442,12 @@ class AzureMLBackend(Backend):
466
442
  discards the local download path.
467
443
 
468
444
  Args:
469
- model_name: Name of the model in the AzureML Model Registry.
470
- version: Version to download. When *None* the latest
471
- version is used.
445
+ registered_name: Name of the model in the AzureML Model Registry.
446
+ version: Version to download.
472
447
 
473
448
  Returns:
474
449
  The reconstructed :class:`TrainingResult`.
475
450
  """
476
- result, _, download_dir = self.download_registered(model_name, version)
451
+ result, _, download_dir = self.download_registered(registered_name, version)
477
452
  shutil.rmtree(download_dir)
478
453
  return result
@@ -1,57 +1,31 @@
1
1
  """Modelzone CLI – train, register, and fetch models.
2
2
 
3
3
  Provides ``modelzone train``, ``modelzone register``, and
4
- ``modelzone fetch`` as console entry points so that project repos
5
- only need config files, not scripts.
6
-
7
- All commands expect to be run from the project root directory
8
- containing ``project.json`` and model packages with ``model.json``.
4
+ ``modelzone fetch`` as console entry points. All commands expect
5
+ a ``pyproject.toml`` with a ``[tool.modelzone]`` section.
9
6
  """
10
7
 
11
8
  from __future__ import annotations
12
9
 
13
10
  import argparse
14
11
  import importlib
15
- import json
16
12
  import os
17
13
  import sys
18
14
 
19
- from modelzone import MODEL_CONFIG, PROJECT_CONFIG
20
- from modelzone.core import Model
21
-
22
-
23
- def _load_project() -> dict:
24
- if not os.path.isfile(PROJECT_CONFIG):
25
- print(f"Error: {PROJECT_CONFIG} not found in {os.getcwd()}", file=sys.stderr)
26
- sys.exit(1)
27
- with open(PROJECT_CONFIG) as f:
28
- return json.load(f)
29
-
15
+ from modelzone import ModelDefinition, project_config
30
16
 
31
- def _load_model_config(model_package: str) -> dict:
32
- path = os.path.join(model_package, MODEL_CONFIG)
33
- if not os.path.isfile(path):
34
- print(f"Error: {path} not found in {os.getcwd()}", file=sys.stderr)
35
- sys.exit(1)
36
- with open(path) as f:
37
- return json.load(f)
38
17
 
39
-
40
- def _load_root_model_config() -> dict:
41
- if not os.path.isfile(MODEL_CONFIG):
42
- print(f"Error: {MODEL_CONFIG} not found in {os.getcwd()}", file=sys.stderr)
43
- sys.exit(1)
44
- with open(MODEL_CONFIG) as f:
45
- return json.load(f)
46
-
47
-
48
- def _discover_model_class(model_package: str) -> type[Model]:
18
+ def _discover_model_class(model_package: str) -> type[ModelDefinition]:
49
19
  mod = importlib.import_module(model_package)
50
20
  for v in vars(mod).values():
51
- if isinstance(v, type) and issubclass(v, Model) and v is not Model:
21
+ if (
22
+ isinstance(v, type)
23
+ and issubclass(v, ModelDefinition)
24
+ and v is not ModelDefinition
25
+ ):
52
26
  return v
53
27
  print(
54
- f"Error: no Model subclass found in {model_package}",
28
+ f"Error: no ModelDefinition subclass found in {model_package}",
55
29
  file=sys.stderr,
56
30
  )
57
31
  sys.exit(1)
@@ -78,7 +52,7 @@ def _build_db(config: dict, cache: bool = True):
78
52
 
79
53
 
80
54
  def train(args: argparse.Namespace) -> None:
81
- config = _load_model_config(args.model_package)
55
+ config = project_config.load_model(args.model_package)
82
56
  db, tags = _build_db(config, cache=not args.no_cache)
83
57
 
84
58
  params = {}
@@ -88,12 +62,12 @@ def train(args: argparse.Namespace) -> None:
88
62
  if args.azureml:
89
63
  from modelzone.azureml import AzureMLBackend
90
64
 
91
- project = _load_project()
92
- backend = AzureMLBackend(
65
+ project = project_config.load_project()
66
+ azureml_backend = AzureMLBackend(
93
67
  **project["workspace"],
94
- experiment_name=config["experiment_name"],
68
+ experiment_name=config["name"],
95
69
  )
96
- result = backend.run(
70
+ result = azureml_backend.run(
97
71
  _discover_model_class(args.model_package)(),
98
72
  seed=42,
99
73
  db=db,
@@ -105,8 +79,8 @@ def train(args: argparse.Namespace) -> None:
105
79
  else:
106
80
  from modelzone.training import LocalBackend
107
81
 
108
- backend = LocalBackend(root="runs")
109
- result = backend.run(
82
+ local_backend = LocalBackend(root="runs")
83
+ result = local_backend.run(
110
84
  _discover_model_class(args.model_package)(),
111
85
  seed=42,
112
86
  db=db,
@@ -116,58 +90,58 @@ def train(args: argparse.Namespace) -> None:
116
90
  )
117
91
  print(f"\nLocal Run ID: {result.run_id}")
118
92
 
119
- for m in result.record.metrics:
120
- print(f" {m.name} = {m.value}")
121
-
122
93
 
123
94
  def register(args: argparse.Namespace) -> None:
124
95
  from modelzone.azureml import AzureMLBackend
125
96
 
126
- project = _load_project()
127
- config = _load_model_config(args.model_package)
97
+ project = project_config.load_project()
98
+ config = project_config.load_model(args.model_package)
128
99
 
129
100
  backend = AzureMLBackend(
130
101
  **project["workspace"],
131
- experiment_name=config["experiment_name"],
102
+ experiment_name=config["name"],
132
103
  )
133
104
  registered = backend.register(
134
105
  run_id=args.run_id,
135
- model_name=config["model_name"],
106
+ registered_name=config["name"],
136
107
  )
137
108
  print(f"Registered: {registered.name} v{registered.version}")
138
109
 
139
110
 
140
- def fetch(args: argparse.Namespace) -> None:
111
+ def _fetch_one(
112
+ config: dict,
113
+ workspace: dict,
114
+ ) -> None:
141
115
  import pickle
142
116
  import shutil
143
117
 
144
118
  from modelzone.azureml import AzureMLBackend
145
119
 
146
- project = _load_project()
147
- if args.model_package:
148
- config = _load_model_config(args.model_package)
149
- else:
150
- config = _load_root_model_config()
151
- model_name = config["model_name"]
120
+ model_name = config["name"]
152
121
 
153
122
  backend = AzureMLBackend(
154
- **project["workspace"],
155
- experiment_name=config["experiment_name"],
123
+ **workspace,
124
+ experiment_name=config["name"],
156
125
  )
157
126
 
158
- version = args.version or config.get("model_version")
159
- version_label = str(version) if version else "latest"
160
- print(f"Downloading {model_name} (version={version_label})...")
161
- result, model_dir, download_dir = backend.download_registered(model_name, version=version)
127
+ version = config.get("model_version")
128
+ if version is None:
129
+ print(
130
+ f"Error: model_version is required for {model_name}",
131
+ file=sys.stderr,
132
+ )
133
+ sys.exit(1)
134
+ version = str(version)
135
+ print(f"Downloading {model_name} (version={version})...")
136
+ result, model_dir, download_dir = backend.download_registered(
137
+ model_name, version=version
138
+ )
162
139
 
163
- out = args.output_dir
140
+ out = config["path"]
164
141
  os.makedirs(out, exist_ok=True)
165
142
 
166
143
  with open(os.path.join(out, "trained_model.pkl"), "wb") as f:
167
- pickle.dump(result.trained_model, f)
168
-
169
- with open(os.path.join(out, "record.pkl"), "wb") as f:
170
- pickle.dump(result.record, f)
144
+ pickle.dump(result.model_artifact, f)
171
145
 
172
146
  src_code = os.path.join(model_dir, "code")
173
147
  dst_code = os.path.join(out, "code")
@@ -180,6 +154,13 @@ def fetch(args: argparse.Namespace) -> None:
180
154
  print(f"Saved to {out}")
181
155
 
182
156
 
157
+ def fetch(args: argparse.Namespace) -> None:
158
+ project = project_config.load_project()
159
+ models = project_config.load_all_models()
160
+ for config in models:
161
+ _fetch_one(config, project["workspace"])
162
+
163
+
183
164
  def main() -> None:
184
165
  parser = argparse.ArgumentParser(prog="modelzone", description="Modelzone CLI")
185
166
  sub = parser.add_subparsers(dest="command", required=True)
@@ -204,19 +185,9 @@ def main() -> None:
204
185
  r.add_argument("model_package", help="Model package name")
205
186
  r.add_argument("run_id", help="MLflow run ID to register")
206
187
 
207
- f = sub.add_parser("fetch", help="Download a registered model for local use")
208
- f.add_argument(
209
- "model_package",
210
- nargs="?",
211
- default=None,
212
- help="Model package name (omit to read model.json from cwd)",
213
- )
214
- f.add_argument(
215
- "--version",
216
- default=None,
217
- help="Model version (default: model_version from config, or latest)",
188
+ sub.add_parser(
189
+ "fetch", help="Download all registered models defined in pyproject.toml"
218
190
  )
219
- f.add_argument("--output-dir", default=".model", help="Local output directory")
220
191
 
221
192
  args = parser.parse_args()
222
193
  if args.command == "train":
@@ -0,0 +1,8 @@
1
+ """Modelzone core – minimal runtime classes for training and prediction."""
2
+
3
+ from modelzone.core.constants import CODE_DIR, TRAINED_MODEL_FILENAME # noqa: F401
4
+ from modelzone.core.model import ModelDefinition # noqa: F401
5
+ from modelzone.core.model_artifact import ModelArtifact # noqa: F401
6
+ from modelzone.core.predict_context import PredictContext # noqa: F401
7
+ from modelzone.core.training_context import TrainingContext # noqa: F401
8
+ from modelzone.core.training_result import TrainingResult # noqa: F401
@@ -0,0 +1,9 @@
1
+ """Modelzone core – shared filename conventions for run directories."""
2
+
3
+ # Filename used for the pickled ModelArtifact inside every run directory.
4
+ TRAINED_MODEL_FILENAME = "trained_model.pkl"
5
+ RUN_INFO_FILENAME = "run_info.json"
6
+ METRICS_FILENAME = "metrics.jsonl"
7
+ LOG_FILENAME = "output.log"
8
+ FILES_DIR = "files"
9
+ CODE_DIR = "code"