0% found this document useful (0 votes)
24 views29 pages

Software Essentials Notes

This document provides comprehensive notes on essential software engineering practices, focusing on Git and version control, branching and merging, pull requests, and development environment setup. It covers key Git commands, best practices for commit messages, and the importance of virtual environments for project isolation. Additionally, it introduces dependency management techniques and modern tools like Poetry for managing project dependencies.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
24 views29 pages

Software Essentials Notes

This document provides comprehensive notes on essential software engineering practices, focusing on Git and version control, branching and merging, pull requests, and development environment setup. It covers key Git commands, best practices for commit messages, and the importance of virtual environments for project isolation. Additionally, it introduces dependency management techniques and modern tools like Poetry for managing project dependencies.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

⚙️

Software Engineering
Essentials
Complete Notes for Industry-Level Projects

🔧 Git & Version Dev Environments 🌐 APIs & Services


Control
🔧 Version Control with Git

1.1 What Is Version Control?


Imagine you're writing a book. You save draft_v1.docx, then draft_v2.docx, then draft_final.docx, then
draft_FINAL_real.docx. This becomes chaos. Version control solves this — it's a system that tracks every change
you make to your code over time, who made it, and why.

💡 The Core Idea


Version control is like a "time machine" for your code.
Every snapshot of your project is saved. You can travel back to any point.
Multiple people can work on the same project without overwriting each other's work.

WITHOUT Version Control WITH Git


• Copy-paste folders (project_v1, • Every change recorded with timestamp +
project_v2...) message
• No record of WHY changes were made • Full history of WHO changed WHAT and
• Accidentally overwrite team member's WHY
code • Multiple people work in parallel safely
• Cannot easily undo bugs introduced weeks • One command to undo any mistake
ago • Code hosted on GitHub, accessible
• Sharing code via email/USB drives anywhere

1.2 Git Fundamentals


Understanding the Git Architecture
Git stores your project in three key areas. Understanding these is the foundation for everything else:

Working Directory Staging Area (Index) Repository (.git)


Files you see and edit on your Preparation zone: "I want to save The permanent history database
computer THESE changes" of all commits
git add [Link] git commit -m "message" Stored forever in .git/
Think of it like this: the staging area is like packing a box (you select which items go in), and a commit is like
sealing and labeling that box and putting it on a permanent shelf.

Essential Git Commands


Here are the commands you'll use every single day. Learn these inside-out:

# Git Daily Commands


# ──────────────────────────────────────────────────────
# SETUP — Do this once when you first install Git
# ──────────────────────────────────────────────────────
git config --global [Link] "Your Name"
git config --global [Link] "you@[Link]"
git config --global [Link] main

# ──────────────────────────────────────────────────────
# STARTING A PROJECT
# ──────────────────────────────────────────────────────
git init # Turn any folder into a Git repo
git clone <url> # Download an existing repo from GitHub

# ──────────────────────────────────────────────────────
# THE DAILY WORKFLOW
# ──────────────────────────────────────────────────────
git status # See what files changed (check this OFTEN)
git add [Link] # Stage one specific file
git add . # Stage ALL changed files
git commit -m "Add login" # Save staged changes with a message

# ──────────────────────────────────────────────────────
# REVIEWING HISTORY
# ──────────────────────────────────────────────────────
git log # Full history of all commits
git log --oneline # Compact one-line per commit view
git diff # See exactly what lines changed
git show abc1234 # Inspect a specific commit

Writing Good Commit Messages


A commit message is a note to your future self and your team. Good messages save hours of debugging.

❌ BAD commit messages ✅ GOOD commit messages

"fix" "Add user authentication with JWT"


"stuff" "Fix null pointer in checkout flow"
"asdf" "Refactor database queries for perf"
"wip" "Update README with setup steps"
"changes"

Clear: what changed AND why it matters


Nobody knows what changed or why!

📐 Commit Message Format (Industry Standard)


Format: <type>(<scope>): <short description>

Types: feat (new feature), fix (bug fix), docs (documentation), refactor, test, chore

Examples:
feat(auth): add OAuth2 Google login
fix(cart): resolve price calculation error for discounts
docs(api): update endpoint documentation for v2

1.3 Branching and Merging


What Is a Branch?
A branch is an independent line of development. Think of it as creating a parallel universe copy of your project
where you can experiment freely. The main branch is your "production" code — always stable. Feature branches
are where you build new things.

