EASYTUTORGUIDE

Practical tutorials, tools, courses, digital skills, and business promotion.

Free Learning

JavaScript – Chapter 2: Development Environment

Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.

Beginner Friendly JavaScript Basics Web Development Code Examples
JavaScript Lesson 2 Chapter 2 Topics Day / Night Mode

Main reading content

python-course-chapter-37

Chapter 37: Virtual Environments and Dependency Management

A complete beginner-friendly guide to isolated Python environments, pip, requirements files, dependency versions, dependency conflicts, pip-tools, Poetry, Pipenv, uv, environment variables, secrets, and reproducible projects.

Goal: Learn how to keep every Python project isolated, install the correct packages, record dependency versions, protect configuration values, and recreate a working development environment on another computer.

Chapter 37 Topics

``` ```

37.1 Why Virtual Environments Matter

```

A virtual environment is an isolated Python workspace created for one project. It has its own Python executable and its own installed packages. Packages installed in one environment normally do not affect another environment.

Isolation is important because different projects may require different versions of the same package. For example, one project may need an older framework version while another project needs a newer version.

Problem Without Isolation

Project A requires:
```

framework==2.0

Project B requires:
framework==4.0
```

Installing both versions into one shared Python installation can cause one project to stop working. A separate virtual environment allows each project to keep the version it needs.

Recommended Structure

projects/
```

│
├── project-a/
│   ├── .venv/
│   ├── app.py
│   └── requirements.txt
│
└── project-b/
├── .venv/
├── app.py
└── requirements.txt
```

Main Benefits

  • Different projects can use different package versions.
  • Project dependencies remain organized.
  • Unnecessary global package installation is reduced.
  • Project setup becomes easier to reproduce.
  • Dependency problems are easier to diagnose.
  • Deleting an environment does not delete project source code.
```

37.2 The venv Module

```

Python includes the venv module for creating lightweight virtual environments. Each environment has an isolated package installation location and a Python interpreter connected to the base Python installation used to create it.

A common environment folder name is .venv. The environment folder should be treated as disposable because it can be recreated from dependency files. It should normally not be copied to another computer or committed to Git.

Basic Command

python -m venv .venv

Windows Alternative

py -m venv .venv

Typical Environment Contents

.venv/
```

│
├── pyvenv.cfg
├── Include/
├── Lib/
└── Scripts/
```

macOS and Linux Structure

.venv/
```

│
├── pyvenv.cfg
├── bin/
└── lib/
```

The exact folders differ by operating system. Windows commonly uses a Scripts folder, while macOS and Linux commonly use a bin folder.

```

37.3 Creating Environments

```

Create a virtual environment from inside the project folder. The Python version used to run the command becomes the base version for that environment.

Step 1: Create a Project Folder

mkdir student-project
```

cd student-project
```

Step 2: Create the Environment

python -m venv .venv

Windows with Python Launcher

py -m venv .venv

Create with a Particular Python Command

python3.12 -m venv .venv

The command above works only when that Python command is installed and available on the computer.

Create with a Custom Prompt

python -m venv .venv --prompt student-app

Upgrade pip During Creation

python -m venv .venv --upgrade-deps

Verify the Folder

student-project/
```

├── .venv/
└── app.py
```

Important Rule

Do not place project source files inside .venv. Keep application files beside the environment folder so the environment can be safely deleted and recreated.

```

37.4 Activating Environments

```

Activating a virtual environment adjusts the terminal's path so commands such as python and pip use the environment's executables.

Activation is convenient, but it is not strictly required. You can also run the environment's Python executable using its full path.

Windows Command Prompt

.venv\Scripts\activate.bat

Windows PowerShell

.venv\Scripts\Activate.ps1

macOS or Linux Bash and Zsh

source .venv/bin/activate

Expected Terminal Prompt

(.venv) C:\projects\student-project>

Verify the Active Python

python -c "import sys; print(sys.executable)"

Example Windows Output

C:\projects\student-project\.venv\Scripts\python.exe

Example macOS Output

/Users/student/projects/student-project/.venv/bin/python

Run Without Activating

# Windows
```

