Python HTTP CRUD 完全指南:从入门到实战

16 阅读13分钟

今天写一篇全面、专业的 Python HTTP CRUD 开发博客文章,这篇文章涵盖了从基础到进阶的内容。


Python HTTP CRUD 完全指南:从入门到实战

用 Python 构建健壮的 RESTful API 客户端,掌握增删改查的核心技能

前言

在现代 Web 开发中,CRUD(Create、Read、Update、Delete)操作是几乎所有应用程序的基础。无论您是开发后端服务、编写自动化脚本,还是构建数据爬虫,掌握如何使用 Python 进行 HTTP CRUD 操作都是一项必备技能。

本文将带您从零开始,系统学习如何使用 Python 的 requests 库实现完整的 HTTP CRUD 操作,并涵盖错误处理、身份验证、性能优化等进阶话题。


你将学到什么

  • ✅ Python requests 库的核心用法
  • ✅ RESTful API 的设计理念与最佳实践
  • ✅ 完整的 CRUD 操作实现(GET、POST、PUT、DELETE)
  • ✅ 专业的错误处理与异常捕获
  • ✅ 身份验证与安全实践(Token、Basic Auth)
  • ✅ 超时设置、重试机制与性能优化
  • ✅ 单元测试与代码质量保障
  • ✅ 实战项目:构建一个完整的任务管理客户端

第一章:环境准备与基础概念

1.1 安装 requests 库

requests 是 Python 中最流行的 HTTP 客户端库,以其简洁优雅的 API 著称。

# 基础安装
pip install requests

# 若使用虚拟环境(推荐)
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install requests

1.2 验证安装

import requests
print(requests.__version__)  # 应输出 2.28.0 或更高版本

1.3 RESTful API 基础

在开始编码前,理解 RESTful API 的核心概念至关重要:

HTTP 方法CRUD 操作幂等性安全性典型用途
GETRead✅ 是✅ 是获取资源
POSTCreate❌ 否❌ 否创建资源
PUTUpdate/Replace✅ 是❌ 否完整更新
PATCHUpdate/Modify❌ 否❌ 否部分更新
DELETEDelete✅ 是❌ 否删除资源

💡 关键概念

  • 幂等性:多次执行相同请求,效果相同(不会产生副作用)
  • 安全性:操作不会修改服务器状态(GET 是安全的)

第二章:基础 CRUD 实现

2.1 完整的 CRUD 类封装

下面是一个生产级的 CRUD 客户端实现:

import requests
import json
from typing import Optional, Dict, Any, List, Union
from urllib.parse import urljoin

