Machine Learning › Machine Learning in Practice › Day 193
Hands-on lab — Day 193: Saving and Versioning Models
- ← Back to the Day 193 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/machine-learning/day-193-saving-and-versioning-models/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/saving_and_versioning_models_lib.py Test
./tests/run_tests.sh File tree
examples/saving_and_versioning_models_lib.py examples/test_saving_and_versioning_models_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/saving_and_versioning_models_lib.py starter/test_saving_and_versioning_models_lib.py tests/run_tests.sh tests/test_saving_and_versioning_models_lib.py troubleshooting.md
Lab README
Lab: Day 193 -- Saving and Versioning Models
Lesson
Day number: 193 of 365. Course: Course04-SS03 (Beyond Supervised Learning). Topic: Model Persistence, Serialization Formats, and Enterprise Model Registries.
Purpose
Build a complete, thread-safe Enterprise Model Registry in pure Python. You will implement cryptographic SHA-256 artifact hashing, register model versions with lineage metadata, enforce stage promotion state transitions, and ensure strict production uniqueness.
Learning objectives
- Analyze serialization security trade-offs (pickle vs ONNX vs safetensors).
- Generate SHA-256 cryptographic hashes to verify binary artifact integrity.
- Implement semantic versioning (MAJOR.MINOR.PATCH) and lineage catalogs.
- Enforce stage promotion rules (Staging -> Production -> Archived).
Prerequisites
- Python standard library (
hashlib,dataclasses, dictionaries). - Core understanding of software versioning and deployment stages.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 512 MB RAM.
- 50 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
All tools used in this lab (Python, pytest) are free and open-source under BSD/MIT licenses.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/saving_and_versioning_models_lib.py: Student scaffold file.examples/saving_and_versioning_models_lib.py: Complete reference implementation.tests/test_saving_and_versioning_models_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/saving_and_versioning_models_lib.py
What the commands do
- Registers model versions
1.0.0and1.1.0. - Computes cryptographic SHA-256 checksums.
- Promotes
1.1.0to Production, archiving1.0.0.
Expected output
Registry Demo: Active Production Version = 1.1.0
Validation steps
- Check that SHA-256 hashes are 64 hexadecimal characters.
- Verify that promoting a new version to PRODUCTION automatically archives the old version.
- Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Invalid Stage Error: Ensure stage strings match
"STAGING","PRODUCTION","ARCHIVED", or"REJECTED".
Security notes
All computations execute locally without external network transmission.
Extension exercises
- Implement Pydantic Schema Validation during model registration.
- Export registry metadata to a formatted JSON Model Card.
Navigation
- Lesson title: Saving and Versioning Models
- Day number: 193 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-193-saving-and-versioning-models
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-193-saving-and-versioning-modelswhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 193
- `Active Production Version`: Currently deployed SemVer string.
- `SHA-256 Length`: Character count of cryptographic digest (64).
- `Stage Transition`: State machine verification status.
examples-run.txt
Registry Demo: Active Production Version = 1.1.0
measured-values.txt
Active Production Version: 1.1.0
SHA-256 Length: 64
Stage Transition: Verified
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 2 items
tests/test_saving_and_versioning_models_lib.py::test_register_and_hashing PASSED [ 50%]
tests/test_saving_and_versioning_models_lib.py::test_stage_promotion_and_archival PASSED [100%]
============================== 2 passed in 0.08s ===============================
Source files
examples/saving_and_versioning_models_lib.py (3024 bytes)
import hashlib
from dataclasses import dataclass
from typing import Dict, Optional
@dataclass
class ModelVersionMetadata:
model_name: str
version: str
sha256_hash: str
stage: str
git_commit: str
metrics: Dict[str, float]
class ModelRegistry:
def __init__(self):
self._models: Dict[str, Dict[str, ModelVersionMetadata]] = {}
@staticmethod
def compute_sha256(file_bytes: bytes) -> str:
return hashlib.sha256(file_bytes).hexdigest()
def register_model(
self, model_name: str, version: str, artifact_bytes: bytes,
git_commit: str, metrics: Dict[str, float]
) -> ModelVersionMetadata:
if model_name not in self._models:
self._models[model_name] = {}
if version in self._models[model_name]:
raise ValueError(f"Version {version} already exists for model {model_name}")
sha256 = self.compute_sha256(artifact_bytes)
meta = ModelVersionMetadata(
model_name=model_name,
version=version,
sha256_hash=sha256,
stage="STAGING",
git_commit=git_commit,
metrics=metrics
)
self._models[model_name][version] = meta
return meta
def transition_stage(self, model_name: str, version: str, new_stage: str) -> ModelVersionMetadata:
valid_stages = {"STAGING", "PRODUCTION", "ARCHIVED", "REJECTED"}
if new_stage not in valid_stages:
raise ValueError(f"Invalid stage: {new_stage}")
if model_name not in self._models or version not in self._models[model_name]:
raise KeyError(f"Model {model_name}:{version} not found")
if new_stage == "PRODUCTION":
for v, meta in self._models[model_name].items():
if meta.stage == "PRODUCTION" and v != version:
meta.stage = "ARCHIVED"
meta = self._models[model_name][version]
meta.stage = new_stage
return meta
def get_production_model(self, model_name: str) -> Optional[ModelVersionMetadata]:
if model_name not in self._models:
return None
for meta in self._models[model_name].values():
if meta.stage == "PRODUCTION":
return meta
return None
def run_registry_demo():
registry = ModelRegistry()
dummy_bytes_v1 = b"MODEL_WEIGHTS_V1_BIN_BLOB"
dummy_bytes_v2 = b"MODEL_WEIGHTS_V2_BIN_BLOB"
m1 = registry.register_model("fraud_detector", "1.0.0", dummy_bytes_v1, "a1b2c3", {"pr_auc": 0.82})
m2 = registry.register_model("fraud_detector", "1.1.0", dummy_bytes_v2, "d4e5f6", {"pr_auc": 0.86})
registry.transition_stage("fraud_detector", "1.0.0", "PRODUCTION")
registry.transition_stage("fraud_detector", "1.1.0", "PRODUCTION")
prod = registry.get_production_model("fraud_detector")
print(f"Registry Demo: Active Production Version = {prod.version if prod else None}")
return registry, prod
if __name__ == "__main__":
run_registry_demo()
examples/test_saving_and_versioning_models_lib.py (1103 bytes)
import pytest
from examples.saving_and_versioning_models_lib import ModelRegistry
def test_register_and_hashing():
reg = ModelRegistry()
raw = b"BINARY_BYTES_12345"
meta = reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
assert meta.stage == "STAGING"
assert len(meta.sha256_hash) == 64
assert meta.version == "1.0.0"
with pytest.raises(ValueError):
reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
def test_stage_promotion_and_archival():
reg = ModelRegistry()
reg.register_model("risk_model", "1.0.0", b"V1", "sha1", {"auc": 0.80})
reg.register_model("risk_model", "1.1.0", b"V2", "sha2", {"auc": 0.85})
reg.transition_stage("risk_model", "1.0.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.0.0"
# Promoting 1.1.0 must archive 1.0.0 automatically
reg.transition_stage("risk_model", "1.1.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.1.0"
assert reg._models["risk_model"]["1.0.0"].stage == "ARCHIVED"
metadata.yml (441 bytes)
lesson_id: D193
day: 193
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/saving_and_versioning_models_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (14 bytes)
pytest>=8.0.0
starter/saving_and_versioning_models_lib.py (1111 bytes)
import hashlib
from dataclasses import dataclass
from typing import Dict, Optional
@dataclass
class ModelVersionMetadata:
model_name: str
version: str
sha256_hash: str
stage: str
git_commit: str
metrics: Dict[str, float]
class ModelRegistry:
def __init__(self):
self._models: Dict[str, Dict[str, ModelVersionMetadata]] = {}
@staticmethod
def compute_sha256(file_bytes: bytes) -> str:
# TODO: Compute SHA-256 hexadecimal digest
pass
def register_model(self, model_name: str, version: str, artifact_bytes: bytes,
git_commit: str, metrics: Dict[str, float]) -> ModelVersionMetadata:
# TODO: Register model with SHA-256 and initial STAGING stage
pass
def transition_stage(self, model_name: str, version: str, new_stage: str) -> ModelVersionMetadata:
# TODO: Update stage; if PRODUCTION, archive existing production versions
pass
def get_production_model(self, model_name: str) -> Optional[ModelVersionMetadata]:
# TODO: Return currently active PRODUCTION model
pass
starter/test_saving_and_versioning_models_lib.py (1103 bytes)
import pytest
from examples.saving_and_versioning_models_lib import ModelRegistry
def test_register_and_hashing():
reg = ModelRegistry()
raw = b"BINARY_BYTES_12345"
meta = reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
assert meta.stage == "STAGING"
assert len(meta.sha256_hash) == 64
assert meta.version == "1.0.0"
with pytest.raises(ValueError):
reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
def test_stage_promotion_and_archival():
reg = ModelRegistry()
reg.register_model("risk_model", "1.0.0", b"V1", "sha1", {"auc": 0.80})
reg.register_model("risk_model", "1.1.0", b"V2", "sha2", {"auc": 0.85})
reg.transition_stage("risk_model", "1.0.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.0.0"
# Promoting 1.1.0 must archive 1.0.0 automatically
reg.transition_stage("risk_model", "1.1.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.1.0"
assert reg._models["risk_model"]["1.0.0"].stage == "ARCHIVED"
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 193 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_saving_and_versioning_models_lib.py (1103 bytes)
import pytest
from examples.saving_and_versioning_models_lib import ModelRegistry
def test_register_and_hashing():
reg = ModelRegistry()
raw = b"BINARY_BYTES_12345"
meta = reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
assert meta.stage == "STAGING"
assert len(meta.sha256_hash) == 64
assert meta.version == "1.0.0"
with pytest.raises(ValueError):
reg.register_model("risk_model", "1.0.0", raw, "git_sha_1", {"roc_auc": 0.90})
def test_stage_promotion_and_archival():
reg = ModelRegistry()
reg.register_model("risk_model", "1.0.0", b"V1", "sha1", {"auc": 0.80})
reg.register_model("risk_model", "1.1.0", b"V2", "sha2", {"auc": 0.85})
reg.transition_stage("risk_model", "1.0.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.0.0"
# Promoting 1.1.0 must archive 1.0.0 automatically
reg.transition_stage("risk_model", "1.1.0", "PRODUCTION")
assert reg.get_production_model("risk_model").version == "1.1.0"
assert reg._models["risk_model"]["1.0.0"].stage == "ARCHIVED"
Troubleshooting
Troubleshooting: Day 193 - Saving and Versioning Models
Common Issues
- Duplicate Version Exception:
- Cause: Attempting to overwrite an existing version string.
- Fix: Increment version number according to SemVer rules.
Security notes
Security & Privacy: Day 193 - Saving and Versioning Models
Security Guidance
- All registry actions execute locally in memory.