.venv\Scripts\python app.py

# macOS or Linux

.venv/bin/python app.py

37.5 Deactivating Environments

```

Deactivating an environment restores the terminal's previous path settings. It does not uninstall packages or delete the environment.

Deactivate Command

deactivate

Before Deactivation

(.venv) C:\projects\student-project>

After Deactivation

C:\projects\student-project>

Delete an Environment

First deactivate it. Then delete the .venv folder using the normal file manager or terminal command.

# Windows Command Prompt
```

rmdir /s /q .venv

# macOS or Linux

rm -rf .venv
```

Be careful with folder-removal commands. Confirm that you are deleting the correct environment folder and not the project source folder.

Recreate It

python -m venv .venv
```

# Activate it, and then:

python -m pip install -r requirements.txt

37.6 Installing Dependencies

```

A dependency is an external package required by a project. After activating a virtual environment, packages installed with pip are placed inside that environment.

Upgrade pip

python -m pip install --upgrade pip

Install One Package

python -m pip install requests

Install Several Packages

python -m pip install requests pytest python-dotenv

Install a Particular Version

python -m pip install requests==2.32.3

List Installed Packages

python -m pip list

Show Package Information

python -m pip show requests

Remove a Package

python -m pip uninstall requests

Simple Package Example

import requests
```

response = requests.get(
"https://example.com",
timeout=10
)

print(response.status_code)
```

Network requests may fail because of connection problems, server problems, or restricted networks, so real applications should handle request exceptions.

```

37.7 requirements.txt

```

A requirements file lists packages that pip should install. The common filename is requirements.txt, although pip does not require that exact name.

Simple Requirements File

requests
```

pytest
python-dotenv
```

Requirements with Versions

requests==2.32.3
```

pytest>=8.0,<9.0
python-dotenv~=1.0
```

Install from the File

python -m pip install -r requirements.txt

Comments in Requirements Files

# Runtime dependencies
```

requests==2.32.3
python-dotenv==1.0.1

# Testing dependency

pytest>=8.0,<9.0
```

Include Another Requirements File

-r requirements-base.txt
```

pytest
pytest-cov
```

Typical Separation

requirements.txt
```

requirements-dev.txt
```
# requirements.txt
```

requests
python-dotenv
```
# requirements-dev.txt
```

-r requirements.txt
pytest
pytest-cov
ruff
```

Pip treats requirements files as lists of installation arguments. They may include package names, version specifiers, constraints, other requirements files, and supported package sources.

```

37.8 Freezing Dependencies

```

The pip freeze command displays installed packages in requirements-file format. Redirecting the output into a file records the currently installed versions.

Freeze the Environment

python -m pip freeze > requirements.txt

Example Generated File

certifi==2026.2.25
```

charset-normalizer==3.4.3
idna==3.10
requests==2.32.3
urllib3==2.5.0
```

Reinstall the Recorded Packages

python -m pip install -r requirements.txt

View Without Saving

python -m pip freeze

Important Difference

pip freeze reports what is installed in the environment. It does not calculate a formal lock solution or distinguish direct dependencies from packages installed as dependencies of other packages.

Recommended Beginner Workflow

python -m venv .venv
```

source .venv/bin/activate
python -m pip install requests pytest
python -m pip freeze > requirements.txt

37.9 Dependency Versioning

```

Version constraints tell package tools which releases are acceptable. Choosing the right constraint involves balancing stability, security updates, compatibility, and reproducibility.

Exact Version

requests==2.32.3

Only the exact version is accepted. This improves repeatability but does not automatically allow later fixes.

Minimum Version

requests>=2.30

Version 2.30 or a newer release may be installed. A future incompatible release could also be selected unless an upper limit is added.

Version Range

requests>=2.30,<3.0

Versions from 2.30 up to, but not including, 3.0 are accepted.

Compatible Release

requests~=2.32.0

The compatible-release operator accepts releases compatible with the stated version level.

Exclude a Problematic Version

example-package>=2.0,!=2.1.4,<3.0

Versioning Advice

  • Use exact resolved versions for reproducible deployment files.
  • Use thoughtful ranges for project dependency declarations.
  • Test upgrades before releasing them.
  • Do not upgrade every package blindly.
  • Record which Python versions the project supports.
```