class RESTClient:
    """
    一个健壮的 RESTful API 客户端
    支持完整的 CRUD 操作、错误处理和日志记录
    """
    
    def __init__(self, base_url: str, timeout: int = 30, 
                 headers: Optional[Dict[str, str]] = None):
        """
        初始化客户端
        
        :param base_url: API 基础 URL
        :param timeout: 请求超时时间(秒)
        :param headers: 默认请求头
        """
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout
        self.session = requests.Session()
        
        # 设置默认请求头
        default_headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'Python-RESTClient/1.0'
        }
        if headers:
            default_headers.update(headers)
        self.session.headers.update(default_headers)
    
    def _build_url(self, endpoint: str) -> str:
        """构建完整的 URL"""
        return urljoin(self.base_url + '/', endpoint.lstrip('/'))
    
    def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
        """
        处理响应,统一解析 JSON 并处理错误
        """
        try:
            response.raise_for_status()
            if response.status_code == 204:  # No Content
                return {'status': 'success', 'message': '操作成功'}
            return response.json() if response.text else {}
        except requests.exceptions.HTTPError as e:
            error_msg = f"HTTP 错误 {response.status_code}: {response.text}"
            raise APIError(error_msg, response.status_code, response)
        except json.JSONDecodeError:
            raise APIError("响应不是有效的 JSON 格式", response.status_code, response)
    
    # ============ CRUD 方法 ============
    
    def get(self, endpoint: str, params: Optional[Dict] = None, 
            **kwargs) -> Union[Dict, List]:
        """
        GET 请求:获取资源
        
        :param endpoint: API 端点
        :param params: 查询参数(分页、过滤等)
        :return: 响应数据
        """
        url = self._build_url(endpoint)
        response = self.session.get(
            url, 
            params=params, 
            timeout=kwargs.get('timeout', self.timeout)
        )
        return self._handle_response(response)
    
    def post(self, endpoint: str, data: Optional[Dict] = None,
             **kwargs) -> Dict:
        """
        POST 请求:创建资源
        
        :param endpoint: API 端点
        :param data: 请求体数据
        :return: 创建的资源数据
        """
        url = self._build_url(endpoint)
        response = self.session.post(
            url,
            json=data,
            timeout=kwargs.get('timeout', self.timeout)
        )
        return self._handle_response(response)
    
    def put(self, endpoint: str, data: Optional[Dict] = None,
            **kwargs) -> Dict:
        """
        PUT 请求:完整更新资源
        
        :param endpoint: API 端点(通常包含资源 ID)
        :param data: 完整的更新数据
        :return: 更新后的资源数据
        """
        url = self._build_url(endpoint)
        response = self.session.put(
            url,
            json=data,
            timeout=kwargs.get('timeout', self.timeout)
        )
        return self._handle_response(response)
    
    def patch(self, endpoint: str, data: Optional[Dict] = None,
              **kwargs) -> Dict:
        """
        PATCH 请求:部分更新资源
        
        :param endpoint: API 端点(通常包含资源 ID)
        :param data: 需要更新的字段
        :return: 更新后的资源数据
        """
        url = self._build_url(endpoint)
        response = self.session.patch(
            url,
            json=data,
            timeout=kwargs.get('timeout', self.timeout)
        )
        return self._handle_response(response)
    
    def delete(self, endpoint: str, **kwargs) -> Dict:
        """
        DELETE 请求:删除资源
        
        :param endpoint: API 端点(包含资源 ID)
        :return: 删除结果
        """
        url = self._build_url(endpoint)
        response = self.session.delete(
            url,
            timeout=kwargs.get('timeout', self.timeout)
        )
        return self._handle_response(response)
    
    # ============ 便捷方法 ============
    
    def get_by_id(self, resource: str, id: Union[int, str]) -> Dict:
        """通过 ID 获取单个资源"""
        return self.get(f"{resource}/{id}")
    
    def list_all(self, resource: str, **params) -> List:
        """获取资源列表"""
        return self.get(resource, params=params)
    
    def create(self, resource: str, data: Dict) -> Dict:
        """创建资源"""
        return self.post(resource, data)
    
    def update(self, resource: str, id: Union[int, str], data: Dict) -> Dict:
        """完整更新资源"""
        return self.put(f"{resource}/{id}", data)
    
    def partial_update(self, resource: str, id: Union[int, str], data: Dict) -> Dict:
        """部分更新资源"""
        return self.patch(f"{resource}/{id}", data)
    
    def delete_one(self, resource: str, id: Union[int, str]) -> Dict:
        """删除资源"""
        return self.delete(f"{resource}/{id}")


class APIError(Exception):
    """自定义 API 异常"""
    def __init__(self, message: str, status_code: int = None, 
                 response: requests.Response = None):
        self.message = message
        self.status_code = status_code
        self.response = response
        super().__init__(message)
    
    def __str__(self):
        if self.status_code:
            return f"[{self.status_code}] {self.message}"
        return self.message

2.2 使用示例

# 初始化客户端
client = RESTClient("https://jsonplaceholder.typicode.com")

# ===== 1. 获取所有文章 =====
try:
    posts = client.list_all("posts")
    print(f"获取到 {len(posts)} 篇文章")
    print(f"第一篇文章标题: {posts[0]['title']}")
except APIError as e:
    print(f"获取失败: {e}")

# ===== 2. 创建新文章 =====
new_post = {
    "title": "Python HTTP CRUD 完全指南",
    "body": "这是一篇关于 Python HTTP CRUD 的全面教程...",
    "userId": 1
}
try:
    created = client.create("posts", new_post)
    print(f"✅ 创建成功,ID: {created['id']}")
except APIError as e:
    print(f"创建失败: {e}")

