pytest-dsl 0.11.1__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from typing import Dict, Any
3
+ from typing import Dict, Any
4
4
  import requests
5
5
  from urllib.parse import urljoin
6
6
  from pytest_dsl.core.yaml_vars import yaml_vars
@@ -11,11 +11,11 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
  class HTTPClient:
13
13
  """HTTP客户端类
14
-
14
+
15
15
  负责管理HTTP请求会话和发送请求
16
16
  """
17
-
18
- def __init__(self,
17
+
18
+ def __init__(self,
19
19
  name: str = "default",
20
20
  base_url: str = "",
21
21
  headers: Dict[str, str] = None,
@@ -26,7 +26,7 @@ class HTTPClient:
26
26
  proxies: Dict[str, str] = None,
27
27
  auth_config: Dict[str, Any] = None):
28
28
  """初始化HTTP客户端
29
-
29
+
30
30
  Args:
31
31
  name: 客户端名称
32
32
  base_url: 基础URL
@@ -50,38 +50,38 @@ class HTTPClient:
50
50
  "retry_on_status": [500, 502, 503, 504]
51
51
  }
52
52
  self.proxies = proxies or {}
53
-
53
+
54
54
  # 处理认证配置
55
55
  self.auth_provider = None
56
56
  if auth_config:
57
57
  self.auth_provider = create_auth_provider(auth_config)
58
58
  if not self.auth_provider:
59
59
  logger.warning(f"无法创建认证提供者: {auth_config}")
60
-
60
+
61
61
  # 创建会话
62
62
  self._session = requests.Session() if self.use_session else None
63
63
  if self.use_session and self.default_headers:
64
64
  self._session.headers.update(self.default_headers)
65
-
65
+
66
66
  def reset_session(self):
67
67
  """完全重置会话对象,创建一个新的会话实例
68
-
68
+
69
69
  当需要彻底清除所有会话状态(例如认证信息、cookies等)时使用
70
70
  """
71
71
  if self.use_session:
72
72
  # 关闭当前会话
73
73
  if self._session:
74
74
  self._session.close()
75
-
75
+
76
76
  # 创建新会话
77
77
  self._session = requests.Session()
78
-
78
+
79
79
  # 重新应用默认头
80
80
  if self.default_headers:
81
81
  self._session.headers.update(self.default_headers)
82
-
82
+
83
83
  logger.debug(f"会话已完全重置: {self.name}")
84
-
84
+
85
85
  def make_request(self, method: str, url: str, **request_kwargs) -> requests.Response:
86
86
  """发送HTTP请求
87
87
 
@@ -102,7 +102,8 @@ class HTTPClient:
102
102
  if disable_auth:
103
103
  # 如果有认证提供者,使用其清理逻辑
104
104
  if self.auth_provider:
105
- request_kwargs = self.auth_provider.clean_auth_state(request_kwargs)
105
+ request_kwargs = self.auth_provider.clean_auth_state(
106
+ request_kwargs)
106
107
  else:
107
108
  # 默认清理逻辑:移除所有认证相关的头
108
109
  auth_headers = [
@@ -115,49 +116,54 @@ class HTTPClient:
115
116
  request_kwargs['headers'].pop(header, None)
116
117
  # 移除认证参数
117
118
  request_kwargs.pop('auth', None)
118
-
119
+
119
120
  # 如果使用会话,并且认证提供者没有自己的会话管理,则使用默认的会话重置
120
121
  if self.use_session and not hasattr(self.auth_provider, 'manage_session'):
121
122
  self.reset_session()
122
-
123
+
123
124
  elif self.auth_provider and 'auth' not in request_kwargs:
124
125
  # 应用认证提供者
125
- request_kwargs = self.auth_provider.apply_auth(self.base_url, request_kwargs)
126
+ request_kwargs = self.auth_provider.apply_auth(
127
+ self.base_url, request_kwargs)
126
128
  # 如果使用会话,更新会话头
127
129
  if self.use_session and 'headers' in request_kwargs:
128
130
  self._session.headers.update(request_kwargs['headers'])
129
131
 
130
132
  # 调用认证提供者的请求前钩子
131
133
  if self.auth_provider and not disable_auth:
132
- request_kwargs = self.auth_provider.pre_request_hook(method, url, request_kwargs)
134
+ request_kwargs = self.auth_provider.pre_request_hook(
135
+ method, url, request_kwargs)
133
136
 
134
137
  # 记录请求详情
135
138
  logger.debug("=== HTTP请求详情 ===")
136
139
  logger.debug(f"方法: {method}")
137
140
  logger.debug(f"URL: {url}")
138
141
  if 'headers' in request_kwargs:
139
- safe_headers = {k: '***' if k.lower() in ['authorization', 'x-api-key', 'token'] else v
140
- for k, v in request_kwargs['headers'].items()}
141
- logger.debug(f"请求头: {json.dumps(safe_headers, indent=2, ensure_ascii=False)}")
142
+ safe_headers = {k: '***' if k.lower() in ['authorization', 'x-api-key', 'token'] else v
143
+ for k, v in request_kwargs['headers'].items()}
144
+ logger.debug(
145
+ f"请求头: {json.dumps(safe_headers, indent=2, ensure_ascii=False)}")
142
146
  if 'params' in request_kwargs:
143
- logger.debug(f"查询参数: {json.dumps(request_kwargs['params'], indent=2, ensure_ascii=False)}")
147
+ logger.debug(
148
+ f"查询参数: {json.dumps(request_kwargs['params'], indent=2, ensure_ascii=False)}")
144
149
  if 'json' in request_kwargs:
145
- logger.debug(f"JSON请求体: {json.dumps(request_kwargs['json'], indent=2, ensure_ascii=False)}")
150
+ logger.debug(
151
+ f"JSON请求体: {json.dumps(request_kwargs['json'], indent=2, ensure_ascii=False)}")
146
152
  if 'data' in request_kwargs:
147
153
  logger.debug(f"表单数据: {request_kwargs['data']}")
148
154
 
149
155
  # 为超时设置默认值
150
156
  if 'timeout' not in request_kwargs:
151
157
  request_kwargs['timeout'] = self.timeout
152
-
158
+
153
159
  # 为SSL验证设置默认值
154
160
  if 'verify' not in request_kwargs:
155
161
  request_kwargs['verify'] = self.verify_ssl
156
-
162
+
157
163
  # 应用代理配置
158
164
  if self.proxies and 'proxies' not in request_kwargs:
159
165
  request_kwargs['proxies'] = self.proxies
160
-
166
+
161
167
  try:
162
168
  # 发送请求
163
169
  if self.use_session:
@@ -173,10 +179,12 @@ class HTTPClient:
173
179
  # 记录响应详情
174
180
  logger.debug("\n=== HTTP响应详情 ===")
175
181
  logger.debug(f"状态码: {response.status_code}")
176
- logger.debug(f"响应头: {json.dumps(dict(response.headers), indent=2, ensure_ascii=False)}")
182
+ logger.debug(
183
+ f"响应头: {json.dumps(dict(response.headers), indent=2, ensure_ascii=False)}")
177
184
  try:
178
185
  if 'application/json' in response.headers.get('Content-Type', ''):
179
- logger.debug(f"响应体 (JSON): {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
186
+ logger.debug(
187
+ f"响应体 (JSON): {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
180
188
  else:
181
189
  logger.debug(f"响应体: {response.text}")
182
190
  except Exception as e:
@@ -186,11 +194,11 @@ class HTTPClient:
186
194
  # 添加响应时间
187
195
  if not hasattr(response, 'elapsed_ms'):
188
196
  response.elapsed_ms = response.elapsed.total_seconds() * 1000
189
-
197
+
190
198
  # 调用认证提供者的响应处理钩子
191
199
  if self.auth_provider and not disable_auth:
192
200
  self.auth_provider.post_response_hook(response, request_kwargs)
193
-
201
+
194
202
  return response
195
203
  except requests.exceptions.RequestException as e:
196
204
  logger.error(f"HTTP请求异常: {type(e).__name__}: {str(e)}")
@@ -198,67 +206,72 @@ class HTTPClient:
198
206
  except Exception as e:
199
207
  logger.error(f"未预期的异常: {type(e).__name__}: {str(e)}")
200
208
  # 将非请求异常包装为请求异常
201
- raised_exception = requests.exceptions.RequestException(f"HTTP请求过程中发生错误: {str(e)}")
209
+ raised_exception = requests.exceptions.RequestException(
210
+ f"HTTP请求过程中发生错误: {str(e)}")
202
211
  raised_exception.__cause__ = e
203
212
  raise raised_exception
204
-
213
+
205
214
  def _log_request(self, method: str, url: str, request_kwargs: Dict[str, Any]) -> None:
206
215
  """记录请求信息
207
-
216
+
208
217
  Args:
209
218
  method: HTTP方法
210
219
  url: 请求URL
211
220
  request_kwargs: 请求参数
212
221
  """
213
222
  logger.info(f"发送 {method} 请求到 {url}")
214
-
223
+
215
224
  # 打印请求头 (排除敏感信息)
216
225
  headers = request_kwargs.get("headers", {})
217
226
  safe_headers = headers.copy()
218
-
227
+
219
228
  for key in headers:
220
229
  if key.lower() in ["authorization", "x-api-key", "token", "api-key"]:
221
230
  safe_headers[key] = "***REDACTED***"
222
-
231
+
223
232
  logger.debug(f"请求头: {safe_headers}")
224
-
233
+
225
234
  # 打印查询参数
226
235
  if request_kwargs.get("params"):
227
236
  logger.debug(f"查询参数: {request_kwargs['params']}")
228
-
237
+
229
238
  # 打印请求体
230
239
  if request_kwargs.get("json"):
231
- logger.debug(f"JSON请求体: {json.dumps(request_kwargs['json'], ensure_ascii=False)}")
240
+ logger.debug(
241
+ f"JSON请求体: {json.dumps(request_kwargs['json'], ensure_ascii=False)}")
232
242
  elif request_kwargs.get("data"):
233
243
  logger.debug(f"表单数据: {request_kwargs['data']}")
234
-
244
+
235
245
  # 打印文件信息
236
246
  if request_kwargs.get("files"):
237
- file_info = {k: f"<文件: {getattr(v, 'name', '未知文件')}>" for k, v in request_kwargs["files"].items()}
247
+ file_info = {
248
+ k: f"<文件: {getattr(v, 'name', '未知文件')}>" for k, v in request_kwargs["files"].items()}
238
249
  logger.debug(f"上传文件: {file_info}")
239
-
250
+
240
251
  def _log_response(self, response: requests.Response) -> None:
241
252
  """记录响应信息
242
-
253
+
243
254
  Args:
244
255
  response: 响应对象
245
256
  """
246
- logger.info(f"收到响应: {response.status_code} {response.reason} ({response.elapsed_ms:.2f}ms)")
247
-
257
+ logger.info(
258
+ f"收到响应: {response.status_code} {response.reason} ({response.elapsed_ms:.2f}ms)")
259
+
248
260
  # 打印响应头
249
261
  logger.debug(f"响应头: {dict(response.headers)}")
250
-
262
+
251
263
  # 尝试打印响应体
252
264
  try:
253
265
  if 'application/json' in response.headers.get('Content-Type', ''):
254
- logger.debug(f"响应体 (JSON): {json.dumps(response.json(), ensure_ascii=False)}")
266
+ logger.debug(
267
+ f"响应体 (JSON): {json.dumps(response.json(), ensure_ascii=False)}")
255
268
  elif len(response.content) < 1024: # 只打印小响应
256
269
  logger.debug(f"响应体: {response.text}")
257
270
  else:
258
271
  logger.debug(f"响应体: <{len(response.content)} 字节>")
259
272
  except Exception as e:
260
273
  logger.debug(f"无法打印响应体: {str(e)}")
261
-
274
+
262
275
  def close(self) -> None:
263
276
  """关闭客户端会话"""
264
277
  if self._session:
@@ -268,21 +281,21 @@ class HTTPClient:
268
281
 
269
282
  class HTTPClientManager:
270
283
  """HTTP客户端管理器
271
-
284
+
272
285
  负责管理多个HTTP客户端实例和会话
273
286
  """
274
-
287
+
275
288
  def __init__(self):
276
289
  """初始化客户端管理器"""
277
290
  self._clients: Dict[str, HTTPClient] = {}
278
291
  self._sessions: Dict[str, HTTPClient] = {}
279
-
292
+
280
293
  def create_client(self, config: Dict[str, Any]) -> HTTPClient:
281
294
  """从配置创建客户端
282
-
295
+
283
296
  Args:
284
297
  config: 客户端配置
285
-
298
+
286
299
  Returns:
287
300
  HTTPClient实例
288
301
  """
@@ -299,24 +312,24 @@ class HTTPClientManager:
299
312
  auth_config=config.get("auth", None) # 获取认证配置
300
313
  )
301
314
  return client
302
-
315
+
303
316
  def get_client(self, name: str = "default") -> HTTPClient:
304
317
  """获取或创建客户端
305
-
318
+
306
319
  Args:
307
320
  name: 客户端名称
308
-
321
+
309
322
  Returns:
310
323
  HTTPClient实例
311
324
  """
312
325
  # 如果客户端已存在,直接返回
313
326
  if name in self._clients:
314
327
  return self._clients[name]
315
-
328
+
316
329
  # 从YAML变量中读取客户端配置
317
330
  http_clients = yaml_vars.get_variable("http_clients") or {}
318
331
  client_config = http_clients.get(name)
319
-
332
+
320
333
  if not client_config:
321
334
  # 如果请求的是default但配置中没有,创建一个默认客户端
322
335
  if name == "default":
@@ -326,91 +339,91 @@ class HTTPClientManager:
326
339
  return client
327
340
  else:
328
341
  raise ValueError(f"未找到名为 '{name}' 的HTTP客户端配置")
329
-
342
+
330
343
  # 创建新客户端
331
344
  client_config["name"] = name
332
345
  client = self.create_client(client_config)
333
346
  self._clients[name] = client
334
347
  return client
335
-
348
+
336
349
  def get_session(self, name: str = "default", client_name: str = None) -> HTTPClient:
337
350
  """获取或创建命名会话
338
-
351
+
339
352
  Args:
340
353
  name: 会话名称
341
354
  client_name: 用于创建会话的客户端名称
342
-
355
+
343
356
  Returns:
344
357
  HTTPClient实例
345
358
  """
346
359
  session_key = name
347
-
360
+
348
361
  # 如果会话已存在,直接返回
349
362
  if session_key in self._sessions:
350
363
  return self._sessions[session_key]
351
-
364
+
352
365
  # 使用指定的客户端配置创建新会话
353
366
  client_name = client_name or name
354
367
  client_config = self._get_client_config(client_name)
355
-
368
+
356
369
  if not client_config:
357
370
  raise ValueError(f"未找到名为 '{client_name}' 的HTTP客户端配置,无法创建会话")
358
-
371
+
359
372
  # 创建新会话
360
373
  client_config["name"] = f"session_{name}"
361
374
  client_config["session"] = True # 确保启用会话
362
375
  session = self.create_client(client_config)
363
376
  self._sessions[session_key] = session
364
377
  return session
365
-
378
+
366
379
  def _get_client_config(self, name: str) -> Dict[str, Any]:
367
380
  """从YAML变量获取客户端配置
368
-
381
+
369
382
  Args:
370
383
  name: 客户端名称
371
-
384
+
372
385
  Returns:
373
386
  客户端配置
374
387
  """
375
388
  http_clients = yaml_vars.get_variable("http_clients") or {}
376
389
  client_config = http_clients.get(name)
377
-
390
+
378
391
  if not client_config and name == "default":
379
392
  # 如果没有默认配置,返回空配置
380
393
  return {"name": "default"}
381
-
394
+
382
395
  return client_config
383
-
396
+
384
397
  def close_client(self, name: str) -> None:
385
398
  """关闭指定的客户端
386
-
399
+
387
400
  Args:
388
401
  name: 客户端名称
389
402
  """
390
403
  if name in self._clients:
391
404
  self._clients[name].close()
392
405
  del self._clients[name]
393
-
406
+
394
407
  def close_session(self, name: str) -> None:
395
408
  """关闭指定的会话
396
-
409
+
397
410
  Args:
398
411
  name: 会话名称
399
412
  """
400
413
  if name in self._sessions:
401
414
  self._sessions[name].close()
402
415
  del self._sessions[name]
403
-
416
+
404
417
  def close_all(self) -> None:
405
418
  """关闭所有客户端和会话"""
406
419
  for client in self._clients.values():
407
420
  client.close()
408
421
  self._clients.clear()
409
-
422
+
410
423
  for session in self._sessions.values():
411
424
  session.close()
412
425
  self._sessions.clear()
413
426
 
414
427
 
415
428
  # 创建全局HTTP客户端管理器实例
416
- http_client_manager = HTTPClientManager()
429
+ http_client_manager = HTTPClientManager()