# Branch diagram
main branch
────●────────●────────────────────────●────▶
│ \ /
│ \ feature/login /
│ ●────●────●─────────●
│ ▲
│ merged back

└──● hotfix/crash (urgent fix on separate branch)
└──●──●──●────▶ merged to main immediately

Branch Commands
# Branch Commands
# ──────────────────────────────────────────────────────
# CREATING AND SWITCHING BRANCHES
# ──────────────────────────────────────────────────────
git branch # List all local branches
git branch feature/user-login # Create a new branch
git checkout feature/user-login # Switch to that branch

# The modern shortcut (create + switch in one command):


git checkout -b feature/user-login

# Even newer syntax (Git 2.23+):


git switch -c feature/user-login

# ──────────────────────────────────────────────────────
# MERGING BRANCHES
# ──────────────────────────────────────────────────────
git checkout main # First, switch to destination
git merge feature/user-login # Bring in changes from feature branch

# ──────────────────────────────────────────────────────
# CLEANING UP
# ──────────────────────────────────────────────────────
git branch -d feature/user-login # Delete after merging
git branch -D feature/user-login # Force delete (unmerged)

Git Flow: The Industry Branching Strategy


Professional teams follow a naming convention for branches to keep things organized:

Branch Type Purpose & Naming Convention

main / master Production-ready code. Only merge here when features are complete and
tested.

develop Integration branch. Features merge here first before going to main.

feature/xxx New features. e.g., feature/user-auth, feature/payment-gateway

fix/xxx or hotfix/xxx Bug fixes. hotfix/ branches are for urgent production bugs.

release/xxx Release preparation. e.g., release/v2.1.0

chore/xxx Non-feature work: updating dependencies, CI config, etc.

1.4 Merge Conflict Resolution


A merge conflict happens when two branches edited the same line of code differently. Git doesn't know which
version to keep — so it asks YOU to decide. This sounds scary but is completely normal.
What a Conflict Looks Like
# Conflict example
# When you run: git merge feature/button-color
# Git might report:
Auto-merging [Link]
CONFLICT (content): Merge conflict in [Link]
Automatic merge failed; fix conflicts and then commit the result.

# Open [Link] — you will see conflict markers:

.button {
<<<<<<< HEAD ← your current branch (main)
background: blue;
======= ← separator
background: green;
>>>>>>> feature/button-color ← incoming branch
}

How to Resolve
Open the file
1 Find all conflict markers (<<<<<<, =======, >>>>>>>). There may be multiple in one file.

Edit to keep the correct code


2 Delete the conflict markers AND one of the versions. Keep whichever code is correct (or
combine them).

Mark as resolved
3 After editing, run git add <file> to tell Git you've resolved this file's conflict.

Complete the merge


4 Run git commit to finalize the merge. Git auto-creates a merge commit message.

Prevention is Better Than Resolution


Pull from main frequently: git pull origin main — sync your branch often to reduce conflicts
Communicate with your team — tell teammates which files you're working on
Make small, frequent commits — large divergent branches = massive conflicts
Use a good IDE like VS Code which shows conflicts with a visual diff tool
1.5 Pull Requests & Code Reviews
A Pull Request (PR) is a formal request to merge your branch into another (usually main). It's NOT just a Git
feature — it's a social process for reviewing code quality before it goes to production. This is how every
professional team works.

The Pull Request Lifecycle

1 2 3 4 5 ➡
Pu Open PR on GitHub Reviewers read & Author fixes issues Approved & merged
sh comment
br
an
ch

Writing a Good PR Description


# PR Template (save as .github/pull_request_template.md)
## What does this PR do?
Adds user authentication using JWT tokens. Users can now
register, log in, and receive a token valid for 24 hours.

## Why?
Resolves #142 - Login functionality was missing from the app.

## How to test
1. Run: npm install && npm start
2. Go to /register and create an account
3. Try logging in — you should receive a JWT in the response
4. Try accessing /api/protected with and without the token

## Screenshots
(attach images of the new login form here)

## Checklist
- [x] Tests written and passing
- [x] Documentation updated
- [x] No hardcoded secrets

Code Review Best Practices