# ===== 3. 获取单个文章 =====
try:
    post = client.get_by_id("posts", 1)
    print(f"文章标题: {post['title']}")
except APIError as e:
    print(f"获取失败: {e}")

# ===== 4. 更新文章 =====
update_data = {
    "title": "更新后的标题",
    "body": "更新后的内容",
    "userId": 1
}
try:
    updated = client.update("posts", 1, update_data)
    print(f"✅ 更新成功: {updated['title']}")
except APIError as e:
    print(f"更新失败: {e}")

# ===== 5. 部分更新 =====
patch_data = {"title": "部分更新后的标题"}
try:
    patched = client.partial_update("posts", 1, patch_data)
    print(f"✅ 部分更新成功: {patched['title']}")
except APIError as e:
    print(f"部分更新失败: {e}")

# ===== 6. 删除文章 =====
try:
    result = client.delete_one("posts", 1)
    print(f"✅ 删除成功: {result}")
except APIError as e:
    print(f"删除失败: {e}")

第三章:高级功能与最佳实践

3.1 身份验证

3.1.1 Token 认证(Bearer Token)
class AuthenticatedRESTClient(RESTClient):
    """支持 Token 认证的 REST 客户端"""
    
    def __init__(self, base_url: str, token: str = None, **kwargs):
        headers = kwargs.pop('headers', {})
        if token:
            headers['Authorization'] = f'Bearer {token}'
        super().__init__(base_url, headers=headers, **kwargs)
    
    def set_token(self, token: str):
        """更新 Token"""
        self.session.headers['Authorization'] = f'Bearer {token}'
    
    def login(self, username: str, password: str, login_endpoint: str = '/auth/login'):
        """
        登录获取 Token
        """
        response = self.post(login_endpoint, data={
            'username': username,
            'password': password
        })
        if 'token' in response:
            self.set_token(response['token'])
            return response
        raise APIError("登录响应中未包含 token")
3.1.2 Basic Auth 认证
import requests
from requests.auth import HTTPBasicAuth

class BasicAuthRESTClient(RESTClient):
    """支持 Basic Auth 的 REST 客户端"""
    
    def __init__(self, base_url: str, username: str = None, 
                 password: str = None, **kwargs):
        super().__init__(base_url, **kwargs)
        if username and password:
            self.session.auth = HTTPBasicAuth(username, password)
    
    def set_auth(self, username: str, password: str):
        self.session.auth = HTTPBasicAuth(username, password)

3.2 请求重试机制

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class RetryRESTClient(RESTClient):
    """支持自动重试的 REST 客户端"""
    
    def __init__(self, base_url: str, retries: int = 3, 
                 backoff_factor: float = 0.5, **kwargs):
        super().__init__(base_url, **kwargs)
        
        # 配置重试策略
        retry_strategy = Retry(
            total=retries,
            backoff_factor=backoff_factor,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"]
        )
        
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("http://", adapter)
        self.session.mount("https://", adapter)

3.3 请求日志与调试

import logging
import time
from functools import wraps

class LoggingRESTClient(RESTClient):
    """带日志记录的 REST 客户端"""
    
    def __init__(self, base_url: str, log_level: int = logging.INFO, **kwargs):
        super().__init__(base_url, **kwargs)
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
        self.logger.setLevel(log_level)
        
        if not self.logger.handlers:
            handler = logging.StreamHandler()
            formatter = logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            )
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
    
    def _log_request(self, method: str, url: str, **kwargs):
        """记录请求信息"""
        self.logger.info(f"➡️  {method} {url}")
        if 'params' in kwargs and kwargs['params']:
            self.logger.debug(f"   Params: {kwargs['params']}")
        if 'json' in kwargs and kwargs['json']:
            self.logger.debug(f"   Body: {kwargs['json']}")
    
    def _log_response(self, response: requests.Response, elapsed: float):
        """记录响应信息"""
        self.logger.info(
            f"⬅️  {response.status_code} {response.url} "
            f"({elapsed:.3f}s)"
        )
    
    def get(self, endpoint: str, **kwargs):
        url = self._build_url(endpoint)
        self._log_request('GET', url, **kwargs)
        start = time.time()
        response = self.session.get(url, **kwargs)
        elapsed = time.time() - start
        self._log_response(response, elapsed)
        return self._handle_response(response)
    
    # 其他方法类似 ...

