Let's see all four HTTP methods working together in a blog post management system that demonstrates professional error handling throughout.
import requests
class BlogPostManager:
"""
Demonstrates all HTTP methods with professional error handling.
Applies Chapter 4's defensive patterns to all operations.
"""
def __init__(self,base_url="https://httpbin.org"):
self.base_url = base_url
self.timeout = 10
def _validate_content_type(self, response, expected="application/json"):
"""Validate response content type (Chapter 4 pattern)."""
content_type = response.headers.get("Content-Type", "")
if expected not in content_type:
return False, f"Expected {expected} but received {content_type}"
return True, ""
def create_post(self, title, content, author):
"""
CREATE: Use POST to create a new blog post.
Demonstrates input validation and status code checking.
"""
# Validate input before making request
if not all([title, content, author]):
return (False, None, "Title, content, and author are required")
post_data = {
"title": title,
"content": content,
"author": author
}
try:
# Make request with timeout
response = requests.post(
f"{self.base_url}/post",
json=post_data,
timeout=self.timeout
)
# Check status codes specific to POST
if response.status_code in (200, 201):
# Validate content type
valid, error = self._validate_content_type(response)
if not valid:
return (False, None, error)
# Extract response data
try:
data = response.json()
return (True, data, f"Post '{title}' created successfully")
except ValueError:
return (False, None, "Server returned invalid JSON")
elif response.status_code == 409:
return (False, None, f"Post '{title}' already exists")
elif response.status_code == 400:
return (False, None, "Invalid post data")
else:
return (False, None, f"Creation failed: {response.status_code}")
except requests.exceptions.Timeout:
return (False, None, "Request timed out")
except requests.exceptions.RequestException as e:
return (False, None, f"Network error: {e}")
def read_post(self, post_id):
"""
READ: Use GET to retrieve a blog post.
Demonstrates Chapter 4's complete validation pattern.
"""
if not post_id:
return (False, None, "Post ID is required")
try:
# Make request with timeout
response = requests.get(
f"{self.base_url}/get",
params={"post_id": post_id},
timeout=self.timeout
)
# Check status
if response.status_code == 200:
# Validate content type
valid, error = self._validate_content_type(response)
if not valid:
return (False, None, error)
# Extract and validate structure
try:
data = response.json()
# In real API, validate data has expected structure
return (True, data, f"Retrieved post {post_id}")
except ValueError:
return (False, None, "Server returned invalid JSON")
elif response.status_code == 404:
return (False, None, f"Post {post_id} not found")
else:
return (False, None, f"Request failed: {response.status_code}")
except requests.exceptions.Timeout:
return (False, None, "Request timed out")
except requests.exceptions.RequestException as e:
return (False, None, f"Network error: {e}")
def update_post(self, post_id, title, content, author):
"""
UPDATE: Use PUT to replace entire blog post.
Demonstrates handling of PUT-specific status codes.
"""
if not post_id:
return (False, None, "Post ID is required")
if not all([title, content, author]):
return (False, None, "All fields required for complete replacement")
updated_data = {
"post_id": post_id,
"title": title,
"content": content,
"author": author
}
try:
# Make request with timeout
response = requests.put(
f"{self.base_url}/put",
json=updated_data,
timeout=self.timeout
)
# Check status codes specific to PUT
if response.status_code == 200:
# Success with response data
valid, error = self._validate_content_type(response)
if not valid:
return (False, None, error)
try:
data = response.json()
return (True, data, f"Post {post_id} updated successfully")
except ValueError:
return (False, None, "Server returned invalid JSON")
elif response.status_code == 204:
# Success without response content (common for PUT)
return (True, None, f"Post {post_id} updated successfully")
elif response.status_code == 404:
return (False, None, f"Post {post_id} not found - cannot update")
elif response.status_code == 409:
return (False, None, "Post was modified by someone else - please reload")
else:
return (False, None, f"Update failed: {response.status_code}")
except requests.exceptions.Timeout:
return (False, None, "Request timed out")
except requests.exceptions.RequestException as e:
return (False, None, f"Network error: {e}")
def delete_post(self, post_id):
"""
DELETE: Remove a blog post.
Demonstrates treating 404 as success for DELETE.
"""
if not post_id:
return (False, "Post ID is required")
try:
# Make request with timeout
response = requests.delete(
f"{self.base_url}/delete",
json={"post_id": post_id},
timeout=self.timeout
)
# Check status codes specific to DELETE
if response.status_code in (200, 204):
return (True, f"Post {post_id} deleted successfully")
elif response.status_code == 404:
# Treat as success - goal accomplished
return (True, f"Post {post_id} not found (already deleted)")
elif response.status_code == 409:
return (False, f"Cannot delete post {post_id} - dependencies exist")
elif response.status_code == 403:
return (False, f"Not authorized to delete post {post_id}")
else:
return (False, f"Deletion failed: {response.status_code}")
except requests.exceptions.Timeout:
return (False, "Request timed out")
except requests.exceptions.RequestException as e:
return (False, f"Network error: {e}")
# Demonstrate complete CRUD workflow
print("="*60)
print("PRODUCTION-GRADE CRUD DEMONSTRATION")
print("="*60)
blog = BlogPostManager()
# CREATE
print("\n1. CREATE - Making a new blog post")
success, data, msg = blog.create_post(
"Learning HTTP Methods",
"Today I learned about GET, POST, PUT, and DELETE...",
"Alice"
)
print(f" {'✅' if success else '❌'} {msg}")
# READ
print("\n2. READ - Retrieving the blog post")
success, data, msg = blog.read_post(123)
print(f" {'✅' if success else '❌'} {msg}")
# UPDATE
print("\n3. UPDATE - Modifying the blog post")
success, data, msg = blog.update_post(
123,
"Mastering HTTP Methods",
"I've now mastered GET, POST, PUT, and DELETE with defensive programming!",
"Alice"
)
print(f" {'✅' if success else '❌'} {msg}")
# DELETE
print("\n4. DELETE - Removing the blog post")
success, msg = blog.delete_post(123)
print(f" {'✅' if success else '❌'} {msg}")
print("\n" + "="*60)
print("All operations completed with professional error handling!")
print("="*60)
This complete example demonstrates how Chapter 4's defensive patterns scale to all HTTP methods. The code is more verbose than basic examples, but this verbosity prevents production bugs and provides clear feedback when things go wrong. This is the difference between code that works in demos and code that survives in production.