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.
Main reading content
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.
Chapter 37 Topics
```- 37.1 Why Virtual Environments Matter
- 37.2 The
venvModule - 37.3 Creating Environments
- 37.4 Activating Environments
- 37.5 Deactivating Environments
- 37.6 Installing Dependencies
- 37.7
requirements.txt - 37.8 Freezing Dependencies
- 37.9 Dependency Versioning
- 37.10 Semantic Versioning
- 37.11 Dependency Conflicts
- 37.12 Pip Tools
- 37.13 Poetry
- 37.14 Pipenv
- 37.15 uv
- 37.16 Environment Variables
- 37.17
.envFiles - 37.18 Secrets Management Concepts
- 37.19 Reproducible Environments
- 37.20 Chapter Practice Exercises
- 37.21 Chapter Project
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
- Read the complete resolver message.
- Identify the package with incompatible requirements.
- Check whether a newer compatible release exists.
- Review the package release notes.
- Try a supported version range.
- Remove packages that are no longer needed.
- Create a clean environment and reinstall.
- 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.
- Create a project folder named
environment-practice. - Create a
.venvenvironment inside it. - Activate the environment on your operating system.
- Display the active Python executable path.
- Display the Python and pip versions.
- Install the
requestspackage. - Display information about the installed package.
- List every installed package.
- Freeze installed dependencies into
requirements.txt. - Delete and recreate the environment.
- Restore packages from
requirements.txt. - Create separate runtime and development requirements files.
- Use an exact version constraint.
- Use a minimum and maximum version range.
- Explain major, minor, and patch numbers.
- Create a sample dependency conflict description.
- Run
pip check. - Install pip-tools.
- Create
requirements.in. - Compile a pinned requirements file.
- Synchronize an environment with pip-tools.
- Create a Poetry project.
- Add a runtime dependency with Poetry.
- Add pytest as a Poetry development dependency.
- Create a Pipenv project.
- Run a script through Pipenv.
- Create a uv project.
- Add and remove a dependency with uv.
- Run tests through uv.
- Read an environment variable with
os.getenv(). - Provide a default configuration value.
- Require an environment variable using
os.environ. - Create a local
.envfile. - Load the file using python-dotenv.
- Create a safe
.env.examplefile. - Add
.envand.venvto.gitignore. - Remove a hard-coded secret from a sample program.
- Write setup instructions in a README file.
- Recreate the project on a clean environment.
- 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
- Create the project folder structure.
- Create an empty
__init__.pyinsidesrc/weather_app. - Copy each code section into its matching file.
- Create the environment with
python -m venv .venv. - Activate the environment.
- Upgrade pip.
- Install pip-tools.
- Compile both requirements files.
- Install development dependencies.
- Copy
.env.exampleto.env. - Change the local settings if needed.
- Run the program with
python main.py. - Run tests with
python -m pytest -v. - Run Ruff with
python -m ruff check .. - 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.
A modern course built to help learners study step by step with clarity, comfort, and confidence.