3.4 并发请求

import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor, as_completed

class AsyncRESTClient:
    """异步 REST 客户端(使用 aiohttp)"""
    
    def __init__(self, base_url: str, max_connections: int = 10):
        self.base_url = base_url
        self.max_connections = max_connections
    
    async def get_async(self, endpoint: str, params: dict = None):
        """异步 GET 请求"""
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        async with aiohttp.ClientSession() as session:
            async with session.get(url, params=params) as response:
                return await response.json()
    
    async def get_many(self, endpoints: list):
        """并发获取多个资源"""
        tasks = [self.get_async(endpoint) for endpoint in endpoints]
        return await asyncio.gather(*tasks, return_exceptions=True)


# 使用示例
async def fetch_all_posts():
    client = AsyncRESTClient("https://jsonplaceholder.typicode.com")
    post_ids = [f"posts/{i}" for i in range(1, 11)]
    results = await client.get_many(post_ids)
    return [r for r in results if not isinstance(r, Exception)]

# 运行异步代码
# asyncio.run(fetch_all_posts())

3.5 文件上传与下载

class FileRESTClient(RESTClient):
    """支持文件操作的 REST 客户端"""
    
    def upload_file(self, endpoint: str, file_path: str, 
                    field_name: str = 'file', extra_data: dict = None):
        """
        上传文件
        
        :param endpoint: 上传端点
        :param file_path: 本地文件路径
        :param field_name: 表单字段名
        :param extra_data: 额外表单数据
        """
        url = self._build_url(endpoint)
        files = {field_name: (file_path.split('/')[-1], open(file_path, 'rb'))}
        data = extra_data or {}
        
        # 临时移除 JSON 内容类型
        original_headers = self.session.headers.copy()
        self.session.headers.pop('Content-Type', None)
        
        try:
            response = self.session.post(url, files=files, data=data)
            return self._handle_response(response)
        finally:
            self.session.headers.update(original_headers)
    
    def download_file(self, endpoint: str, save_path: str, **kwargs):
        """下载文件"""
        url = self._build_url(endpoint)
        response = self.session.get(url, stream=True, **kwargs)
        response.raise_for_status()
        
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        return save_path

第四章:单元测试

4.1 使用 unittest 测试

import unittest
from unittest.mock import Mock, patch
from requests.exceptions import RequestException

class TestRESTClient(unittest.TestCase):
    
    def setUp(self):
        """每个测试方法执行前的准备工作"""
        self.client = RESTClient("https://api.example.com")
    
    @patch('requests.Session.get')
    def test_get_success(self, mock_get):
        """测试 GET 请求成功"""
        # 模拟响应
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"id": 1, "name": "Test"}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        # 执行测试
        result = self.client.get("items/1")
        
        # 断言
        self.assertEqual(result, {"id": 1, "name": "Test"})
        mock_get.assert_called_once_with(
            "https://api.example.com/items/1",
            params=None,
            timeout=30
        )
    
    @patch('requests.Session.get')
    def test_get_404(self, mock_get):
        """测试 GET 请求 404 错误"""
        mock_response = Mock()
        mock_response.status_code = 404
        mock_response.text = "Not Found"
        mock_response.raise_for_status.side_effect = RequestException("404")
        mock_get.return_value = mock_response
        
        with self.assertRaises(APIError):
            self.client.get("items/999")
    
    @patch('requests.Session.post')
    def test_create_success(self, mock_post):
        """测试 POST 请求成功"""
        mock_response = Mock()
        mock_response.status_code = 201
        mock_response.json.return_value = {"id": 2, "name": "New Item"}
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response
        
        data = {"name": "New Item"}
        result = self.client.post("items", data)
        
        self.assertEqual(result["id"], 2)
        mock_post.assert_called_once_with(
            "https://api.example.com/items",
            json=data,
            timeout=30
        )


if __name__ == "__main__":
    unittest.main()

4.2 使用 pytest 测试

