MLOps Industrialization
Goal
To convert experimental code (notebooks/scripts) into a high-quality, distributable Python package. This skill enforces the src/ layout, a Hybrid Paradigm (OOP structure + Functional purity), and Strict Configuration to ensure scalability, security, and maintainability.
Prerequisites
- Language: Python 3.14
- Manager:
uv
- Context: Moving from
notebooks/ to src/.
Instructions
1. Packaging Structure (src Layout)
Adopt the src layout to prevent import errors and separate source from tooling.
Directory Tree:
my-project/
├── pyproject.toml # Dependencies & Metadata
├── uv.lock # Pinned Python dependencies
├── mise.toml # Task vocabulary & pinned tools
├── mise.lock # Pinned tool binaries
├── AGENTS.md # Instructions for AI agents
├── README.md
└── src/
└── my_package/ # Main package directory
├── __init__.py
├── io/ # Side-effects (Datasets, APIs)
├── domain/ # Pure business logic (Models, Features)
└── application/ # Orchestration (Training loops, Inference)
Configuration: Use pyproject.toml for all build metadata and dependencies.
2. Modularity & Paradigm (Hybrid Style)
Balance structure with predictability.
- Domain Layer (Pure):
- Rule: Code here must be deterministic and free of side effects (no I/O).
- Use Case: Feature transformations, Model architecture definitions.
- Style: Functional (pure functions) or Immutable Objects (dataclasses).
- I/O Layer (Impure):
- Rule: Isolate external interactions here.
- Use Case: Loading data from S3, saving models to disk, logging to MLflow.
- Style: OOP (Classes to manage connections/state).
- Application Layer (Orchestration):
- Rule: Wire Domain and I/O together.
- Use Case: Tuning, Training, Inference, Evaluation, etc.
3. Application Entrypoints
Create standard, installable CLI tools.
Define Script: Create src/my_package/scripts.py with a main() function.
Register: Add to pyproject.toml:
[project.scripts]
my-tool = "my_package.scripts:main"
CLI Execution:
- Dev:
uv run my-tool (No install needed).
- Prod:
pip install . -> my-tool (Installed on PATH).
Guard: Always use if __name__ == "__main__": in scripts to prevent execution on import.
4. Configuration Management
Decouple settings from code using OmegaConf (Parsing) and Pydantic (Validation).
Define Schema (Pydantic):
- Create a class that defines expected types and defaults.
from pydantic import BaseModel
class TrainingConfig(BaseModel):
batch_size: int = 32
learning_rate: float = 0.001
use_gpu: bool = False
Parse & Validate (OmegaConf):
- Load YAML, merge with CLI args, and validate against the schema.
import omegaconf
# 1. Load YAML
conf = omegaconf.OmegaConf.load("config.yaml")
# 2. Merge with CLI (optional)
cli_conf = omegaconf.OmegaConf.from_cli()
merged = omegaconf.OmegaConf.merge(conf, cli_conf)
# 3. Validate -> Returns a validated Pydantic object
cfg: TrainingConfig = TrainingConfig(**omegaconf.OmegaConf.to_container(merged))
Secrets: Use Environment Variables (os.getenv) or pydantic-settings, never commit them.
5. MLflow Services as I/O Objects
Tracking is a side effect, so it belongs in the I/O layer behind a small, configurable service.
- Backend: Default the tracking and registry URIs to a SQL store —
sqlite:///mlflow.db locally, a Postgres or HTTP tracking server in production. The file store is deprecated in MLflow 3.15 and does not support the model registry, so it is not a valid default for a package meant to reach production.
- Configuration, not constants: Expose
tracking_uri, registry_uri, experiment_name, and autolog as validated fields, so the same package runs against a laptop database and a shared server without a code change.
- Lifecycle: Give the service explicit
start()/stop() methods called by the application layer, never at import time.
6. Documentation & Quality
Make code usable and maintainable.
Docstrings: Use Google Style docstrings for all modules, classes, and functions.
def calculate_metric(y_true: np.ndarray, y_pred: np.ndarray) -> float:
"""Calculates the accuracy score.
Args:
y_true: Ground truth labels.
y_pred: Predicted labels.
Returns:
The accuracy as a float between 0 and 1.
"""
Type Hints: Use modern Python typing (list[str], X | Y) everywhere; ty (0.0.69+) checks them.
Instructions: Record the layer boundaries, the naming conventions, and the exact commands in AGENTS.md so assistants stop guessing where new code belongs.
Gate: mise run all (format -> check -> test -> build) must pass before a refactor is considered finished — see mlops-validation.
7. Best Practices Summary
- Config != Code: Never hardcode paths or hyperparams; use the
Pydantic + OmegaConf pattern.
- Entrypoints are APIs: Design your CLI (
[project.scripts]) as the public interface for your automation tools.
- Immutable Core: Keep your domain logic side-effect free; push I/O to the edges.
Self-Correction Checklist
1---2name: mlops-industrialization3description: Convert notebook prototypes into a distributable Python package with a src layout, a domain/io/application split, and validated OmegaConf plus Pydantic configuration. Use when moving code out of notebooks or designing entrypoints.4license: MIT5---67# MLOps Industrialization89## Goal1011To convert experimental code (notebooks/scripts) into a high-quality, distributable Python package. This skill enforces the **src/ layout**, a **Hybrid Paradigm** (OOP structure + Functional purity), and **Strict Configuration** to ensure scalability, security, and maintainability.1213## Prerequisites1415- **Language**: Python 3.1416- **Manager**: `uv`17- **Context**: Moving from `notebooks/` to `src/`.1819## Instructions2021### 1. Packaging Structure (`src` Layout)2223Adopt the `src` layout to prevent import errors and separate source from tooling.24251. **Directory Tree**:2627 ```text28 my-project/29 ├── pyproject.toml # Dependencies & Metadata30 ├── uv.lock # Pinned Python dependencies31 ├── mise.toml # Task vocabulary & pinned tools32 ├── mise.lock # Pinned tool binaries33 ├── AGENTS.md # Instructions for AI agents34 ├── README.md35 └── src/36 └── my_package/ # Main package directory37 ├── __init__.py38 ├── io/ # Side-effects (Datasets, APIs)39 ├── domain/ # Pure business logic (Models, Features)40 └── application/ # Orchestration (Training loops, Inference)41 ```42431. **Configuration**: Use `pyproject.toml` for all build metadata and dependencies.4445### 2. Modularity & Paradigm (Hybrid Style)4647Balance structure with predictability.48491. **Domain Layer (Pure)**:50 - **Rule**: Code here must be deterministic and free of side effects (no I/O).51 - **Use Case**: Feature transformations, Model architecture definitions.52 - **Style**: Functional (pure functions) or Immutable Objects (dataclasses).531. **I/O Layer (Impure)**:54 - **Rule**: Isolate external interactions here.55 - **Use Case**: Loading data from S3, saving models to disk, logging to MLflow.56 - **Style**: OOP (Classes to manage connections/state).571. **Application Layer (Orchestration)**:58 - **Rule**: Wire Domain and I/O together.59 - **Use Case**: Tuning, Training, Inference, Evaluation, etc.6061### 3. Application Entrypoints6263Create standard, installable CLI tools.64651. **Define Script**: Create `src/my_package/scripts.py` with a `main()` function.661. **Register**: Add to `pyproject.toml`:6768 ```toml69 [project.scripts]70 my-tool = "my_package.scripts:main"71 ```72731. **CLI Execution**:74 - **Dev**: `uv run my-tool` (No install needed).75 - **Prod**: `pip install .` -> `my-tool` (Installed on PATH).761. **Guard**: Always use `if __name__ == "__main__":` in scripts to prevent execution on import.7778### 4. Configuration Management7980Decouple settings from code using **OmegaConf** (Parsing) and **Pydantic** (Validation).81821. **Define Schema (Pydantic)**:83 - Create a class that defines _expected_ types and defaults.8485 ```python86 from pydantic import BaseModel878889 class TrainingConfig(BaseModel):90 batch_size: int = 3291 learning_rate: float = 0.00192 use_gpu: bool = False93 ```94951. **Parse & Validate (OmegaConf)**:96 - Load YAML, merge with CLI args, and validate against the schema.9798 ```python99 import omegaconf100101 # 1. Load YAML102 conf = omegaconf.OmegaConf.load("config.yaml")103 # 2. Merge with CLI (optional)104 cli_conf = omegaconf.OmegaConf.from_cli()105 merged = omegaconf.OmegaConf.merge(conf, cli_conf)106 # 3. Validate -> Returns a validated Pydantic object107 cfg: TrainingConfig = TrainingConfig(**omegaconf.OmegaConf.to_container(merged))108 ```1091101. **Secrets**: Use Environment Variables (`os.getenv`) or `pydantic-settings`, never commit them.111112### 5. MLflow Services as I/O Objects113114Tracking is a side effect, so it belongs in the I/O layer behind a small, configurable service.1151161. **Backend**: Default the tracking and registry URIs to a SQL store — `sqlite:///mlflow.db` locally, a Postgres or HTTP tracking server in production. The file store is deprecated in MLflow 3.15 and does not support the model registry, so it is not a valid default for a package meant to reach production.1171. **Configuration, not constants**: Expose `tracking_uri`, `registry_uri`, `experiment_name`, and `autolog` as validated fields, so the same package runs against a laptop database and a shared server without a code change.1181. **Lifecycle**: Give the service explicit `start()`/`stop()` methods called by the application layer, never at import time.119120### 6. Documentation & Quality121122Make code usable and maintainable.1231241. **Docstrings**: Use **Google Style** docstrings for all modules, classes, and functions.125126 ```python127 def calculate_metric(y_true: np.ndarray, y_pred: np.ndarray) -> float:128 """Calculates the accuracy score.129130 Args:131 y_true: Ground truth labels.132 y_pred: Predicted labels.133134 Returns:135 The accuracy as a float between 0 and 1.136 """137 ```1381391. **Type Hints**: Use modern Python typing (`list[str]`, `X | Y`) everywhere; `ty` (0.0.69+) checks them.1401. **Instructions**: Record the layer boundaries, the naming conventions, and the exact commands in `AGENTS.md` so assistants stop guessing where new code belongs.1411. **Gate**: `mise run all` (format -> check -> test -> build) must pass before a refactor is considered finished — see [mlops-validation](../mlops-validation/SKILL.md).142143### 7. Best Practices Summary144145- **Config != Code**: Never hardcode paths or hyperparams; use the `Pydantic + OmegaConf` pattern.146- **Entrypoints are APIs**: Design your CLI (`[project.scripts]`) as the public interface for your automation tools.147- **Immutable Core**: Keep your domain logic side-effect free; push I/O to the edges.148149## Self-Correction Checklist150151- [ ] **No Side Effects on Import**: Does `import my_package` run any code? (It shouldn't).152- [ ] **Src Layout**: Is code inside `src/`?153- [ ] **Config Safety**: Are secrets excluded from `pyproject.toml` and YAML?154- [ ] **Typing**: Are function signatures fully type-hinted and does `ty check` pass?155- [ ] **Entrypoints**: Is the CLI registered in `pyproject.toml`?156- [ ] **Tracking**: Does the MLflow service default to a SQL backend rather than the file store?157- [ ] **Gate**: Does `mise run all` pass?