37.10 Semantic Versioning

```

Semantic versioning commonly expresses a release using three numbers: major, minor, and patch. A version such as 3.4.2 can be read as major version 3, minor version 4, and patch version 2.

Version Structure

MAJOR.MINOR.PATCH
```

3.4.2
```

Typical Meaning

  • Major: Incompatible or breaking changes.
  • Minor: New backward-compatible functionality.
  • Patch: Backward-compatible corrections.

Example Progression

1.0.0
```

1.0.1
1.1.0
2.0.0
```

Package maintainers do not always follow semantic versioning perfectly. Read release notes and test important upgrades instead of relying only on the version number.

Pre-Release Versions

2.0.0a1
```

2.0.0b1
2.0.0rc1
2.0.0
```

Alpha, beta, and release-candidate versions are generally intended for testing before the final stable release.

```

37.11 Dependency Conflicts

```

A dependency conflict happens when package requirements cannot be satisfied together. For example, one package may require a dependency below version 2 while another requires version 3 or newer.

Conflict Example

package-a requires shared-library<2.0
```

package-b requires shared-library>=3.0
```

No single version can satisfy both constraints.

Possible pip Message

ERROR: Cannot install package-a and package-b because
```

these package versions have conflicting dependencies.
```

Inspect Installed Dependencies

python -m pip check

Inspect a Dependency Tree

python -m pip install pipdeptree
```

python -m pipdeptree
```

Conflict Resolution Process

  1. Read the complete resolver message.
  2. Identify the package with incompatible requirements.
  3. Check whether a newer compatible release exists.
  4. Review the package release notes.
  5. Try a supported version range.
  6. Remove packages that are no longer needed.
  7. Create a clean environment and reinstall.
  8. Run the full test suite.

Do Not Force Random Versions

Installing packages with incompatible versions may temporarily complete installation but produce runtime errors later. Resolve the underlying requirements instead.

```

37.12 Pip Tools

```

Pip-tools provides command-line tools for compiling high-level dependency declarations into pinned requirements files and synchronizing environments with those files.

Install pip-tools

python -m pip install pip-tools

Create requirements.in

requests
```

python-dotenv
pytest
```

Compile Exact Dependencies

python -m piptools compile requirements.in

Generated requirements.txt

certifi==2026.2.25
```

charset-normalizer==3.4.3
idna==3.10
python-dotenv==1.1.1
requests==2.32.3
urllib3==2.5.0
```

Synchronize the Environment

python -m piptools sync requirements.txt

Upgrade Dependencies

python -m piptools compile --upgrade requirements.in

Two-File Idea

requirements.in
```

requirements.txt
```

The .in file describes the direct dependencies you choose. The compiled .txt file records the exact resolved dependency set.

```

37.13 Poetry

```

Poetry is a project and dependency management tool for Python. It can create projects, declare dependencies in pyproject.toml, produce a lock file, create environments, run commands, build packages, and publish packages.

Create a New Project

poetry new student-manager

Initialize an Existing Folder

poetry init

Add a Runtime Dependency

poetry add requests

Add a Development Dependency

poetry add --group dev pytest

Install Project Dependencies

poetry install

Run a Command in the Environment

poetry run python app.py
```

poetry run pytest
```

Example pyproject.toml

[project]
```

name = "student-manager"
version = "0.1.0"
description = "A beginner student manager"
requires-python = ">=3.11"
dependencies = [
"requests>=2.32,<3"
]

[dependency-groups]
dev = [
"pytest>=8,<9"
]
```

Important Files

pyproject.toml
```