import pytest
from unittest.mock import Mock

@pytest.fixture
def client():
    """提供 REST 客户端实例"""
    return RESTClient("https://api.example.com")

def test_get_all_items(client, mocker):
    """测试获取所有项目"""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = [{"id": 1}, {"id": 2}]
    mock_response.raise_for_status.return_value = None
    
    mocker.patch.object(client.session, 'get', return_value=mock_response)
    
    result = client.list_all("items")
    assert len(result) == 2
    assert result[0]["id"] == 1

def test_delete_item(client, mocker):
    """测试删除项目"""
    mock_response = Mock()
    mock_response.status_code = 204
    mock_response.text = ""
    mock_response.raise_for_status.return_value = None
    
    mocker.patch.object(client.session, 'delete', return_value=mock_response)
    
    result = client.delete_one("items", 1)
    assert result["status"] == "success"

@pytest.mark.parametrize("status_code,expected", [
    (200, True),
    (404, False),
    (500, False),
])
def test_response_handling(client, status_code, expected):
    """测试不同状态码的响应处理"""
    # 测试逻辑...
    pass

第五章:实战项目:任务管理客户端

5.1 项目结构

task_manager/
├── __init__.py
├── client.py          # REST 客户端
├── models.py          # 数据模型
├── service.py         # 业务逻辑
├── cli.py             # 命令行接口
├── config.py          # 配置管理
└── tests/
    ├── __init__.py
    ├── test_client.py
    └── test_service.py

5.2 数据模型

# models.py
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import Optional, List

@dataclass
class Task:
    """任务数据模型"""
    id: Optional[int] = None
    title: str = ""
    description: str = ""
    completed: bool = False
    priority: int = 1  # 1-5
    due_date: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None
    
    def to_dict(self) -> dict:
        """转换为字典"""
        return {k: v for k, v in asdict(self).items() if v is not None}
    
    def is_overdue(self) -> bool:
        """检查任务是否过期"""
        if not self.due_date:
            return False
        due = datetime.fromisoformat(self.due_date)
        return due < datetime.now() and not self.completed

5.3 业务服务层

# service.py
from typing import List, Optional
from client import RESTClient
from models import Task
from config import Config

class TaskService:
    """任务管理服务"""
    
    def __init__(self, config: Config = None):
        self.config = config or Config()
        self.client = RESTClient(
            self.config.api_url,
            headers=self.config.headers
        )
        self._cache = {}
    
    def get_all_tasks(self, filter_completed: Optional[bool] = None) -> List[Task]:
        """获取所有任务,支持过滤"""
        params = {}
        if filter_completed is not None:
            params['completed'] = str(filter_completed).lower()
        
        data = self.client.list_all("tasks", **params)
        return [Task(**item) for item in data]
    
    def get_task(self, task_id: int) -> Optional[Task]:
        """获取单个任务"""
        try:
            data = self.client.get_by_id("tasks", task_id)
            return Task(**data)
        except APIError as e:
            if e.status_code == 404:
                return None
            raise
    
    def create_task(self, title: str, description: str = "", 
                    priority: int = 1, due_date: str = None) -> Task:
        """创建新任务"""
        task = Task(
            title=title,
            description=description,
            priority=priority,
            due_date=due_date
        )
        data = self.client.create("tasks", task.to_dict())
        return Task(**data)
    
    def update_task(self, task_id: int, **updates) -> Task:
        """更新任务(部分更新)"""
        data = self.client.partial_update("tasks", task_id, updates)
        return Task(**data)
    
    def complete_task(self, task_id: int) -> Task:
        """标记任务为已完成"""
        return self.update_task(task_id, completed=True)
    
    def delete_task(self, task_id: int) -> bool:
        """删除任务"""
        try:
            self.client.delete_one("tasks", task_id)
            return True
        except APIError:
            return False
    
    def get_overdue_tasks(self) -> List[Task]:
        """获取过期的任务"""
        tasks = self.get_all_tasks(filter_completed=False)
        return [t for t in tasks if t.is_overdue()]
    
    def get_statistics(self) -> dict:
        """获取任务统计信息"""
        tasks = self.get_all_tasks()
        total = len(tasks)
        completed = sum(1 for t in tasks if t.completed)
        overdue = sum(1 for t in tasks if t.is_overdue())
        
        return {
            'total': total,
            'completed': completed,
            'pending': total - completed,
            'overdue': overdue,
            'completion_rate': (completed / total * 100) if total > 0 else 0
        }

