别再手动拼Headers了!用Python requests封装一个带Token的万能请求类(附Postman对比)
告别重复造轮子:打造Python requests的智能请求封装库
每次调用API时手动拼接Headers和Token的日子该结束了。如果你已经厌倦了在代码中反复复制粘贴相似的请求代码,或者羡慕Postman那种一键配置Headers的便捷体验,那么是时候升级你的开发工具链了。本文将带你从零构建一个高度封装的Python请求类,支持自动Token管理、多参数格式适配和智能异常处理,让HTTP请求变得像发送电子邮件一样简单。
1. 为什么需要封装requests库?
在日常开发中,我们经常需要与各种API打交道。无论是调用内部微服务还是接入第三方平台,HTTP请求都是最基础的交互方式。Python的requests库虽然简洁易用,但在企业级开发中直接使用原生方法会导致大量重复代码,尤其是处理以下场景时:
- Token管理 :每次请求都需要手动添加Authorization头
- 参数格式化 :根据API要求切换JSON、Form-data等格式
- 异常处理 :对超时、认证失败等场景缺乏统一处理
- 日志记录 :难以集中管理请求日志和监控
# 典型的不封装代码 - 每个请求都需要重复配置
headers = {
'Authorization': 'Bearer xxxxx',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.example.com/data',
headers=headers,
params={'page': 1},
timeout=10
)
Postman之所以受欢迎,正是因为它解决了这些问题。我们的目标是在Python代码中实现类似的开发体验,同时保持代码的灵活性和可维护性。
2. 设计智能请求类的核心架构
一个优秀的请求封装类应该像瑞士军刀一样多功能,又像专业工具一样精准。下面是我们设计的核心功能矩阵:
| 功能模块 | 实现要点 | 技术方案 |
|---|---|---|
| 认证管理 | 自动处理Token生命周期 | 可刷新的JWT令牌存储 |
| 请求构造 | 支持多种参数格式 | 动态Content-Type检测 |
| 异常处理 | 统一错误捕获和重试机制 | 装饰器+自定义异常类 |
| 性能监控 | 请求耗时统计和限流 | 时间戳记录+令牌桶算法 |
| 日志记录 | 完整的请求/响应追踪 | 结构化日志输出 |
2.1 基础请求类实现
我们从最基础的封装开始,创建一个支持GET/POST方法的类骨架:
import requests
from typing import Optional, Dict, Any
class SmartRequest:
def __init__(self, base_url: str = '', default_timeout: int = 30):
self.base_url = base_url
self.default_timeout = default_timeout
self.session = requests.Session()
self._token = None
@property
def token(self):
"""获取当前认证令牌"""
return self._token
@token.setter
def token(self, value: str):
"""设置认证令牌"""
self._token = value
if value:
self.session.headers.update({'Authorization': f'Bearer {value}'})
else:
self.session.headers.pop('Authorization', None)
这个基础版本已经解决了Token自动管理的问题。使用时只需设置一次Token,后续所有请求都会自动携带:
client = SmartRequest('https://api.example.com')
client.token = 'your_access_token' # 设置一次,全局生效
response = client.get('/users') # 自动携带Token
3. 多参数格式的智能适配
现代API通常支持多种参数格式,我们的封装需要能够自动处理这些差异。以下是常见的三种参数格式及其特点:
- JSON格式 :
Content-Type: application/json- 适合复杂嵌套数据结构
- 需要明确指定内容类型
- Form-data :
Content-Type: multipart/form-data- 适合文件上传和混合数据
- 边界值自动生成
- URL编码表单 :
Content-Type: application/x-www-form-urlencoded- 适合简单键值对
- 需要URL编码
我们扩展SmartRequest类,增加智能参数检测功能:
class SmartRequest:
# ... 延续之前的代码 ...
def _detect_content_type(self, data: Any, files: Any) -> str:
"""自动检测合适的Content-Type"""
if files is not None:
return 'multipart/form-data'
elif isinstance(data, (dict, list)):
return 'application/json'
return 'application/x-www-form-urlencoded'
def request(self, method: str, endpoint: str,
data: Optional[Dict] = None,
files: Optional[Dict] = None,
**kwargs) -> requests.Response:
"""统一请求方法"""
url = f"{self.base_url}{endpoint}"
content_type = self._detect_content_type(data, files)
headers = kwargs.pop('headers', {})
headers['Content-Type'] = content_type
if content_type == 'application/json':
kwargs['json'] = data
else:
kwargs['data'] = data
return self.session.request(
method=method.upper(),
url=url,
files=files,
timeout=self.default_timeout,
headers=headers,
**kwargs
)
def get(self, endpoint: str, params: Optional[Dict] = None, **kwargs):
return self.request('GET', endpoint, params=params, **kwargs)
def post(self, endpoint: str, data: Optional[Dict] = None, **kwargs):
return self.request('POST', endpoint, data=data, **kwargs)
现在客户端代码可以完全不关心参数格式问题:
# JSON请求
client.post('/users', data={'name': 'Alice', 'age': 25})
# 表单请求
client.post('/login', data={'username': 'admin', 'password': '123'})
# 文件上传
with open('report.pdf', 'rb') as f:
client.post('/upload', files={'file': f})
4. 异常处理与重试机制
健壮的API客户端需要妥善处理各种异常情况。我们设计一个分层的异常处理策略:
常见异常分类:
- 网络层异常(连接超时、DNS解析失败)
- 应用层异常(401未授权、403禁止访问)
- 业务逻辑异常(自定义错误码)
- 数据校验异常(响应格式不符)
实现带指数退避的重试机制:
from time import sleep
from functools import wraps
class APIError(Exception):
"""自定义API异常基类"""
pass
def retry(max_attempts=3, initial_delay=1):
"""重试装饰器"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
attempts = 0
delay = initial_delay
last_exception = None
while attempts < max_attempts:
try:
return f(*args, **kwargs)
except (requests.Timeout, requests.ConnectionError) as e:
last_exception = e
attempts += 1
if attempts < max_attempts:
sleep(delay)
delay *= 2 # 指数退避
raise APIError(f"请求失败,重试{max_attempts}次后仍然失败") from last_exception
return wrapper
return decorator
class SmartRequest:
# ... 延续之前的代码 ...
@retry(max_attempts=3)
def request(self, method: str, endpoint: str, **kwargs):
try:
response = self._raw_request(method, endpoint, **kwargs)
response.raise_for_status() # 检查HTTP状态码
return response
except requests.HTTPError as e:
if e.response.status_code == 401:
self._handle_unauthorized()
raise APIError(f"API请求失败: {e}") from e
def _raw_request(self, method: str, endpoint: str, **kwargs):
"""原始请求方法,不包含重试逻辑"""
# ... 实际请求实现 ...
5. 高级功能扩展
5.1 请求日志与监控
在生产环境中,详细的请求日志对调试和监控至关重要:
import logging
class SmartRequest:
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger('smart_request')
# ... 其他初始化 ...
def request(self, method: str, endpoint: str, **kwargs):
start_time = time.time()
request_id = str(uuid.uuid4())
self.logger.info(
"请求开始 | ID: %s | %s %s",
request_id, method.upper(), endpoint
)
try:
response = self._raw_request(method, endpoint, **kwargs)
elapsed = time.time() - start_time
self.logger.info(
"请求成功 | ID: %s | 耗时: %.2fs | 状态码: %d",
request_id, elapsed, response.status_code
)
return response
except Exception as e:
self.logger.error(
"请求失败 | ID: %s | 错误: %s",
request_id, str(e)
)
raise
5.2 自动Token刷新
对于使用短期Token的系统,我们可以实现自动刷新逻辑:
class SmartRequest:
# ... 延续之前的代码 ...
def _handle_unauthorized(self):
"""处理401未授权错误"""
if self._refresh_token:
try:
new_token = self._refresh_access_token()
self.token = new_token
return
except Exception as e:
self.logger.warning("Token刷新失败: %s", str(e))
raise APIError("认证失败,请重新登录")
def _refresh_access_token(self) -> str:
"""使用refresh_token获取新的access_token"""
response = self._raw_request(
'POST',
'/auth/refresh',
json={'refresh_token': self._refresh_token}
)
return response.json()['access_token']
6. 完整实现与使用示例
将上述所有功能整合,我们得到最终的SmartRequest类:
# smart_request.py
import requests
import logging
import time
import uuid
from typing import Optional, Dict, Any
class APIError(Exception):
pass
class SmartRequest:
def __init__(self, base_url: str = '', default_timeout: int = 30):
self.base_url = base_url.rstrip('/')
self.default_timeout = default_timeout
self.session = requests.Session()
self._token = None
self._refresh_token = None
self.logger = logging.getLogger('smart_request')
def set_tokens(self, access_token: str, refresh_token: str = None):
self.token = access_token
self._refresh_token = refresh_token
@property
def token(self):
return self._token
@token.setter
def token(self, value: str):
self._token = value
if value:
self.session.headers.update({'Authorization': f'Bearer {value}'})
else:
self.session.headers.pop('Authorization', None)
@retry(max_attempts=3)
def request(self, method: str, endpoint: str,
data: Optional[Dict] = None,
files: Optional[Dict] = None,
**kwargs) -> requests.Response:
"""统一请求入口"""
request_id = str(uuid.uuid4())
url = f"{self.base_url}{endpoint}"
content_type = self._detect_content_type(data, files)
headers = kwargs.pop('headers', {})
headers['Content-Type'] = content_type
# 记录请求开始
self.logger.debug(
"请求开始 | ID: %s | %s %s | 参数: %s",
request_id, method.upper(), url, kwargs
)
start_time = time.time()
try:
# 实际请求执行
response = self._raw_request(
method, url,
data=data,
files=files,
headers=headers,
**kwargs
)
response.raise_for_status()
# 记录成功日志
elapsed = time.time() - start_time
self.logger.info(
"请求成功 | ID: %s | 耗时: %.2fs | 状态码: %d",
request_id, elapsed, response.status_code
)
return response
except requests.HTTPError as e:
if e.response.status_code == 401:
self._handle_unauthorized()
# 重试一次
return self.request(method, endpoint, data, files, **kwargs)
raise APIError(f"API请求失败: {e}") from e
except Exception as e:
self.logger.error(
"请求异常 | ID: %s | 错误: %s",
request_id, str(e)
)
raise APIError(f"请求发生异常: {e}") from e
# ... 其他方法保持不变 ...
使用示例:
# 初始化客户端
client = SmartRequest('https://api.example.com/v1')
client.set_tokens(
access_token='your_access_token',
refresh_token='your_refresh_token'
)
# 调用API
try:
# 获取用户列表
users = client.get('/users', params={'active': True}).json()
# 创建新用户
new_user = client.post('/users', data={
'name': 'Bob',
'email': 'bob@example.com'
}).json()
except APIError as e:
print(f"API调用失败: {e}")
7. 与Postman的功能对比
Postman作为API测试的黄金标准,我们的Python实现能达到怎样的相似度?以下是关键功能对比:
| 功能特性 | Postman实现方式 | SmartRequest实现方式 | 优势对比 |
|---|---|---|---|
| Token管理 | 环境变量/全局变量 | 类属性自动携带 | 代码更简洁 |
| 参数格式 | 手动选择标签页 | 自动检测 | 减少配置错误 |
| 异常处理 | 通过Tests脚本验证 | 内置重试和统一异常 | 更适合生产环境 |
| 请求组织 | 集合和文件夹 | Python模块化 | 版本控制友好 |
| 环境切换 | 环境变量切换 | 不同类实例 | 类型安全 |
| 自动化测试 | Collection Runner | pytest集成 | 更适合CI/CD |
实际项目中,两者可以互补使用:Postman用于快速测试和调试,SmartRequest用于生产代码集成。特别是在以下场景中,代码封装更具优势:
- 批量操作 :需要循环调用同一API多次时
- 条件逻辑 :根据响应结果决定后续请求路径时
- 性能关键 :需要减少网络开销的高频调用场景
- 安全要求 :需要严格保护Token等敏感信息的场景
# 批量创建用户的示例 - 代码封装的优势显现
def onboard_users(users_data):
results = []
for user in users_data:
try:
resp = client.post('/users', data=user)
results.append(resp.json())
except APIError as e:
logger.error(f"用户创建失败: {user['email']} - {str(e)}")
continue
return results
更多推荐



所有评论(0)