This final part combines everything from the series into a professional Python project. We set up the project properly, structure the code, write tests, add logging, and build a real server monitoring automation tool. This is how Python projects look in production teams -- not toy examples, but maintainable, testable, deployable code.
mkdir server-monitor && cd server-monitor
python3 -m venv venv
source venv/bin/activate
mkdir -p src tests
touch src/__init__.py
touch src/monitor.py
touch src/alerts.py
touch tests/__init__.py
touch tests/test_monitor.py
touch .env.example .gitignore README.md
pip install psutil requests python-dotenv pytest
pip freeze > requirements.txt
import psutil
import logging
from dataclasses import dataclass
from datetime import datetime
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
@dataclass
class Metrics:
cpu: float
memory: float
disk: float
timestamp: datetime
def is_critical(self, cpu_thresh=90, mem_thresh=85):
return self.cpu > cpu_thresh or self.memory > mem_thresh
def collect():
return Metrics(
cpu=psutil.cpu_percent(interval=1),
memory=psutil.virtual_memory().percent,
disk=psutil.disk_usage("/").percent,
timestamp=datetime.now()
)
def check_and_report():
m = collect()
logger.info(f"CPU: {m.cpu}% | Memory: {m.memory}% | Disk: {m.disk}%")
if m.is_critical():
logger.warning("ALERT: Critical resource usage!")
return False
return True
import pytest
from datetime import datetime
from src.monitor import Metrics
def test_not_critical():
m = Metrics(50.0, 60.0, 40.0, datetime.now())
assert not m.is_critical()
def test_critical_cpu():
m = Metrics(95.0, 50.0, 50.0, datetime.now())
assert m.is_critical()
def test_custom_threshold():
m = Metrics(75.0, 70.0, 65.0, datetime.now())
assert m.is_critical(cpu_thresh=70) # 75 > 70
assert not m.is_critical(cpu_thresh=80)
pytest tests/ -v
# tests/test_monitor.py::test_not_critical PASSED
# tests/test_monitor.py::test_critical_cpu PASSED
# tests/test_monitor.py::test_custom_threshold PASSED
pytest tests/ -v --tb=short # Shorter error output
pytest tests/ -x # Stop on first failure
# .env.example (commit this -- template for others)
CPU_THRESHOLD=90
MEMORY_THRESHOLD=85
ALERT_WEBHOOK=https://hooks.slack.com/services/YOUR/HOOK
# .gitignore (never commit .env)
.env
venv/
__pycache__/
*.pyc
.pytest_cache/
from dotenv import load_dotenv
import os
load_dotenv() # Load from .env file
CPU_THRESH = int(os.getenv("CPU_THRESHOLD", "90"))
MEM_THRESH = int(os.getenv("MEMORY_THRESHOLD", "85"))
WEBHOOK = os.getenv("ALERT_WEBHOOK") # None if not set
venv/
.env
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/
.DS_Store
Isolates each project's dependencies. Prevents version conflicts between projects. Always create a venv per project.
src/ or package-name/ for code, tests/ for tests, requirements.txt for deps, .env.example for config, README.md, .gitignore. Keep it simple until you need more.
Create test_*.py files in tests/. Write def test_*() functions with assert statements. Run with pytest. Use -v for verbose, -x to stop on first failure.
Use environment variables loaded from a .env file with python-dotenv. Never commit .env to Git. Commit .env.example as a template. Use os.getenv() with default values.
pip freeze > requirements.txt. Git commit everything except .env and venv/. Add README.md with setup steps. Others: git clone, python -m venv venv, pip install -r requirements.txt, then run.
You have completed the full Python series. From first print() to a tested, configurable production tool. The DevOps Roadmap shows how Python fits into a complete cloud engineering career. The Docker series shows how to containerise and deploy everything you build.
name: Python CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Lint with flake8
run: flake8 src/ tests/
- name: Type check with mypy
run: mypy src/
- name: Security scan
run: bandit -r src/ -ll # -ll only medium+ severity
- name: Run tests with coverage
run: pytest tests/ -v --cov=src --cov-report=xml --cov-fail-under=80
- name: Upload coverage
uses: codecov/codecov-action@v3
deploy:
needs: quality
runs-on: ubuntu-latest
if: github.ref == "refs/heads/main"
steps:
- uses: actions/checkout@v4
- name: Deploy to server
run: |
ssh -i ${{ secrets.SSH_KEY }} ubuntu@${{ secrets.SERVER_IP }} "
cd /opt/server-monitor &&
git pull &&
pip install -r requirements.txt &&
systemctl restart server-monitor
"
from typing import Optional, List, Dict, Union, Callable
from dataclasses import dataclass
@dataclass
class MetricPoint:
timestamp: float
value: float
labels: Dict[str, str]
def collect_metrics(
sources: List[str],
callback: Optional[Callable[[MetricPoint], None]] = None,
timeout: float = 30.0
) -> Dict[str, List[MetricPoint]]:
results: Dict[str, List[MetricPoint]] = {}
for source in sources:
# ... fetch metrics ...
pass
return results
# Run mypy: mypy src/ --strict
# Catches type errors before runtime
# pyproject.toml
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"
[project]
name = "server-monitor"
version = "1.0.0"
description = "Monitor server health metrics"
authors = [{name = "Suraj Ahir", email = "suraj@srjahir.in"}]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"psutil>=5.9",
"requests>=2.31",
"python-dotenv>=1.0",
]
[project.scripts]
server-monitor = "server_monitor.cli:main" # Creates CLI command
[project.optional-dependencies]
dev = ["pytest", "black", "mypy", "bandit"]
pip install build twine
python -m build # Creates dist/*.whl and dist/*.tar.gz
twine check dist/* # Validate before publishing
twine upload dist/* # Upload to PyPI
# Or publish to TestPyPI first
twine upload --repository testpypi dist/*
pip install -i https://test.pypi.org/simple/ server-monitor