5.4 命令行接口

# cli.py
import argparse
import sys
from datetime import datetime
from service import TaskService
from config import Config

class TaskCLI:
    """任务管理命令行界面"""
    
    def __init__(self):
        self.service = TaskService()
        self.setup_parser()
    
    def setup_parser(self):
        """配置命令行参数解析器"""
        self.parser = argparse.ArgumentParser(
            description="任务管理命令行工具",
            usage="python cli.py <command> [options]"
        )
        subparsers = self.parser.add_subparsers(dest='command', help='子命令')
        
        # list 命令
        list_parser = subparsers.add_parser('list', help='列出所有任务')
        list_parser.add_argument('--completed', action='store_true', help='显示已完成的任务')
        list_parser.add_argument('--pending', action='store_true', help='仅显示未完成的任务')
        list_parser.add_argument('--overdue', action='store_true', help='显示过期的任务')
        
        # add 命令
        add_parser = subparsers.add_parser('add', help='添加任务')
        add_parser.add_argument('title', help='任务标题')
        add_parser.add_argument('--description', '-d', help='任务描述')
        add_parser.add_argument('--priority', '-p', type=int, default=1, choices=range(1, 6), help='优先级 (1-5)')
        add_parser.add_argument('--due', help='截止日期 (YYYY-MM-DD)')
        
        # get 命令
        get_parser = subparsers.add_parser('get', help='查看任务详情')
        get_parser.add_argument('id', type=int, help='任务 ID')
        
        # complete 命令
        complete_parser = subparsers.add_parser('complete', help='标记任务为已完成')
        complete_parser.add_argument('id', type=int, help='任务 ID')
        
        # delete 命令
        delete_parser = subparsers.add_parser('delete', help='删除任务')
        delete_parser.add_argument('id', type=int, help='任务 ID')
        
        # stats 命令
        subparsers.add_parser('stats', help='查看统计信息')
    
    def run(self, args=None):
        """运行 CLI"""
        args = self.parser.parse_args(args)
        
        if not args.command:
            self.parser.print_help()
            return
        
        # 路由到对应的处理方法
        handler = getattr(self, f'cmd_{args.command}', None)
        if handler:
            handler(args)
        else:
            print(f"未知命令: {args.command}")
    
    def cmd_list(self, args):
        """列出任务"""
        if args.overdue:
            tasks = self.service.get_overdue_tasks()
            print(f"📅 过期任务 ({len(tasks)}):")
        elif args.completed:
            tasks = self.service.get_all_tasks(filter_completed=True)
            print(f"✅ 已完成的任务 ({len(tasks)}):")
        elif args.pending:
            tasks = self.service.get_all_tasks(filter_completed=False)
            print(f"⏳ 未完成的任务 ({len(tasks)}):")
        else:
            tasks = self.service.get_all_tasks()
            print(f"📋 所有任务 ({len(tasks)}):")
        
        for task in tasks:
            status = "✅" if task.completed else "⏳"
            overdue = "🔥" if task.is_overdue() else ""
            print(f"  {status} [{task.id}] {task.title} (优先级: {task.priority}) {overdue}")
    
    def cmd_add(self, args):
        """添加任务"""
        try:
            task = self.service.create_task(
                title=args.title,
                description=args.description or "",
                priority=args.priority,
                due_date=args.due
            )
            print(f"✅ 任务已创建: [{task.id}] {task.title}")
        except Exception as e:
            print(f"❌ 创建失败: {e}")
    
    def cmd_get(self, args):
        """查看任务详情"""
        task = self.service.get_task(args.id)
        if task:
            print(f"\n📌 任务详情:")
            print(f"  ID: {task.id}")
            print(f"  标题: {task.title}")
            print(f"  描述: {task.description or '无'}")
            print(f"  状态: {'✅ 已完成' if task.completed else '⏳ 未完成'}")
            print(f"  优先级: {task.priority}")
            print(f"  截止日期: {task.due_date or '无'}")
            print(f"  创建时间: {task.created_at or '未知'}")
            if task.is_overdue():
                print("  ⚠️ 此任务已过期!")
        else:
            print(f"❌ 未找到 ID 为 {args.id} 的任务")
    
    def cmd_complete(self, args):
        """标记任务完成"""
        try:
            task = self.service.complete_task(args.id)
            print(f"✅ 任务已完成: [{task.id}] {task.title}")
        except Exception as e:
            print(f"❌ 操作失败: {e}")
    
    def cmd_delete(self, args):
        """删除任务"""
        confirm = input(f"⚠️ 确认删除任务 {args.id}? (y/N): ")
        if confirm.lower() == 'y':
            if self.service.delete_task(args.id):
                print(f"✅ 任务 {args.id} 已删除")
            else:
                print(f"❌ 删除失败")
        else:
            print("操作已取消")
    
    def cmd_stats(self, args):
        """显示统计信息"""
        stats = self.service.get_statistics()
        print("\n📊 任务统计:")
        print(f"  总任务数: {stats['total']}")
        print(f"  已完成: {stats['completed']}")
        print(f"  未完成: {stats['pending']}")
        print(f"  已过期: {stats['overdue']}")
        print(f"  完成率: {stats['completion_rate']:.1f}%")
        
        # 显示进度条
        bar_len = 20
        filled = int(stats['completion_rate'] / 100 * bar_len)
        bar = "█" * filled + "░" * (bar_len - filled)
        print(f"  进度: [{bar}] {stats['completion_rate']:.0f}%")