poetry.lock
```

The project file declares intended requirements and metadata. The lock file records the resolved dependency set used for repeatable installation.

```

37.14 Pipenv

```

Pipenv combines virtual environment management with dependency declaration and locking. It commonly uses a Pipfile for declared dependencies and a Pipfile.lock for exact resolved versions.

Install Pipenv with pipx

pipx install pipenv

Create a Project Environment

pipenv install

Install a Package

pipenv install requests

Install a Development Package

pipenv install pytest --dev

Activate a Pipenv Shell

pipenv shell

Run Without Activating

pipenv run python app.py
```

pipenv run pytest
```

Generate or Update the Lock File

pipenv lock

Install Exact Locked Dependencies

pipenv sync
```

pipenv sync --dev
```

Example Pipfile

[[source]]
```

url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
requests = "*"
python-dotenv = "*"

[dev-packages]
pytest = "*"

[requires]
python_version = "3.12"
```

Display the Dependency Graph

pipenv graph
```

37.15 uv

```

uv is a Python project and package management tool that supports environments, dependency resolution, lock files, project commands, Python installation workflows, and pip-compatible operations.

Create a New Project

uv init student-manager
```

cd student-manager
```

Add a Dependency

uv add requests

Add a Development Dependency

uv add --dev pytest

Remove a Dependency

uv remove requests

Synchronize the Environment

uv sync

Run a Project Command

uv run python app.py
```

uv run pytest
```

Typical uv Project Files

student-manager/
```

│
├── .venv/
├── .python-version
├── pyproject.toml
├── uv.lock
└── app.py
```

Example pyproject.toml

[project]
```

name = "student-manager"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.3"
]

[dependency-groups]
dev = [
"pytest>=8.0"
]
```

Pip-Compatible Commands

uv venv
```

uv pip install requests
uv pip compile requirements.in
uv pip sync requirements.txt
```

In a uv-managed project, uv add updates the project dependency declaration, lock file, and environment. uv sync brings the environment into agreement with the lock file.

```

37.16 Environment Variables

```

Environment variables are named values supplied by the operating system, terminal, hosting platform, container, or development tool. Applications use them for configuration that should not be permanently written into source code.

Read an Environment Variable

import os
```

application_mode = os.getenv(
"APP_MODE",
"development"
)

print(application_mode)
```

Output When Variable Is Missing

development

Require a Variable

import os
```

database_url = os.environ[
"DATABASE_URL"
]

print(database_url)
```

Accessing os.environ["DATABASE_URL"] raises KeyError when the variable is missing. This may be useful when the application cannot safely start without the value.

Set a Variable on Windows Command Prompt

set APP_MODE=development
```

python app.py
```

Set a Variable on Windows PowerShell

$env:APP_MODE = "development"
```

python app.py
```

Set a Variable on macOS or Linux

export APP_MODE=development
```

python app.py
```

Use a Variable for Configuration

import os
```

debug_text = os.getenv(
"DEBUG",
"false"
)

debug_enabled = (
debug_text.lower()
in {"1", "true", "yes"}
)

print("Debug:", debug_enabled)

37.17 .env Files

```

A .env file stores environment-style configuration values in a text file. It is convenient during local development, but sensitive .env files should not be committed to public repositories.

Install python-dotenv

python -m pip install python-dotenv

Create .env

APP_NAME=Student Manager
```

APP_MODE=development
DEBUG=true
DATABASE_URL=sqlite:///students.db
```

Load the File

import os
```

from dotenv import load_dotenv

load_dotenv()

application_name = os.getenv(
"APP_NAME",
"Python App"
)

debug_enabled = (
os.getenv("DEBUG", "false").lower()
== "true"
)

print(application_name)
print(debug_enabled)
```

Output

Student Manager
```

True
```

Create a Safe Example File

# .env.example
```

APP_NAME=Student Manager
APP_MODE=development
DEBUG=false
DATABASE_URL=replace-with-database-url
```

Add the Real File to .gitignore

.venv/
```

.env
**pycache**/
.pytest_cache/