Code review is a skill in itself. As a reviewer, your job is to improve code quality, catch bugs, and share
knowledge — NOT to judge the person.
As the Author As the Reviewer
• Keep PRs small (< 400 lines ideally) • Review within 24 hours (don't block
• Write a clear description of what and why teammates)
• Link to the issue it resolves • Ask questions, don't just demand changes
• Self-review before requesting reviews • Distinguish: "must fix" vs "nice to have"
• Respond to every comment (even just • Approve when good enough, not perfect
"done") • Praise good patterns you notice
• Don't take feedback personally • Check: logic, security, tests, readability

1.6 GitHub Workflows


GitHub adds a layer on top of Git — hosting, collaboration, and automation. Here are the key GitHub-specific
concepts:

Remote Repository Commands


# GitHub Commands
# ──────────────────────────────────────────────────────
# CONNECTING TO GITHUB
# ──────────────────────────────────────────────────────
git remote add origin [Link]
git remote -v # Verify your remote connections

# ──────────────────────────────────────────────────────
# SYNCING WITH GITHUB
# ──────────────────────────────────────────────────────
git push origin main # Upload commits to GitHub
git push -u origin main # Push + set upstream (first time)
git pull origin main # Download + merge changes from GitHub
git fetch origin # Download changes (do NOT merge yet)

# ──────────────────────────────────────────────────────
# WORKING WITH FORKS
# ──────────────────────────────────────────────────────
# Fork = your personal copy of someone else's repo on GitHub
git remote add upstream [Link]
git fetch upstream # Get updates from original repo
git merge upstream/main # Sync your fork with original

GitHub Actions: CI/CD Automation


GitHub Actions lets you automate tasks — run tests, deploy your app, check code style — automatically
whenever you push code or open a PR.
# GitHub Actions CI workflow
# .github/workflows/[Link]
# This runs tests automatically on every push and PR

name: CI Pipeline

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3 # Checkout the code

- name: Set up Python


uses: actions/setup-python@v4
with:
python-version: "3.11"

- name: Install dependencies


run: |
pip install -r [Link]

- name: Run tests


run: pytest --cov=app tests/

- name: Check code style


run: flake8 app/

🏆 .gitignore — Always Create This File


The .gitignore file tells Git which files to NEVER track.
You should NEVER commit: passwords/secrets, API keys, .env files, node_modules/,
__pycache__/, .DS_Store, venv/.

Create it at the root of your project:


# .gitignore
.env
node_modules/
__pycache__/
*.pyc
venv/
.DS_Store
dist/

Visit [Link] to generate one for your tech stack automatically.


Development Environment Setup

2.1 Virtual Environments


A virtual environment is an isolated Python installation for a specific project. This prevents your projects from
interfering with each other or with your system Python.

Why Virtual Environments Are Critical


Imagine: Project A needs Django 3.2, Project B needs Django 4.2. Without virtual environments, you cannot have
both installed at once. With virtual environments, each project has its own isolated space with its own packages.

# Isolation diagram
# SYSTEM PYTHON (global) VENV A (project_1/) VENV B
(project_2/)
# ──────────────────── ────────────────────
────────────────────
# Python 3.11 Django 3.2 Django 4.2
# pip (package manager) requests 2.28 requests
2.31
# (DO NOT install packages here!) numpy 1.23 numpy 1.26

Creating and Using Virtual Environments


# Virtual Environment Commands
# ──────────────────────────────────────────────────────
# CREATING A VIRTUAL ENVIRONMENT
# ──────────────────────────────────────────────────────
python -m venv venv # Creates a "venv" folder in your project

# ──────────────────────────────────────────────────────
# ACTIVATING (do this every time you open a terminal)
# ──────────────────────────────────────────────────────
source venv/bin/activate # Linux / macOS
.\venv\Scripts\activate # Windows (Command Prompt)
.\venv\Scripts\Activate.ps1 # Windows (PowerShell)

# You'll see (venv) in your terminal prompt — that means it's active!
(venv) $ python --version # This is the ISOLATED Python

# ──────────────────────────────────────────────────────
# WORKING INSIDE THE ENVIRONMENT
# ──────────────────────────────────────────────────────
pip install django # Installs ONLY in this venv, nowhere else
pip list # See what's installed here

# ──────────────────────────────────────────────────────
# DEACTIVATING
# ──────────────────────────────────────────────────────
deactivate # Return to system Python

# ──────────────────────────────────────────────────────
# IMPORTANT: Add venv/ to .gitignore! Never commit it!
# ──────────────────────────────────────────────────────