if __name__ == "__main__":
    cli = TaskCLI()
    cli.run()

5.5 配置文件

# config.py
import os
from typing import Dict, Optional

class Config:
    """配置管理"""
    
    def __init__(self):
        self.api_url = os.getenv('API_URL', 'https://api.example.com/v1')
        self.api_key = os.getenv('API_KEY', '')
        self.timeout = int(os.getenv('REQUEST_TIMEOUT', '30'))
        self.max_retries = int(os.getenv('MAX_RETRIES', '3'))
    
    @property
    def headers(self) -> Dict[str, str]:
        """请求头"""
        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
        if self.api_key:
            headers['Authorization'] = f'Bearer {self.api_key}'
        return headers

5.6 使用示例

# 添加任务
python cli.py add "完成项目报告" --description "撰写年度项目总结报告" --priority 3 --due 2026-09-30

# 列出所有任务
python cli.py list

# 列出未完成任务
python cli.py list --pending

# 查看任务详情
python cli.py get 1

# 标记任务完成
python cli.py complete 1

# 查看统计信息
python cli.py stats

# 删除任务
python cli.py delete 1

第六章:常见问题与解决方案

6.1 SSL 证书验证

# 跳过 SSL 验证(不推荐生产环境)
response = requests.get(url, verify=False)

# 使用自定义证书
response = requests.get(url, verify='/path/to/cert.pem')

# 在企业环境中,可能需要禁用警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

6.2 处理大文件响应

def download_large_file(url, save_path):
    """流式下载大文件"""
    response = requests.get(url, stream=True)
    response.raise_for_status()
    
    with open(save_path, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)
    
    return save_path

6.3 处理重定向

# 允许重定向(默认)
response = requests.get(url, allow_redirects=True)

# 禁止重定向
response = requests.get(url, allow_redirects=False)

6.4 会话管理(保持 Cookie)

# 使用 Session 自动管理 Cookie
session = requests.Session()
session.get('https://example.com/login')  # 设置 Cookie
response = session.get('https://example.com/dashboard')  # 自动携带 Cookie

6.5 代理配置

proxies = {
    'http': 'http://proxy.example.com:8080',
    'https': 'https://proxy.example.com:8080',
}
response = requests.get(url, proxies=proxies)

第七章:性能优化与监控

7.1 连接池配置

class OptimizedRESTClient(RESTClient):
    """优化性能的 REST 客户端"""
    
    def __init__(self, base_url: str, **kwargs):
        super().__init__(base_url, **