MLOpsThe MLOps lifecycle · MLflow 3.17 · DVC 3.67 · FastAPI · Docker · GitHub Actions · Evidently · Python 3.11+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
50 small wins to finish your pathNext lesson →

The project template script

A project template script is a short Python file that creates every folder and empty file a project needs, so the layout is decided once, written down as a list, and rebuilt in one command instead of clicked together by hand.

Last updated: 19 Oct, 2026 · MLOps

With the libraries from Installing the MLOps toolkit in place, the next thing the end-to-end video does is write template.py. It is the first file of the project and the only one whose job is to make other files. The layout it creates is the one the rest of the course fills in: components under src, config in YAML at the top level, and a place for the notebook experiments.

One Python script that creates the whole tree · from the Complete End to End Deep Learning Project With MLFLOW,DVC And Deployment · 12:57 to 17:07

The clip's script lays out the deep-learning project's cnnClassifier package; this one lays out the student-performance project's sp package, and the list-then-loop shape is the same.

Naming the project package

Two names decide everything else. PKG is the import name, the thing that appears in from sp.logger import logger once the project is installed. ROOT is the folder on disk.

python
from pathlib import Path

PKG = "sp"                            # the import name: "from sp.logger import logger"
ROOT = Path("student-performance")     # the project folder everything lives under

Listing the files the project needs

The list is the design. Reading it tells a newcomer where code goes before they open a single file, and adding a line is how the layout grows later.

python
FILES = [
    ".github/workflows/.gitkeep",      # git stores a folder only if it holds a file
    "requirements.txt", "setup.py", "app.py", "Dockerfile",
    "config/config.yaml", "params.yaml", "schema.yaml",
    "notebook/experiments.ipynb",

    # the package itself, which setup.py installs
    f"src/{PKG}/__init__.py", f"src/{PKG}/logger.py", f"src/{PKG}/exception.py",
    f"src/{PKG}/utils/common.py", f"src/{PKG}/entity/config_entity.py",
    f"src/{PKG}/config/configuration.py",
]
  • .github/workflows/.gitkeep holds the folder the CI/CD part writes its workflow into. Git stores files, not folders, so an empty marker file is what keeps the folder in the repository.
  • config/config.yaml, params.yaml, schema.yaml are the three YAML files the project reads instead of hard-coding paths, numbers and column types.
  • src/sp/logger.py and src/sp/exception.py are written in the next two lessons and imported by every component after that.
  • notebook/experiments.ipynb is where a stage is tried out before it becomes a component, which is the order the videos work in.

Adding the components and pipelines

The component and pipeline files follow a pattern, so a comprehension writes them. Each folder also gets an __init__.py, which is the file that turns a folder into an importable package.

python
FILES += [f"src/{PKG}/components/{name}.py" for name in
          ("data_ingestion", "data_validation", "data_transformation", "model_trainer")]
FILES += [f"src/{PKG}/pipeline/{name}.py" for name in ("train_pipeline", "predict_pipeline")]

# a folder under src is a package only when it holds an __init__.py, and setup.py
# finds the packages by looking for exactly that file
FILES += [f"src/{PKG}/{folder}/__init__.py" for folder in
          ("components", "config", "entity", "pipeline", "utils")]

Creating the folder tree

One loop does the work. Each path gets its parent folders made, then the file itself is touched into existence. The size check is what makes the script safe to run again: a file that already holds code is left alone.

python
def make_tree():
    touched = []
    for item in FILES:
        path = ROOT / item
        path.parent.mkdir(parents=True, exist_ok=True)

        # a file that already holds code is left alone, so re-running is safe
        if not path.exists() or path.stat().st_size == 0:
            path.touch()
            touched.append(item)
    return touched

Running the script twice

The second run is the interesting one. Between the two, one line of code goes into logger.py, which is what the size check exists to protect:

Exampletemplate.py, run from the project folder
import shutil

shutil.rmtree(ROOT, ignore_errors=True)   # start from nothing, so the run repeats
print("first run :", len(make_tree()), "files touched")

# a file with code in it is the one that must survive the next run
(ROOT / "src/sp/logger.py").write_text("logger = None")
print("second run:", len(make_tree()), "files touched")
print("logger.py :", (ROOT / "src/sp/logger.py").read_text().strip())
  • The first run touches every path in the list, folders included, because none of them existed.
  • The second run touches one fewer. logger.py now holds a line of code, so the size check sends it down the leave-it-alone branch.
  • The empty files are touched again, which costs nothing: a zero-byte file rewritten as a zero-byte file is the same file.
  • That is what makes the script safe to repeat: no file you have written into is ever opened for writing, so the script can stay in the repository and be re-run whenever the layout grows.

Reading the tree it made

Walking the folder shows what a reader checking out the repository sees:

ExampleThe layout the rest of the course fills in
# every path the script left behind, in sorted order
for path in sorted(ROOT.rglob("*")):
    print(path.relative_to(ROOT), "/" if path.is_dir() else "", sep="")
The folder tree the template script creates, drawn as a nested list under the project root student-performance: a .github/workflows folder holding a .gitkeep marker for the CI workflow, a config folder with config.yaml, the top-level params.yaml, schema.yaml, requirements.txt, setup.py, app.py and Dockerfile, a notebook folder with experiments.ipynb, and a src folder holding the sp package, which in turn holds logger.py, exception.py and the five subpackages components, config, entity, pipeline and utils, each with its own __init__.py and its stage files.

A template script against clicking folders together

New folder, new file, by handtemplate.py
Time for a second projectThe same clicking againChange two names, run it
Layout written downOnly in the finished repositoryAs a list at the top of the file
Missing __init__.pyEasy to forget, breaks the import laterIn the list, so never forgotten
Adding a stage laterRemember where it goesAdd a line, re-run
Safe to repeatNot a question that arisesYes, files with content are kept

Where a template script earns its keep

  • Starting a new model project, where the first hour goes on layout and the script turns it into one command.
  • A team with several projects, where the same tree in every repository means anyone can find the data ingestion code without asking.
  • Growing a project, where a new component is one line in the list rather than four files created by hand in the right places.
Watch out. Dropping the size check and writing every file unconditionally turns the script into a way to erase a day's work: the second run would truncate data_ingestion.py back to empty. Keep the check, and keep the script in the repository so the layout and the code that makes it stay together.
Try it yourself
  • Add src/sp/components/model_evaluation.py to the list and run the script again.
  • Write a line of code into one of the created files, run the script once more, and check the file still holds it.
  • Change PKG to a name of your own and read the tree the script builds.

You understood something today that you didn't yesterday.