37.18 Secrets Management Concepts

```

A secret is a sensitive value that grants access to a system or protected resource. Examples include database passwords, private API credentials, authentication tokens, encryption keys, and service account credentials.

Unsafe Example

API_KEY = "real-private-key-here"

Hard-coding a secret can expose it through source control, screenshots, shared files, backups, logs, or published packages.

Safer Application Code

import os
```

def get_required_secret(
variable_name: str
) -> str:
value = os.getenv(variable_name)

```
if not value:
    raise RuntimeError(
        f"Missing required configuration: "
        f"{variable_name}"
    )

return value
```

api_key = get_required_secret(
"SERVICE_API_KEY"
)
```

Secrets Guidelines

  • Do not hard-code real secrets in source code.
  • Do not commit real secrets to Git.
  • Use environment variables or an approved secret-management service.
  • Give each service only the access it requires.
  • Rotate a secret after suspected exposure.
  • Avoid printing secrets in logs or error messages.
  • Use separate credentials for development and production.
  • Provide placeholder values in .env.example.

Redacted Logging

def display_key_status(
api_key: str
```

) -> None:
visible_suffix = api_key[-4:]

```
print(
    "API key loaded: ****"
    + visible_suffix
)
```

37.19 Reproducible Environments

```

A reproducible environment can be recreated on another computer with the same intended Python version and dependency set. Reproducibility reduces the problem where code works on one machine but fails elsewhere.

Information to Record

  • Supported Python version
  • Direct dependencies
  • Exact resolved dependencies
  • Development dependencies
  • Required environment variable names
  • Operating-system requirements
  • Installation and test commands

Simple pip-Based Project

project/
```

│
├── .python-version
├── requirements.txt
├── requirements-dev.txt
├── .env.example
├── .gitignore
├── README.md
└── app.py
```

Recreation Commands

python -m venv .venv
```

# Activate the environment, and then:

python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
python app.py
python -m pytest
```

Lock-File Project

pyproject.toml
```

uv.lock
```

Recreate with uv

uv sync
```

uv run pytest
uv run python app.py
```

Do Not Commit the Environment Folder

.venv/

Commit the files needed to recreate the environment, not the generated environment directory itself.

```

37.20 Chapter Practice Exercises

```

Complete these exercises to practise virtual environments, pip, requirements files, dependency constraints, modern dependency tools, environment variables, and reproducible setup.

  1. Create a project folder named environment-practice.
  2. Create a .venv environment inside it.
  3. Activate the environment on your operating system.
  4. Display the active Python executable path.
  5. Display the Python and pip versions.
  6. Install the requests package.
  7. Display information about the installed package.
  8. List every installed package.
  9. Freeze installed dependencies into requirements.txt.
  10. Delete and recreate the environment.
  11. Restore packages from requirements.txt.
  12. Create separate runtime and development requirements files.
  13. Use an exact version constraint.
  14. Use a minimum and maximum version range.
  15. Explain major, minor, and patch numbers.
  16. Create a sample dependency conflict description.
  17. Run pip check.
  18. Install pip-tools.
  19. Create requirements.in.
  20. Compile a pinned requirements file.
  21. Synchronize an environment with pip-tools.
  22. Create a Poetry project.
  23. Add a runtime dependency with Poetry.
  24. Add pytest as a Poetry development dependency.
  25. Create a Pipenv project.
  26. Run a script through Pipenv.
  27. Create a uv project.
  28. Add and remove a dependency with uv.
  29. Run tests through uv.
  30. Read an environment variable with os.getenv().
  31. Provide a default configuration value.
  32. Require an environment variable using os.environ.
  33. Create a local .env file.
  34. Load the file using python-dotenv.
  35. Create a safe .env.example file.
  36. Add .env and .venv to .gitignore.
  37. Remove a hard-coded secret from a sample program.
  38. Write setup instructions in a README file.
  39. Recreate the project on a clean environment.
  40. Build the complete chapter project below.

Practice Example

import os
```

