MLOps Initialization
Goal
To initialize a robust, production-ready MLOps project structure using the modern Python toolchain (uv), industry-standard version control (git), a shared task runner (mise), and a configured development environment (VS Code). This skill ensures reproducibility, collaboration, and high code quality from day one.
Prerequisites
- Language: Python 3.14 (latest stable)
- Manager:
uv(replaces pip, venv, poetry, pyenv) - Tasks:
mise(replaces make/just, and pins the toolchain) - VCS: Git
- IDE: VS Code (recommended)
Instructions
1. System & Toolchain Verification
Before modifying files, verify that the essential tools are available.
- Check
uv:- Ensure
uvis installed:uv --version - If missing, install it:
curl -LsSf https://astral.sh/uv/install.sh | sh
- Ensure
- Check
git:- Ensure
gitis installed:git --version
- Ensure
- Check
mise:- Ensure
miseis installed:mise --version misepins every non-Python tool (dprint,gitleaks,trivy,actionlint, ...) so contributors and CI resolve identical binaries.
- Ensure
2. Project Initialization
Initialize the project structure using uv to ensure modern standards (pyproject.toml).
- Create Directory (if not already inside):
mkdir <project_name> && cd <project_name>
- Initialize Project:
- Run
uv init - This creates
pyproject.toml,.python-version, and a basichello.py.
- Run
- Configure
pyproject.toml:Update metadata:
name,version,description,authors,license.Set requires-python: Ensure it matches the project's target environment (e.g.,
>=3.14).Declare the license the PEP 639 way:
licenseis an SPDX expression (a plain string), and the file itself is listed inlicense-files. The oldlicense = { file = "LICENSE" }table form is deprecated and rejected by current build backends.Example Structure:
[project] name = "my-mlops-project" version = "0.1.0" description = "A robust MLOps project." readme = "README.md" requires-python = ">=3.14" license = "MIT" # SPDX expression (PEP 639) license-files = ["LICENSE.txt"] # the file(s) shipped in the distribution authors = [{ name = "Your Name", email = "your.email@example.com" }] dependencies = [ "loguru>=0.7.3", "mlflow>=3.15.1", # MLflow 3.15 still declares `pandas<3`, so the pandas 3.x line is unreachable # here; keep the floor permissive and let `uv.lock` pin the tested version. "pandas>=2.3.3", "pydantic>=2.13.4", "scikit-learn>=1.9.0", ] [project.urls] Repository = "https://github.com/username/my-mlops-project" Documentation = "https://username.github.io/my-mlops-project" # PEP 735 dependency groups (not shipped with the package). [dependency-groups] dev = [ "lefthook>=2.1.10", "pip-audit>=2.10.1", "pytest>=9.1.1", # Ruff 0.16 rewrote the default rule set and now formats Python inside Markdown: # an older Ruff disagrees with a 0.16-formatted repository, so floor it here. "ruff>=0.16.2", "ty>=0.0.69,<0.1", # pre-1.0: pin a compatible range ] [build-system] # Keep the upper bound at least one minor ahead of the pinned `uv`: without it # `uv build` warns, and a future breaking `uv_build` silently breaks the sdist. requires = ["uv_build>=0.9,<0.13"] build-backend = "uv_build"
3. Dependency Management
Establish a clean separation between production and development dependencies.
- Add Runtime Dependencies (Production):
- Use
uv add <package>for libraries needed in production (e.g.,mlflow,pandas,scikit-learn). - These go into
[project.dependencies]inpyproject.toml.
- Use
- Add Dev Dependencies (Development):
- Use
uv add --dev <package>(or--group dev) for tools likepytest,ruff,ty. - These go into
[dependency-groups](PEP 735) and are kept out of production builds.
- Use
- Sync Environment:
- Run
uv syncto resolve dependencies, create the.venv, and generate theuv.lockfile. - Critical: The
uv.lockfile pins exact versions of all dependencies (including transitive ones). It ensures that every developer and CI/CD pipeline uses the exact same environment, preventing "it works on my machine" issues. Commit this file to git.
- Run
4. Version Control (Git)
Set up a clean repository and ensure unwanted files are ignored.
- Initialize Git:
git initgit branch -M main
- Create
.gitignore:- Write a robust
.gitignoretailored for Python/MLOps. - Must Include:
- Environment:
.venv/,.env - Caches:
__pycache__/,.pytest_cache/,.ruff_cache/,.ty_cache/ - Builds:
dist/,build/,*.egg-info/ - Data/Models:
data/,models/,outputs/(unless using DVC/LFS) - MLflow local state:
mlflow.db,mlartifacts/,mlruns/ - IDE:
.vscode/(selectively),.idea/,.DS_Store
- Environment:
- Note: It is often good practice to commit project-specific
.vscode/settings.jsonbut ignoreUsersettings.
- Write a robust
- Verify Status:
git statusshould show only source files, config files, and the lockfile.
5. Task Runner & Project Instructions
Give every contributor — human or agent — one entrypoint and one written contract.
- Task Vocabulary: Create
mise.tomlwith the canonical tasksinstall,format,check,test,build, plusall=format->check->test->build.mise run allis the gate: git hooks and CI run that exact task and nothing else, so a green local run means a green pipeline. The mlops-automation skill details the tasks. - Pinned Toolchain: Declare non-Python tools in
[tools]and runmise lock, then commitmise.lock. Python dependencies are pinned byuv.lock; everything else is pinned bymise.lock. README.md(humans): what the project is, how to install it, how to run it.AGENTS.md(AI agents): the project overview, the exact commands, the definition of done, the conventions, and the repository layout. Keep it short and current — a staleAGENTS.mdmisleads every assistant that reads it.
6. IDE Configuration (VS Code)
Standardize the developer experience (DX) by committing project-specific settings.
- Install Recommended Extensions:
- Python Tier A:
ms-python.python,charliermarsh.ruff,astral-sh.ty,ms-toolsai.jupyter. - Productivity:
eamodio.gitlens,alefragnani.project-manager,usernamehw.errorlens. - One checker only: the project type-checks with
ty, so install Astral'sastral-sh.tyextension rather than Pylance. It ships the ty language server and setspython.languageServerto"None"itself, which prevents two checkers from reporting contradictory diagnostics on the same file.
- Python Tier A:
- Create
.vscodeDirectory:mkdir .vscode
- Create
settings.json:Configure settings to enforce code quality and use the
uvenvironment.Key Settings:
{ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } }, "python.defaultInterpreterPath": ".venv/bin/python", "python.terminal.activateEnvironment": true, "python.testing.pytestEnabled": true, "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "editor.rulers": [120], "files.exclude": { "**/__pycache__": true, "**/.pytest_cache": true, "**/.ruff_cache": true, "**/.ty_cache": true, "**/.venv": true } }Keep the ruler and the linter in agreement:
editor.rulersmust equal[tool.ruff] line-length(120 in this stack). A ruler at 88 against a 120-character limit makes the editor flag code thatruffaccepts, and developers reformat by hand to satisfy a line that no check enforces.
7. Verification & First Commit
Finalize the initialization.
- Verify Environment:
- Run
uv run python -c "import sys; print(sys.executable)"to confirm it uses the.venv.
- Run
- Verify the Gate:
- Run
mise run alland fix whatever it reports before the first commit.
- Run
- Initial Commit:
git add .git commit -m "chore: initialize project with uv, git, mise, and vscode settings"
8. Best Practices Summary
- One Command Setup: ideally,
mise run install(which wrapsuv sync) should be the only command needed to set up the environment. - One Command Gate:
mise run allis the single answer to "is this ready?" — locally, in hooks, and in CI. - Lockfiles: Commit both
uv.lock(Python) andmise.lock(tools) so all environments are identical. - Editor Config: Checked-in
.vscode/settings.jsonreduces onboarding friction and enforces standards (formatting, linting). - Dependency Separation: Keep production dependencies light; put testing/linting tools in
dev.
Self-Correction Checklist
- Lockfiles: Do
uv.lockandmise.lockexist and are they committed? - Virtual Env: Is
.venv/created and ignored in.gitignore? - Project Config: Does
pyproject.tomlvalidly describe the project, with an SPDXlicenseandlicense-files? - Git Cleanliness: Are secrets, large data files, and local MLflow state excluded?
- Instructions: Do
README.md(humans) andAGENTS.md(agents) exist and match the real commands? - Gate: Does
mise run allpass on a fresh clone? - Reproducibility: Can another developer
git cloneanduv syncto get the exact same state?