In our last lesson, we established the importance of creating isolated virtual environments with venv to prevent dependency conflicts. We briefly touched upon using pip to install packages and creating a requirements.txt file to make your environment reproducible. Today, we will expand on that foundation to fully master the art of dependency management.
This lesson is dedicated to the learning outcome: Manage third-party dependencies using pip and requirements.txt files. This skill is non-negotiable for a professional backend engineer. For a company like Infilect, which builds and deploys scalable systems, ensuring that an application behaves identically on a developer's machine, a testing server, and in production is paramount. This is precisely what robust dependency management guarantees.
By the end of this session, you will be able to confidently create, manage, and share your project's dependencies, employing best practices that ensure consistency and collaboration.
1. The requirements.txt Workflow Revisited
As a quick refresher from our last session, the core of Python dependency management revolves around pip (the package installer) and a simple text file, requirements.txt. This file acts as a manifest, listing all the external libraries your project needs to run.
The workflow is straightforward:
- Generate the file: You capture the packages from your active virtual environment.
- Recreate the environment: Another developer (or a deployment server) uses this file to install the exact same packages.
The blog post from NamasteDev provides a good summary of these fundamental concepts and commands.
pip & requirements.txt - NamasteDev Blogs
This article clearly explains the roles of pip and requirements.txt and the basic commands for managing them.
Please read the following sections: What is Pip? Understanding requirements.txt Creating a requirements.txt File Installing Dependencies and the subsequent command block. Focus on how pip freeze automatically generates the file and pip install -r consumes it.
The image below shows what this looks like in practice. First, pip freeze > requirements.txt creates the file. Then, pip install -r requirements.txt reads the file and installs the listed packages. In this case, since the packages are already present, it confirms that the "Requirement [is] already satisfied."
The resulting requirements.txt file is a simple list of packages and their versions, which you will commit to your Git repository.

2. Version Pinning: The Key to Reproducibility
Notice the == symbol in the requirements.txt file. This is called version pinning. It instructs pip to install the exact version specified (e.g., requests==2.28.2).
Why is this so important?
- Consistency: It eliminates the "it works on my machine" problem by ensuring every environment (developer, testing, production) uses the exact same code.
- Stability: A new version of a library, even a minor one, could introduce a bug or a breaking change. Pinning versions prevents your application from breaking unexpectedly due to an automatic update of a dependency.
For building applications (like the backend systems at Infilect), pinning exact versions is the standard and recommended practice. While other operators exist (>= for minimum version, ~= for compatible release), they are more suited for libraries intended to be used by other developers, where some flexibility is desired. For your applications, always aim for the predictability that == provides.
3. The Problem with pip freeze
While pip freeze is effective, it has one significant drawback: it's not very smart. It dumps every single package installed in your virtual environment into the file, including the dependencies of your dependencies (known as transitive dependencies).
For example, when you install requests, pip also installs its dependencies, such as charset-normalizer, idna, and urllib3. When you run pip freeze, your requirements.txt will contain all of them.
This creates two problems:
- Noise: The file becomes cluttered, making it difficult to see what your project's direct dependencies are.
- Maintenance: If you want to upgrade
requests, which of its dependencies should you also upgrade or remove? It's not clear.
A much cleaner approach is to specify only the top-level packages your project directly imports and let pip resolve the sub-dependencies during installation.
4. A Better Way: Generating Requirements from Imports
Fortunately, there are tools that can generate a clean requirements file by scanning your source code for import statements. One of the most straightforward tools for this is pipreqs.
The following video clearly demonstrates the problem with pip freeze and how pipreqs solves it.
How To CREATE requirements.txt File The Better Way
This video from the Data Science Basics channel provides a practical comparison between pip freeze and the more intelligent pipreqs library.
Watch the introduction explaining the problem of dependency conflicts from cluttered requirements.txt files. See the demonstration of pip freeze creating a messy file with many transitive dependencies. Finally, watch how to install and use pipreqs to generate a clean file based only on the project's direct imports.
By using pipreqs, you create a requirements.txt file that serves as a clear declaration of your project's direct needs, which is much easier to read and maintain.
5. Hands-On Practice
Let's put this into practice and see the difference firsthand.
-
Set up your project:
Open your terminal, create a new project directory, andcdinto it.mkdir my-api-client cd my-api-client -
Create and activate a virtual environment:
# On Mac/Linux python3 -m venv venv source venv/bin/activate # On Windows python -m venv venv .\venv\Scripts\activate -
Create a simple Python script:
Create a file namedmain.pyand add the following code. This script will use therequestslibrary to fetch some data from a public API.import requests def get_user_data(user_id): url = f"https://jsonplaceholder.typicode.com/users/{user_id}" try: response = requests.get(url) response.raise_for_status() # Raises an exception for bad status codes (4xx or 5xx) print("User Data:") print(response.json()) except requests.exceptions.RequestException as e: print(f"An error occurred: {e}") if __name__ == "__main__": get_user_data(1) -
Install the package and generate requirements with
pip freeze:
First, install therequestspackage. Then, usepip freezeto generate a requirements file.pip install requests pip freeze > requirements_frozen.txtNow, open
requirements_frozen.txt. You will seerequestsalong with several other packages it depends on (likecertifi,charset-normalizer,idna,urllib3). -
Generate clean requirements with
pipreqs:
Now, let's try the better way. First, installpipreqsitself. Then, run it in your project directory.pip install pipreqs pipreqs .This command will scan
main.py, find theimport requestsline, and create a newrequirements.txtfile.Now, open
requirements.txt. You will see it contains only one line:requests==.... This is a much cleaner and more accurate representation of your project's direct dependencies.
When a collaborator clones your project and runs pip install -r requirements.txt, pip will automatically fetch requests and all its necessary sub-dependencies. The outcome is the same, but your dependency manifest is far more maintainable.
Conclusion
You have now moved beyond the basics and learned the professional standard for managing Python dependencies. This is a critical building block for creating reliable and scalable backend applications.
Key Takeaways:
- A
requirements.txtfile is essential for reproducible environments, a cornerstone of professional software development. - Pinning versions with
==is the standard for applications to ensure stability and consistency. pip freezeis a blunt tool that includes all transitive dependencies, creating noise.- Tools like
pipreqsprovide a much cleaner solution by generating requirements based on your project's actualimportstatements. - Always commit your
requirements.txtto Git, but add yourvenvfolder to.gitignore.
In the next lesson, we will shift our focus from the local development environment to the outside world. We'll explore the HTTP request-response cycle, the fundamental communication protocol of the web. This will lay the groundwork for understanding how your future Django applications will interact with clients and other services.