def read_boolean(
variable_name: str,
default: bool = False
) -> bool:
default_text = (
"true"
if default
else "false"
)

```
value = os.getenv(
    variable_name,
    default_text
)

return value.lower() in {
    "1",
    "true",
    "yes",
    "on"
}
```

debug_enabled = read_boolean(
"DEBUG"
)

print("Debug enabled:", debug_enabled)
```

Example Output

Debug enabled: False
```

37.21 Chapter Project

```

This project creates a configurable command-line weather report application. It demonstrates project isolation, dependency declaration, environment variables, .env files, configuration validation, testing dependencies, and reproducible setup instructions.

Project Structure

weather-report-project/
```

│
├── src/
│   └── weather_app/
│       ├── **init**.py
│       ├── config.py
│       ├── models.py
│       ├── service.py
│       └── presentation.py
│
├── tests/
│   ├── test_config.py
│   └── test_service.py
│
├── main.py
├── requirements.in
├── requirements.txt
├── requirements-dev.in
├── requirements-dev.txt
├── .env.example
├── .gitignore
└── README.md
```

File 1: src/weather_app/config.py

"""Application configuration loaded from environment variables."""
```

import os
from dataclasses import dataclass

from dotenv import load_dotenv

load_dotenv()

@dataclass(frozen=True)
class Settings:
"""Store validated application configuration."""

```
application_name: str
default_city: str
temperature_unit: str
debug: bool
```

def read_boolean(
variable_name: str,
default: bool = False
) -> bool:
"""Read a Boolean environment variable."""
default_text = (
"true"
if default
else "false"
)

```
value = os.getenv(
    variable_name,
    default_text
)

return value.strip().lower() in {
    "1",
    "true",
    "yes",
    "on"
}
```

def load_settings() -> Settings:
"""Load and validate application settings."""
application_name = os.getenv(
"APP_NAME",
"Weather Report"
).strip()

```
default_city = os.getenv(
    "DEFAULT_CITY",
    "Toronto"
).strip()

temperature_unit = os.getenv(
    "TEMPERATURE_UNIT",
    "celsius"
).strip().lower()

if temperature_unit not in {
    "celsius",
    "fahrenheit"
}:
    raise ValueError(
        "TEMPERATURE_UNIT must be "
        "'celsius' or 'fahrenheit'."
    )

if not application_name:
    raise ValueError(
        "APP_NAME cannot be empty."
    )

if not default_city:
    raise ValueError(
        "DEFAULT_CITY cannot be empty."
    )

return Settings(
    application_name=application_name,
    default_city=default_city,
    temperature_unit=temperature_unit,
    debug=read_boolean("DEBUG")
)

File 2: src/weather_app/models.py

"""Data models for weather reports."""
```

from dataclasses import dataclass

@dataclass(frozen=True)
class WeatherReport:
"""Represent weather information for one city."""

```
city: str
temperature_celsius: float
condition: str

def temperature_fahrenheit(
    self
) -> float:
    """Convert Celsius to Fahrenheit."""
    return round(
        (
            self.temperature_celsius
            * 9 / 5
        )
        \+ 32,
        1
    )

File 3: src/weather_app/service.py

"""Weather data service used by the sample application."""
```

from weather_app.models import (
WeatherReport
)

SAMPLE_WEATHER: dict[
str,
WeatherReport
] = {
"toronto": WeatherReport(
city="Toronto",
temperature_celsius=22.5,
condition="Sunny"
),
"vancouver": WeatherReport(
city="Vancouver",
temperature_celsius=18.0,
condition="Cloudy"
),
"montreal": WeatherReport(
city="Montreal",
temperature_celsius=24.0,
condition="Partly cloudy"
)
}

class WeatherService:
"""Retrieve sample weather reports."""

```
def get_report(
    self,
    city: str
) -> WeatherReport | None:
    """Return a report for a city when available."""
    normalized_city = (
        city
        .strip()
        .lower()
    )

    return SAMPLE_WEATHER.get(
        normalized_city
    )

File 4: src/weather_app/presentation.py

"""Presentation functions for weather reports."""
```

