modelzone-sdk 0.2.0.dev1__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.dev1 → modelzone_sdk-0.3.0}/PKG-INFO +12 -9
  2. {modelzone_sdk-0.2.0.dev1 → 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.dev1 → modelzone_sdk-0.3.0}/modelzone/azureml/backend.py +38 -64
  6. {modelzone_sdk-0.2.0.dev1 → 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.dev1 → 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.2.0.dev1/modelzone/training/context.py → modelzone_sdk-0.3.0/modelzone/core/training_context.py +25 -20
  13. {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/core/training_result.py +3 -7
  14. modelzone_sdk-0.3.0/modelzone/project_config.py +73 -0
  15. modelzone_sdk-0.3.0/modelzone/training/__init__.py +19 -0
  16. {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/training/backend.py +23 -68
  17. {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/pyproject.toml +13 -19
  18. modelzone_sdk-0.2.0.dev1/modelzone/__init__.py +0 -27
  19. modelzone_sdk-0.2.0.dev1/modelzone/azureml/__init__.py +0 -35
  20. modelzone_sdk-0.2.0.dev1/modelzone/core/__init__.py +0 -20
  21. modelzone_sdk-0.2.0.dev1/modelzone/core/constants.py +0 -6
  22. modelzone_sdk-0.2.0.dev1/modelzone/core/predict_context.py +0 -47
  23. modelzone_sdk-0.2.0.dev1/modelzone/core/record.py +0 -37
  24. modelzone_sdk-0.2.0.dev1/modelzone/core/trained_model.py +0 -86
  25. modelzone_sdk-0.2.0.dev1/modelzone/predict.py +0 -41
  26. modelzone_sdk-0.2.0.dev1/modelzone/training/__init__.py +0 -32
  27. {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/core/protocols.py +0 -0
  28. {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/training/db.py +0 -0
  29. {modelzone_sdk-0.2.0.dev1 → 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.dev1
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
@@ -19,11 +19,15 @@ from typing import Any
19
19
 
20
20
  import requests
21
21
 
22
+ from modelzone.core import (
23
+ ModelArtifact,
24
+ ModelDefinition,
25
+ TrainingContext,
26
+ TrainingResult,
27
+ )
28
+ from modelzone.core.constants import TRAINED_MODEL_FILENAME
22
29
  from modelzone.core.protocols import Database
23
-
24
- from modelzone.core.constants import RECORD_FILENAME, TRAINED_MODEL_FILENAME
25
- from modelzone.core import Model, RunRecord, TrainedModel, TrainingResult
26
- from modelzone.training import Backend, TrainingContext
30
+ from modelzone.training import Backend
27
31
 
28
32
  try:
29
33
  import mlflow
@@ -85,20 +89,12 @@ class AzureMLBackend(Backend):
85
89
  self.resource_group = resource_group
86
90
  self.workspace_name = workspace_name
87
91
  self.experiment_name = experiment_name
88
- self._ml_client: MLClient | None = None
89
-
90
- @property
91
- def ml_client(self) -> MLClient:
92
- """Lazily connect to the AzureML workspace."""
93
- if self._ml_client is None:
94
- credential = DefaultAzureCredential()
95
- self._ml_client = MLClient(
96
- credential=credential,
97
- subscription_id=self.subscription_id,
98
- resource_group_name=self.resource_group,
99
- workspace_name=self.workspace_name,
100
- )
101
- 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
+ )
102
98
 
103
99
  def _configure_mlflow(self) -> None:
104
100
  """Point MLflow at the AzureML workspace tracking URI.
@@ -241,7 +237,7 @@ class AzureMLBackend(Backend):
241
237
 
242
238
  def run(
243
239
  self,
244
- model: Model,
240
+ model: ModelDefinition,
245
241
  *,
246
242
  seed: int | None = None,
247
243
  params: dict[str, Any] | None = None,
@@ -284,8 +280,7 @@ class AzureMLBackend(Backend):
284
280
  print(f"\n AzureML run: {portal_url}")
285
281
 
286
282
  return TrainingResult(
287
- trained_model=result.trained_model,
288
- record=result.record,
283
+ model_artifact=result.model_artifact,
289
284
  run_id=run_id,
290
285
  )
291
286
 
@@ -329,16 +324,12 @@ class AzureMLBackend(Backend):
329
324
  )
330
325
 
331
326
  with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
332
- trained_model: TrainedModel = pickle.load(f) # noqa: S301
333
-
334
- with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
335
- record: RunRecord = pickle.load(f) # noqa: S301
327
+ model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
336
328
 
337
329
  shutil.rmtree(download_dir)
338
330
 
339
331
  return TrainingResult(
340
- trained_model=trained_model,
341
- record=record,
332
+ model_artifact=model_artifact,
342
333
  run_id=run_id,
343
334
  )
344
335
 
@@ -349,7 +340,7 @@ class AzureMLBackend(Backend):
349
340
  def register(
350
341
  self,
351
342
  run_id: str,
352
- model_name: str,
343
+ registered_name: str,
353
344
  *,
354
345
  description: str | None = None,
355
346
  tags: dict[str, str] | None = None,
@@ -366,7 +357,7 @@ class AzureMLBackend(Backend):
366
357
  Args:
367
358
  run_id: The MLflow run ID whose ``run_output`` artifacts
368
359
  will back the registered model.
369
- model_name: Name for the model in the AzureML Model Registry.
360
+ registered_name: Name for the model in the AzureML Model Registry.
370
361
  description: Optional human-readable description.
371
362
  tags: Optional key/value tags stored on the model version.
372
363
 
@@ -380,14 +371,14 @@ class AzureMLBackend(Backend):
380
371
  merged_tags.update(tags)
381
372
 
382
373
  try:
383
- existing = self.ml_client.models.list(name=model_name)
374
+ existing = self.ml_client.models.list(name=registered_name)
384
375
  versions = [int(m.version) for m in existing if m.version.isdigit()]
385
376
  except Exception:
386
377
  versions = []
387
378
  version = str(max(versions) + 1) if versions else "1"
388
379
 
389
380
  model = AzureMLModel(
390
- name=model_name,
381
+ name=registered_name,
391
382
  version=version,
392
383
  path=f"azureml://jobs/{run_id}/outputs/artifacts/run_output",
393
384
  type=AssetTypes.CUSTOM_MODEL,
@@ -399,67 +390,51 @@ class AzureMLBackend(Backend):
399
390
 
400
391
  def download_registered(
401
392
  self,
402
- model_name: str,
403
- version: str | None = None,
393
+ registered_name: str,
394
+ version: str,
404
395
  ) -> tuple[TrainingResult, str, str]:
405
396
  """Download a registered model, returning the result and the local path.
406
397
 
407
398
  The local path points to the downloaded model directory which
408
- contains ``trained_model.pkl``, ``record.pkl``, and ``code/``.
399
+ contains ``trained_model.pkl``, ``run_info.json``, and ``code/``.
409
400
  This is useful at build time to export the model into a
410
401
  :class:`~modelzone.training.LocalBackend` via
411
402
  :meth:`~modelzone.training.LocalBackend.save`.
412
403
 
413
404
  Args:
414
- model_name: Name of the model in the AzureML Model Registry.
415
- version: Version to download. When *None* the latest
416
- version is used.
405
+ registered_name: Name of the model in the AzureML Model Registry.
406
+ version: Version to download.
417
407
 
418
408
  Returns:
419
- A tuple of ``(TrainingResult, model_dir_path)``.
409
+ A tuple of ``(TrainingResult, model_dir_path, download_dir)``.
420
410
  """
421
- if version is None:
422
- try:
423
- existing = self.ml_client.models.list(name=model_name)
424
- versions = [m for m in existing if m.version.isdigit()]
425
- except Exception:
426
- versions = []
427
- if not versions:
428
- raise ValueError(f"No versions found for model '{model_name}'")
429
- latest = max(versions, key=lambda m: int(m.version))
430
- version = latest.version
431
-
432
411
  download_dir = tempfile.mkdtemp(prefix="modelzone_registered_")
433
412
  self.ml_client.models.download(
434
- name=model_name,
413
+ name=registered_name,
435
414
  version=version,
436
415
  download_path=download_dir,
437
416
  )
438
417
 
439
- local_path = os.path.join(download_dir, model_name, "run_output")
418
+ local_path = os.path.join(download_dir, registered_name, "run_output")
440
419
 
441
420
  with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
442
- trained_model: TrainedModel = pickle.load(f) # noqa: S301
443
-
444
- with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
445
- record: RunRecord = pickle.load(f) # noqa: S301
421
+ model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
446
422
 
447
423
  run_id = ""
448
- 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)
449
425
  if model_entity.tags:
450
426
  run_id = model_entity.tags.get("run_id", "")
451
427
 
452
428
  result = TrainingResult(
453
- trained_model=trained_model,
454
- record=record,
429
+ model_artifact=model_artifact,
455
430
  run_id=run_id,
456
431
  )
457
432
  return result, local_path, download_dir
458
433
 
459
434
  def load_registered(
460
435
  self,
461
- model_name: str,
462
- version: str | None = None,
436
+ registered_name: str,
437
+ version: str,
463
438
  ) -> TrainingResult:
464
439
  """Download a registered model and reconstruct a TrainingResult.
465
440
 
@@ -467,13 +442,12 @@ class AzureMLBackend(Backend):
467
442
  discards the local download path.
468
443
 
469
444
  Args:
470
- model_name: Name of the model in the AzureML Model Registry.
471
- version: Version to download. When *None* the latest
472
- version is used.
445
+ registered_name: Name of the model in the AzureML Model Registry.
446
+ version: Version to download.
473
447
 
474
448
  Returns:
475
449
  The reconstructed :class:`TrainingResult`.
476
450
  """
477
- result, _, download_dir = self.download_registered(model_name, version)
451
+ result, _, download_dir = self.download_registered(registered_name, version)
478
452
  shutil.rmtree(download_dir)
479
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"
@@ -1,24 +1,22 @@
1
- """Modelzone core – base Model class that users inherit from."""
1
+ """Modelzone core – base ModelDefinition class that users inherit from."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import abstractmethod
6
- from typing import TYPE_CHECKING, Any
7
-
8
- if TYPE_CHECKING:
9
- from modelzone.training.context import TrainingContext
6
+ from typing import Any
10
7
 
8
+ from modelzone.core.model_artifact import ModelArtifact
11
9
  from modelzone.core.predict_context import PredictContext
12
- from modelzone.core.trained_model import TrainedModel
10
+ from modelzone.core.training_context import TrainingContext
13
11
 
14
12
 
15
- class Model:
13
+ class ModelDefinition:
16
14
  """Base class for user-defined models.
17
15
 
18
16
  Subclass this and implement :meth:`train` and :meth:`predict`.
19
17
 
20
18
  * ``train(ctx)`` runs in the **training environment** and receives a
21
- :class:`~modelzone.training.TrainingContext` for logging metrics, plots
19
+ :class:`~modelzone.core.TrainingContext` for logging metrics, plots
22
20
  and tags.
23
21
  * ``predict(ctx)`` runs in the **operational environment** and
24
22
  receives a :class:`~modelzone.core.PredictContext` with the
@@ -27,13 +25,13 @@ class Model:
27
25
 
28
26
  Example::
29
27
 
30
- from modelzone.core import Model, TrainedModel
28
+ from modelzone.core import ModelDefinition, ModelArtifact
31
29
 
32
- class MyModel(Model):
30
+ class MyModel(ModelDefinition):
33
31
  def train(self, ctx):
34
32
  fitted = fit(load_data(), seed=ctx.seed)
35
33
  ctx.log_metric("rmse", compute_rmse(fitted))
36
- return TrainedModel(model=fitted, features=["a", "b"])
34
+ return ModelArtifact(model=fitted)
37
35
 
38
36
  def predict(self, ctx):
39
37
  df = ctx.db.read("SELECT * FROM input_table")
@@ -41,8 +39,8 @@ class Model:
41
39
  """
42
40
 
43
41
  @abstractmethod
44
- def train(self, ctx: TrainingContext) -> TrainedModel:
45
- """Run training logic and return a :class:`TrainedModel`.
42
+ def train(self, ctx: TrainingContext) -> ModelArtifact:
43
+ """Run training logic and return a :class:`ModelArtifact`.
46
44
 
47
45
  Only called in the training environment. Use *ctx* to log
48
46
  metrics, plots and tags; access ``ctx.seed`` and ``ctx.params``.
@@ -51,7 +49,7 @@ class Model:
51
49
  ctx: The run context for this training run.
52
50
 
53
51
  Returns:
54
- A :class:`TrainedModel` with the fitted artefact(s).
52
+ A :class:`ModelArtifact` with the fitted artefact(s).
55
53
  """
56
54
 
57
55
  @abstractmethod