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:
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.4Writing 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.
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=[],
)nameis the distribution name, what pip lists and what an uninstall asks for. It is free to differ from the import name, which here issp.versionmoves 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 undersrcthat holds an__init__.py.package_dir={"": "src"}maps the root of the package tree onto thesrcfolder, sosrc/spinstalls asspand not assrc.sp.install_requires=[]stays empty here because the pins live inrequirements.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.
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', 'sp.components', 'sp.config', 'sp.entity', 'sp.pipeline', 'sp.utils']
spis the top-level package, found becausesrc/sp/__init__.pyexists.- 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.
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.
# requirements.txt, last line
-e .$ python3 -m pip install -r requirements.txt
...
Successfully installed student-performance-0.1.0The -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 environment | A link to your src folder | A copy of the code |
| After you edit a component | The next import sees the change | Needs installing again |
| Use it for | Writing the project | A container image or a release |
Reads setup.py | Yes | Yes |
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 findsp. - A service that imports the model code, where
app.pysaysfrom sp.pipeline.predict_pipeline import PredictPipelineand 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.
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.Related
- Previous: The project template script
- Next: The logging module
- Reference: setuptools package discovery
- Delete one
__init__.pyand run thefind_packagescall again to see which name disappears. - Point
find_packagesat the project root instead ofsrcand read what it returns. - Change
versionto0.2.0and install the project again, watching the name pip prints.
Slow is fine. Stopping is the only problem.