2.2 Dependency Management


[Link] — The Standard
[Link] lists every package your project needs. Anyone who clones your project can install all
dependencies with one command.

# Dependency Management
# ──────────────────────────────────────────────────────
# GENERATING [Link]
# ──────────────────────────────────────────────────────
pip freeze > [Link] # Snapshot ALL installed packages

# [Link] looks like:


Django==4.2.7
requests==2.31.0
numpy==1.26.0
python-dotenv==1.0.0

# ──────────────────────────────────────────────────────
# INSTALLING FROM [Link]
# ──────────────────────────────────────────────────────
pip install -r [Link] # Used by everyone who clones your repo

# ──────────────────────────────────────────────────────
# BEST PRACTICE: Separate dev dependencies
# ──────────────────────────────────────────────────────
[Link] # Production dependencies only
[Link] # + testing, linting, debugging tools

# [Link]:
-r [Link] # Include base requirements
pytest==7.4.0
black==23.0.0
flake8==6.0.0

Modern Alternative: Poetry


Poetry is a newer, more powerful dependency manager. It replaces pip + [Link] with a cleaner
approach using [Link]:

# Poetry Commands
# Install Poetry (one-time)
curl -sSL [Link] | python3 -

# Start a new project


poetry new my-project

# Add a dependency
poetry add django # Adds to [Link] automatically
poetry add pytest --dev # Development-only dependency

# Install all dependencies (like pip install -r [Link])


poetry install

# Run commands in the virtual environment


poetry run python [Link] runserver
poetry shell # Activate the venv

2.3 Environment Variables


Environment variables are configuration values stored OUTSIDE your code. They typically hold secrets (API keys,
passwords, database URLs) and settings that change between environments (development vs. production).

🚨 CRITICAL Security Rule


NEVER put secrets directly in your code. NEVER commit .env files to Git.

Bad: API_KEY = "sk-abc123secret" ← This is in your code and Git history!


Good: API_KEY = [Link]("API_KEY") ← Read from environment at runtime

Secrets in Git history = permanent security breach, even if you delete them later.

Using python-dotenv
# Environment Variables with python-dotenv
# Step 1: Install the library
pip install python-dotenv

# Step 2: Create a .env file in your project root


# .env (NEVER commit this file!)
DATABASE_URL=postgres://user:password@localhost:5432/mydb
SECRET_KEY=your-django-secret-key-here
DEBUG=True
STRIPE_API_KEY=sk_test_abc123

# Step 3: Load in your Python code


from dotenv import load_dotenv
import os

load_dotenv() # Reads .env file and makes vars available

DB_URL = [Link]("DATABASE_URL")
SECRET = [Link]("SECRET_KEY")
DEBUG = [Link]("DEBUG", "False") == "True" # with default

# Step 4: Create .[Link] with FAKE values (DO commit this)


# .[Link]
DATABASE_URL=postgres://user:password@localhost:5432/dbname
SECRET_KEY=replace-this-with-a-real-secret-key
DEBUG=True
STRIPE_API_KEY=your-stripe-key-here

2.4 Professional Project Structure


A well-organized project is easier to navigate, test, and scale. Here are the standard structures for Python and
[Link] projects:

Python / Django Project [Link] / Express Project

my-project/ my-api/
├── .github/ ├── .github/
│ └── workflows/ │ └── workflows/
│ └── [Link] │ └── [Link]
├── app/ ├── src/
│ ├── __init__.py │ ├── controllers/
│ ├── [Link] │ ├── middleware/
│ ├── [Link] │ ├── models/
│ ├── [Link] │ ├── routes/
│ └── tests/ │ ├── services/
│ └── test_views.py │ └── [Link]
├── config/ ├── tests/
│ └── [Link] │ └── [Link]
├── docs/ ├── .env ← gitignored
├── tests/ ├── .[Link] ← committed
├── .env ← gitignored ├── .gitignore
├── .[Link] ← committed ├── [Link]
├── .gitignore ├── [Link]
├── [Link] └── [Link]
├── [Link]
└── [Link]

2.5 Reproducible Setups


A reproducible setup means anyone (or any server) can clone your project and get it running identically with
minimal steps. This is how professional teams work.

The Gold Standard: [Link]


# [Link] template
# My Project

## Prerequisites
- Python 3.11+
- PostgreSQL 15+