from weather_app.models import (
WeatherReport
)

def format_report(
report: WeatherReport,
temperature_unit: str
) -> str:
"""Return a formatted weather report."""
if temperature_unit == "fahrenheit":
temperature = (
report.temperature_fahrenheit()
)

```
    unit_symbol = "°F"

else:
    temperature = round(
        report.temperature_celsius,
        1
    )

    unit_symbol = "°C"

return (
    f"Weather for {report.city}\n"
    f"Condition: {report.condition}\n"
    f"Temperature: "
    f"{temperature}{unit_symbol}"
)

File 5: main.py

"""Run the configurable weather report application."""
```

import sys
from pathlib import Path

PROJECT_ROOT = Path(**file**).parent
SOURCE_FOLDER = PROJECT_ROOT / "src"

sys.path.insert(
0,
str(SOURCE_FOLDER)
)

from weather_app.config import load_settings
from weather_app.presentation import (
format_report
)
from weather_app.service import (
WeatherService
)

def main() -> None:
"""Load configuration and display a weather report."""
settings = load_settings()
service = WeatherService()

```
print(settings.application_name)
print("=" * len(settings.application_name))

report = service.get_report(
    settings.default_city
)

if report is None:
    print(
        "No sample weather report "
        f"was found for "
        f"{settings.default_city}."
    )

    return

if settings.debug:
    print(
        "[DEBUG] Loaded settings:",
        settings
    )

print(
    format_report(
        report,
        settings.temperature_unit
    )
)
```

if **name** == "**main**":
main()
```

File 6: tests/test_config.py

"""Tests for environment-based configuration."""
```

import pytest

from weather_app.config import (
load_settings,
read_boolean
)

@pytest.mark.parametrize(
"value",
[
"true",
"TRUE",
"1",
"yes",
"on"
]
)
def test_read_boolean_true_values(
monkeypatch,
value: str
) -> None:
monkeypatch.setenv(
"FEATURE_ENABLED",
value
)

```
assert (
    read_boolean(
        "FEATURE_ENABLED"
    )
    is True
)
```

def test_default_settings(
monkeypatch
) -> None:
monkeypatch.delenv(
"APP_NAME",
raising=False
)

```
monkeypatch.delenv(
    "DEFAULT_CITY",
    raising=False
)

monkeypatch.delenv(
    "TEMPERATURE_UNIT",
    raising=False
)

settings = load_settings()

assert (
    settings.application_name
    == "Weather Report"
)

assert (
    settings.default_city
    == "Toronto"
)

assert (
    settings.temperature_unit
    == "celsius"
)
```

def test_invalid_temperature_unit(
monkeypatch
) -> None:
monkeypatch.setenv(
"TEMPERATURE_UNIT",
"kelvin"
)

```
with pytest.raises(
    ValueError,
    match="celsius.*fahrenheit"
):
    load_settings()

File 7: tests/test_service.py

"""Tests for the sample weather service."""
```

from weather_app.presentation import (
format_report
)

from weather_app.service import (
WeatherService
)

def test_find_known_city() -> None:
service = WeatherService()

```
report = service.get_report(
    "Toronto"
)

assert report is not None
assert report.city == "Toronto"
assert (
    report.temperature_celsius
    == 22.5
)
```

def test_city_lookup_ignores_case() -> None:
service = WeatherService()

```
report = service.get_report(
    "VANCOUVER"
)

assert report is not None
assert report.city == "Vancouver"
```

def test_unknown_city_returns_none() -> None:
service = WeatherService()

```
assert (
    service.get_report("Unknown")
    is None
)
```

def test_celsius_report() -> None:
service = WeatherService()

```
report = service.get_report(
    "Montreal"
)

assert report is not None

result = format_report(
    report,
    "celsius"
)

assert "24.0°C" in result
```

def test_fahrenheit_report() -> None:
service = WeatherService()

