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 →

Packaging with setup.py

setup.py is the script that declares a project as an installable Python package, so from sp.components.data_ingestion import ingest works from any folder instead of only from the one the code sits in.

Last updated: 19 Oct, 2026 · MLOps

The project template script left a tree with an empty setup.py and an empty requirements.txt in it. Filling those two files is what turns the folder into a package. The end-to-end videos do this before writing a single component, and for a good reason: every component after this point imports from sp, and that import only resolves once the package is installed.

Listing the libraries in requirements.txt

requirements.txt is the pin list for the project itself, separate from the libraries the course uses for tracking and monitoring. These are what the model code and the service need:

text
pandas==2.3.3
scikit-learn==1.9.1
joblib==1.5.2
fastapi==0.146.0
uvicorn==0.54.0
pydantic==2.14.1
PyYAML==6.0.3
python-box==7.4.1
ensure==1.0.4

Writing setup.py

The whole file is ten lines. find_packages walks a folder and returns every package it finds, and package_dir tells the installer that the packages live under src rather than at the top level.

python
from setuptools import find_packages, setup

setup(
    name="student-performance",           # the name pip lists and uninstalls
    version="0.1.0",
    author="krishnaik.in",
    packages=find_packages(where="src"),
    package_dir={"": "src"},              # src/sp installs as sp, not as src.sp
    install_requires=[],
)
  • name is the distribution name, what pip lists and what an uninstall asks for. It is free to differ from the import name, which here is sp.
  • version moves with the project. Later lessons bump it when the model contract changes, which is what a container tag ends up pointing at.
  • packages=find_packages(where="src") finds every folder under src that holds an __init__.py.
  • package_dir={"": "src"} maps the root of the package tree onto the src folder, so src/sp installs as sp and not as src.sp.
  • install_requires=[] stays empty here because the pins live in requirements.txt, which the next section wires to this file.

Reading what find_packages found

The function is the part worth seeing run, because a missing __init__.py is a silent failure: the package does not appear in the list, and the import that needs it fails much later.

ExampleEvery package pip will install
from setuptools import find_packages

# the same call setup.py makes, against the tree the template script built
print(sorted(find_packages(where="student-performance/src")))
  • sp is the top-level package, found because src/sp/__init__.py exists.
  • The five dotted names are the subpackages, one per folder the template script gave an __init__.py: components, config, entity, pipeline and utils.
  • A folder missing from this list is a folder missing its __init__.py. The install succeeds anyway and the import fails afterwards, which is why the file is in the template list.
Why <code>-e .</code> turns the source folder into an importable package · from the Complete End to End Deep Learning Project With MLFLOW,DVC And Deployment · 31:20 to 33:49

The clip keeps the project's name, version and packages in setup.py, which is still what the editable install below reads; setuptools now takes that metadata from pyproject.toml, so a new project would declare it there.

Installing the project with the -e . line

The last line of requirements.txt in the videos is -e ., and it is the hinge between the two files. A single install command then does both jobs: it installs the pinned libraries and it runs setup.py to install the project itself.

text
# requirements.txt, last line
-e .
bash
$ python3 -m pip install -r requirements.txt
...
Successfully installed student-performance-0.1.0

The -e stands for editable. Instead of copying the code into the environment's site-packages folder, pip records a link back to src. Editing data_ingestion.py changes what the next import sees, with no reinstall, which is the behaviour you want while writing a project and not the one you want in a container image.

An editable install against a plain one

pip install -e .pip install .
What lands in the environmentA link to your src folderA copy of the code
After you edit a componentThe next import sees the changeNeeds installing again
Use it forWriting the projectA container image or a release
Reads setup.pyYesYes

Where packaging changes what you can write

  • Imports that do not depend on where you stand, so a pipeline run from the project root and a test run from tests/ both find sp.
  • A service that imports the model code, where app.py says from sp.pipeline.predict_pipeline import PredictPipeline and does not care about relative paths.
  • A container image, where the build installs the project the same way the reader did, so the code that runs in the image is the code that was tested.
Watch out. Skipping the install and relying on being in the right folder works until the first component imports another one, and then it fails with ModuleNotFoundError: No module named 'sp'. Either install the project with -e . or set PYTHONPATH=src for the run. Picking one and writing it in the readme saves the next person an hour.
Try it yourself
  • Delete one __init__.py and run the find_packages call again to see which name disappears.
  • Point find_packages at the project root instead of src and read what it returns.
  • Change version to 0.2.0 and install the project again, watching the name pip prints.

Slow is fine. Stopping is the only problem.