## Setup

```bash
# 1. Clone the repo
git clone [Link]
cd my-project

# 2. Create and activate virtual environment


python -m venv venv
source venv/bin/activate # Windows: .\venv\Scripts\activate

# 3. Install dependencies
pip install -r [Link]

# 4. Set up environment variables


cp .[Link] .env
# Edit .env with your actual values

# 5. Run database migrations


python [Link] migrate

# 6. Start the server


python [Link] runserver
```

## Running Tests
```bash
pytest
```
🌐 Working with APIs & Services

3.1 What Is a REST API?


An API (Application Programming Interface) is a way for two programs to talk to each other. A REST API is the
most common type used on the web. Think of it as a waiter at a restaurant: you (client) tell the waiter (API) what
you want, the waiter goes to the kitchen (server), and brings back your food (data).

──── HTTP Request ────▶


CLIENT ⚙️SERVER
GET /api/users/42
Your app, browser, or mobile app Your backend, database, logic
◀── HTTP Response ────

REST Principles
REST (Representational State Transfer) follows a set of rules that make APIs predictable and easy to use:

REST Principle What It Means in Practice

Stateless Every request must contain all needed info. Server doesn't remember
previous requests.

Client-Server Frontend and backend are completely separate. API is the contract
between them.

Uniform Interface Resources are identified by URLs. Same patterns used everywhere.

Resource-Based Think in NOUNS not verbs. /users, /products — not /getUser,


/createProduct

3.2 HTTP Methods & Status Codes


The 5 HTTP Methods (CRUD Mapping)
Every action in a REST API maps to one of these HTTP methods. CRUD = Create, Read, Update, Delete — the four
basic database operations.

Method CRUD Example URL What it does Has Body?


GET Read GET /users/42 Fetch user with ID 42 No
POST Create POST /users Create a new user Yes
PUT Full Update PUT /users/42 Replace entire user 42 Yes
record
PATCH Part Update PATCH /users/42 Update only specific Yes
fields
DELETE Delete DELETE /users/42 Delete user with ID 42 No

HTTP Status Codes — The Language of APIs


Status codes tell you what happened to your request. They are grouped by their first digit:

Code Name Meaning & When to Use


200 OK 2xx = Success. Request succeeded, response body has the data.
201 Created POST succeeded. A new resource was created. Return the new resource
in body.
204 No Content Request succeeded but nothing to return. Common for DELETE
responses.
301 Moved Permanently 3xx = Redirect. URL has permanently moved to a new location.
304 Not Modified Client's cached version is still valid. No need to resend data.
400 Bad Request 4xx = Client Error. Request was malformed. Invalid JSON, missing
required fields.
401 Unauthorized Not authenticated. No valid token provided. Must log in first.
403 Forbidden Authenticated but not authorized. You don't have permission for this.
404 Not Found Resource doesn't exist. GET /users/999 but user 999 doesn't exist.
409 Conflict Resource conflict. E.g., trying to create a user with an email that already
exists.
422 Unprocessable Valid JSON but business logic validation failed. E.g., negative price.
429 Too Many Requests Rate limit exceeded. Slow down your requests!
500 Internal Server Error 5xx = Server Error. Something crashed on the server. Bug in your code.
503 Service Unavailable Server is down or overloaded. Often temporary.

3.3 JSON Handling


JSON (JavaScript Object Notation) is the universal language of REST APIs. Every API sends and receives data in
JSON format. It looks like a Python dictionary.
JSON Structure
// JSON data types
// JSON supports these data types:
{
"name": "Alice", // string (always double quotes)
"age": 28, // number (integer)
"price": 9.99, // number (float)
"is_active": true, // boolean (lowercase true/false)
"address": null, // null (no value)
"tags": ["python", "api"], // array
"metadata": { // nested object
"created_at": "2024-01-15T10:30:00Z",
"version": 2
}
}

Working with JSON in Python


# JSON in Python
import json

# ──────────────────────────────────────────────────────
# PARSING (string → Python object)
# ──────────────────────────────────────────────────────
json_string = '{"name": "Alice", "age": 28, "tags": ["python"]}'

data = [Link](json_string) # Parse JSON string to dict


print(data["name"]) # → "Alice"
print(data["tags"][0]) # → "python"