```
report = service.get_report(
    "Toronto"
)

assert report is not None

result = format_report(
    report,
    "fahrenheit"
)

assert "72.5°F" in result

File 8: requirements.in

python-dotenv

File 9: requirements-dev.in

-r requirements.in
```

pytest
pytest-cov
ruff
```

Compile Requirements Files

python -m piptools compile requirements.in
```

python -m piptools compile requirements-dev.in
```

File 10: .env.example

APP_NAME=Local Weather Report
```

DEFAULT_CITY=Toronto
TEMPERATURE_UNIT=celsius
DEBUG=false
```

Local .env Example

APP_NAME=My Weather Application
```

DEFAULT_CITY=Vancouver
TEMPERATURE_UNIT=fahrenheit
DEBUG=true
```

File 11: .gitignore

.venv/
```

.env
**pycache**/
*.py[cod]
.pytest_cache/
.coverage
htmlcov/
.ruff_cache/
```

File 12: README.md

# Weather Report Project
```

## Description

A small configurable Python weather report application.
It demonstrates virtual environments, dependency management,
environment variables, .env files, tests, and reproducible setup.

## Create the Environment

Windows:

py -m venv .venv
.venv\Scripts\activate

macOS or Linux:

python3 -m venv .venv
source .venv/bin/activate

## Install pip-tools

python -m pip install --upgrade pip
python -m pip install pip-tools

## Install Development Dependencies

python -m pip install -r requirements-dev.txt

## Configure the Application

Copy .env.example to .env and change the values.

## Run

python main.py

## Test

python -m pytest -v

## Coverage

python -m pytest --cov=weather_app --cov-report=term-missing

## Lint

python -m ruff check .

## Recreate the Environment

Delete .venv, create it again, activate it, and run:

python -m pip install -r requirements-dev.txt
```

How to Run the Project

  1. Create the project folder structure.
  2. Create an empty __init__.py inside src/weather_app.
  3. Copy each code section into its matching file.
  4. Create the environment with python -m venv .venv.
  5. Activate the environment.
  6. Upgrade pip.
  7. Install pip-tools.
  8. Compile both requirements files.
  9. Install development dependencies.
  10. Copy .env.example to .env.
  11. Change the local settings if needed.
  12. Run the program with python main.py.
  13. Run tests with python -m pytest -v.
  14. Run Ruff with python -m ruff check ..
  15. Delete and recreate the environment to prove the setup is reproducible.

Example Output

My Weather Application
```

======================
[DEBUG] Loaded settings: Settings(application_name='My Weather Application', default_city='Vancouver', temperature_unit='fahrenheit', debug=True)
Weather for Vancouver
Condition: Cloudy
Temperature: 64.4°F
```

Project Explanation

The project uses a virtual environment to isolate its packages. Direct dependencies are stored in requirements.in, while development tools are stored in requirements-dev.in.

Pip-tools compiles the high-level dependency files into exact installation files. Another developer can use those files to recreate the same package environment.

Application configuration is loaded from environment variables. The local .env file supplies convenient development values, while .env.example documents the required variable names without containing private values.

The real .env file and generated virtual environment are excluded from Git. Source code, dependency files, tests, documentation, and safe configuration examples are included.

Project Challenges

  • Add Ottawa and Calgary sample reports.
  • Add a command-line city argument.
  • Add humidity and wind-speed fields.
  • Create a real API service abstraction without hard-coding credentials.
  • Add an environment variable for request timeout.
  • Validate numeric environment variables.
  • Add separate development and testing configuration files.
  • Convert the project to Poetry.
  • Convert the project to Pipenv.
  • Convert the project to uv.
  • Compare the generated lock or requirements files.
  • Add static type checking.
  • Add pre-commit quality checks.
  • Create a clean environment and repeat every setup step.
  • Write a troubleshooting section for common installation errors.
```
Chapter 2: Development EnvironmentPrepare Your JavaScript Workspace

A modern course built to help learners study step by step with clarity, comfort, and confidence.