# ──────────────────────────────────────────────────────
# SERIALIZING (Python object → string)
# ──────────────────────────────────────────────────────
user = {"name": "Bob", "age": 35, "active": True}

json_string = [Link](user) # Compact


json_string = [Link](user, indent=2) # Pretty-printed

# ──────────────────────────────────────────────────────
# READING/WRITING JSON FILES
# ──────────────────────────────────────────────────────
with open("[Link]", "r") as f:
data = [Link](f) # Read from file

with open("[Link]", "w") as f:


[Link](data, f, indent=2) # Write to file

# ──────────────────────────────────────────────────────
# COMMON GOTCHAS
# ──────────────────────────────────────────────────────
# Python True/False → JSON true/false (lowercase)
# Python None → JSON null
# Python tuples → JSON arrays
# datetime objects need special handling:

from datetime import datetime


now = [Link]()
# [Link]({"time": now}) ← FAILS - not serializable
[Link]({"time": [Link]()}) # ← Works: convert to string

3.4 Making HTTP Requests


The requests Library — Python Standard
# HTTP Requests with Python requests library
pip install requests

import requests

# ──────────────────────────────────────────────────────
# GET REQUEST — Fetching data
# ──────────────────────────────────────────────────────
response = [Link]("[Link]

print(response.status_code) # 200
print([Link]) # Response headers dict
data = [Link]() # Parse JSON response body
print(data["name"]) # "The Octocat"

# With query parameters:


params = {"q": "python", "sort": "stars", "order": "desc"}
response = [Link]("[Link]
params=params)
# This calls: /search/repositories?q=python&sort=stars&order=desc

# ──────────────────────────────────────────────────────
# POST REQUEST — Sending data
# ──────────────────────────────────────────────────────
new_user = {
"name": "Alice",
"email": "alice@[Link]",
"password": "secure_password_123"
}

response = [Link](
"[Link]
json=new_user, # Automatically serializes to JSON + sets Content-
Type header
headers={"Authorization": "Bearer YOUR_TOKEN"}
)

if response.status_code == 201:
created_user = [Link]()
print(f"Created user: {created_user['id']}")

# ──────────────────────────────────────────────────────
# PUT and PATCH
# ──────────────────────────────────────────────────────
response = [Link]("[Link]
json={"name": "Alice Smith", "email": "new@[Link]"},
headers={"Authorization": "Bearer YOUR_TOKEN"})

response = [Link]("[Link]
json={"name": "Alice Smith"}, # Only update name
headers={"Authorization": "Bearer YOUR_TOKEN"})

# ──────────────────────────────────────────────────────
# DELETE
# ──────────────────────────────────────────────────────
response = [Link]("[Link]
headers={"Authorization": "Bearer YOUR_TOKEN"})
print(response.status_code) # 204 = success, no content

3.5 Authentication Mechanisms


Authentication answers: "Who are you?" Almost every real API requires authentication. Here are the main
methods from simple to advanced:

1. API Keys — Simplest Method


# API Key Authentication
# API keys are just a secret string that identifies your app
# Usually sent in a header or query parameter

# Method 1: Header (most common, most secure)


headers = {"X-API-Key": "your_api_key_here"}
response = [Link]("[Link] headers=headers)

# Method 2: Query parameter (less secure — key visible in URL logs)


response = [Link]("[Link]

# ⚠️ Always store API keys in .env, NEVER in code:


import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = [Link]("SERVICE_API_KEY")
2. Bearer Tokens (JWT) — Modern Standard
JWT (JSON Web Token) is the industry standard for APIs today. The client logs in, receives a token, and includes
it in every subsequent request.

# JWT / Bearer Token Authentication


# Step 1: Login to get a token
response = [Link]("[Link] json={
"email": "user@[Link]",
"password": "password123"
})

token = [Link]()["access_token"]
# token looks like: "[Link]..."

# Step 2: Use token in all subsequent requests


headers = {"Authorization": f"Bearer {token}"}

response = [Link]("[Link] headers=headers)


response = [Link]("[Link]
json={"title": "Hello"},
headers=headers)

# ──────────────────────────────────────────────────────
# WHAT IS A JWT?
# ──────────────────────────────────────────────────────
# A JWT has 3 parts separated by dots:
# [Link]
#
# Header: algorithm used to sign the token
# Payload: data (user ID, email, expiry, permissions)
# Signature: proves the token hasn't been tampered with
#
# The payload is BASE64 ENCODED, not encrypted.
# Never put sensitive data (passwords) in a JWT payload!

# Decode a JWT to see its contents:


import base64, json
payload_b64 = [Link](".")[1]
payload_b64 += "=" * (4 - len(payload_b64) % 4) # Add padding
payload = [Link](base64.b64decode(payload_b64))
print(payload) # {'user_id': 42, 'email': 'user@[Link]', 'exp':
1234567890}

3. OAuth2 — Delegated Authorization


OAuth2 is what powers "Login with Google/GitHub/Facebook." You authorize a third-party app to access your
data without giving it your password.
# OAuth2 Flow
# OAuth2 Flow (simplified):
#
# 1. Your app redirects user to:
# [Link]
# client_id=YOUR_APP_ID&
# redirect_uri=[Link]
# scope=read:user
#
# 2. User logs in on GitHub and approves your app
#
# 3. GitHub redirects back to your app with a CODE:
# [Link]
#
# 4. Your backend exchanges the code for a token:

code = [Link]("code") # Code from URL

response = [Link]("[Link]
data={
"client_id": [Link]("GITHUB_CLIENT_ID"),
"client_secret": [Link]("GITHUB_CLIENT_SECRET"),
"code": code
},
headers={"Accept": "application/json"}
)

access_token = [Link]()["access_token"]

# 5. Use the token to access user's data:


user_data = [Link]("[Link]
headers={"Authorization": f"Bearer {access_token}"}
).json()

3.6 Error Handling


Production-quality code handles every possible failure. Networks fail, APIs return errors, data is malformed.
Robust error handling is what separates amateur code from industry code.

# Production Error Handling


import requests
from [Link] import Timeout, ConnectionError, RequestException

def get_user(user_id: int) -> dict:


"""
Fetch user from API with comprehensive error handling.
Returns user dict or raises a descriptive exception.
"""
url = f"[Link]
headers = {"Authorization": f"Bearer {[Link]('API_TOKEN')}"}

try:
response = [Link](
url,
headers=headers,
timeout=10 # ALWAYS set a timeout! Default is infinite!
)

# ── HTTP Error Handling ──────────────────────────


if response.status_code == 200:
return [Link]()

elif response.status_code == 401:


raise PermissionError("API token is invalid or expired. Re-
authenticate.")

elif response.status_code == 403:


raise PermissionError(f"Not authorized to access user
{user_id}")

elif response.status_code == 404:


raise ValueError(f"User {user_id} not found")

elif response.status_code == 429:


retry_after = [Link]("Retry-After", "60")
raise Exception(f"Rate limit exceeded. Retry after
{retry_after}s")

elif response.status_code >= 500:


raise Exception(f"Server error {response.status_code}: Try
again later")

else:
# Unexpected status code
response.raise_for_status() # Raises HTTPError

except Timeout:
raise Exception("Request timed out after 10s. Check your
connection.")

except ConnectionError:
raise Exception("Cannot reach API. Check your internet
connection.")

except RequestException as e:
raise Exception(f"Request failed: {e}")

3.7 Rate Limiting


APIs limit how many requests you can make per time period to prevent abuse. Understanding and handling rate
limits is essential for production applications.

Header What It Tells You

X-RateLimit-Limit Total requests allowed per window (e.g., 1000 per hour)

X-RateLimit-Remaining Requests remaining in current window (e.g., 47 left)

X-RateLimit-Reset Unix timestamp when window resets (e.g., 1705316400)

Retry-After Seconds to wait before retrying (sent with 429 responses)

Handling Rate Limits with Exponential Backoff


# Rate Limiting with Exponential Backoff
import time
import requests

def api_request_with_retry(url, headers, max_retries=5):


"""
Exponential backoff: wait 1s, 2s, 4s, 8s, 16s between retries.
This is the industry standard pattern for handling rate limits.
"""
for attempt in range(max_retries):
response = [Link](url, headers=headers, timeout=10)

if response.status_code == 200:
return [Link]() # Success!

elif response.status_code == 429: # Rate limited


retry_after = int([Link]("Retry-After", 0))

if retry_after:
wait_time = retry_after # Use API's suggested wait time
else:
wait_time = 2 ** attempt # Exponential backoff:
1,2,4,8,16

print(f"Rate limited. Waiting {wait_time}s (attempt


{attempt+1})")
[Link](wait_time)

elif response.status_code >= 500:


wait_time = 2 ** attempt
print(f"Server error. Retrying in {wait_time}s...")
[Link](wait_time)

else:
response.raise_for_status() # Non-retryable error, fail fast

raise Exception(f"Failed after {max_retries} attempts")


# ──────────────────────────────────────────────────────
# PRO TIP: Proactively check remaining rate limit
# ──────────────────────────────────────────────────────
response = [Link]("[Link]
headers={"Authorization": "Bearer TOKEN"})

remaining = int([Link]("X-RateLimit-Remaining", 999))


reset_time = int([Link]("X-RateLimit-Reset", 0))

if remaining < 10:


wait_seconds = reset_time - [Link]()
print(f"Warning: Only {remaining} API calls left.")
print(f"Rate limit resets in {wait_seconds:.0f} seconds.")

3.8 Complete Real-World API Integration


Putting it all together — here is how a production-quality API client class looks:

# Production API Client Class


import os, time, requests, logging
from dotenv import load_dotenv

load_dotenv()
logger = [Link](__name__)

class APIClient:
"""
Production-quality API client with:
- Authentication via Bearer token
- Automatic retry with exponential backoff
- Rate limit awareness
- Comprehensive error handling
- Request logging
"""

def __init__(self, base_url: str, api_key: str):


self.base_url = base_url.rstrip("/")
[Link] = [Link]() # Reuse TCP connections
[Link]({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Accept": "application/json",
})

def _request(self, method, endpoint, **kwargs):',


url = f"{self.base_url}/{[Link]('/')}"
[Link]("timeout", 30)
for attempt in range(5):
[Link](f"{method} {url} (attempt {attempt+1})")
response = [Link](method, url, **kwargs)

if response.status_code in (200, 201, 204):


return response

if response.status_code == 429:
wait = int([Link]("Retry-After", 2 **
attempt))
[Link](f"Rate limited. Sleeping {wait}s")
[Link](wait)
continue

if response.status_code >= 500 and attempt < 4:


[Link](2 ** attempt)
continue

response.raise_for_status()

raise Exception("Max retries exceeded")

def get(self, endpoint, params=None):


return self._request("GET", endpoint, params=params).json()

def post(self, endpoint, data):


return self._request("POST", endpoint, json=data).json()

def patch(self, endpoint, data):


return self._request("PATCH", endpoint, json=data).json()

def delete(self, endpoint):


self._request("DELETE", endpoint)

# ──────────────────────────────────────────────────────
# USAGE
# ──────────────────────────────────────────────────────
client = APIClient(
base_url="[Link]
api_key=[Link]("API_KEY")
)

users = [Link]("/users", params={"page": 1, "limit": 50})


new_user = [Link]("/users", data={"name": "Alice", "email":
"a@[Link]"})
[Link](f"/users/{new_user['id']}", data={"name": "Alice Smith"})
[Link](f"/users/{new_user['id']}")
⚡ Quick Reference Cheat Sheet

Git Commands
Command What it does

git init Initialize a new Git repository in the current folder

git clone <url> Download a remote repository to your local machine

git status Show which files are modified, staged, or untracked

git add <file> Stage a file for the next commit

git add . Stage ALL modified and new files

git commit -m "msg" Save staged changes with a descriptive message

git push origin <branch> Upload local commits to GitHub

git pull origin <branch> Download and merge changes from GitHub

git checkout -b <branch> Create and switch to a new branch

git merge <branch> Merge the specified branch into the current branch

git log --oneline See compact commit history

git stash Temporarily save uncommitted changes (like a clipboard)

git stash pop Restore the stashed changes

git reset --hard HEAD Discard ALL local changes (use with caution!)

HTTP Status Codes at a Glance


2xx — Success ✅ 3xx — Redirect ↪️
• 200 OK — success with data • 301 Moved Permanently
• 201 Created — new resource made • 304 Not Modified (cached)
• 204 No Content — success, nothing
returned

4xx — Client Error ⚠️ 5xx — Server Error 🔥


• 400 Bad Request — malformed data • 500 Internal Server Error — bug in server
• 401 Unauthorized — not logged in • 502 Bad Gateway — upstream issue
• 403 Forbidden — no permission • 503 Service Unavailable — server down
• 404 Not Found — doesn't exist
• 429 Too Many Requests — rate limit

You might also like