pytest-api-framework-alpha 0.3.23__tar.gz → 0.3.25__tar.gz

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.
Files changed (34) hide show
  1. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/PKG-INFO +29 -29
  2. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/base_class.py +27 -26
  3. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/conftest.py +57 -25
  4. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/global_attribute.py +17 -4
  5. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/http_client.py +7 -5
  6. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/common.py +15 -4
  7. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/pytest_api_framework_alpha.egg-info/PKG-INFO +29 -29
  8. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/setup.cfg +4 -4
  9. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/setup.py +1 -1
  10. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/__init__.py +0 -0
  11. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/assert_webhook.py +0 -0
  12. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/db/__init__.py +0 -0
  13. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/db/mysql_db.py +0 -0
  14. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/db/redis_db.py +0 -0
  15. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/exceptions.py +0 -0
  16. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/exit_code.py +0 -0
  17. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/extract.py +0 -0
  18. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/render_data.py +0 -0
  19. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/report.py +0 -0
  20. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/retry_assert.py +0 -0
  21. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/script.py +0 -0
  22. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/startapp.py +0 -0
  23. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/__init__.py +0 -0
  24. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/date_util.py +0 -0
  25. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/encrypt.py +0 -0
  26. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/lark_util.py +0 -0
  27. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/log_util.py +0 -0
  28. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/mock_util.py +0 -0
  29. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/utils/yaml_util.py +0 -0
  30. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/framework/validate.py +0 -0
  31. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/pytest_api_framework_alpha.egg-info/SOURCES.txt +0 -0
  32. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/pytest_api_framework_alpha.egg-info/dependency_links.txt +0 -0
  33. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/pytest_api_framework_alpha.egg-info/requires.txt +0 -0
  34. {pytest_api_framework_alpha-0.3.23 → pytest_api_framework_alpha-0.3.25}/pytest_api_framework_alpha.egg-info/top_level.txt +0 -0
@@ -1,29 +1,29 @@
1
- Metadata-Version: 2.4
2
- Name: pytest-api-framework-alpha
3
- Version: 0.3.23
4
- Author: alpha
5
- Author-email:
6
- Requires-Python: >=3.6
7
- Requires-Dist: allure-pytest==2.13.1
8
- Requires-Dist: allure-python-commons==2.13.1
9
- Requires-Dist: cn2an==0.5.19
10
- Requires-Dist: DBUtils==3.1.0
11
- Requires-Dist: Faker==18.3.2
12
- Requires-Dist: jsonpath==0.82
13
- Requires-Dist: pytest==7.2.2
14
- Requires-Dist: python-dotenv==1.0.1
15
- Requires-Dist: PyYAML==6.0.1
16
- Requires-Dist: python-box==7.2.0
17
- Requires-Dist: pycryptodome==3.21.0
18
- Requires-Dist: pyotp==2.9.0
19
- Requires-Dist: pytest-order==1.3.0
20
- Requires-Dist: PyMySQL==1.1.0
21
- Requires-Dist: redis==3.5.3
22
- Requires-Dist: requests==2.25.1
23
- Requires-Dist: requests-toolbelt==1.0.0
24
- Requires-Dist: retry==0.9.2
25
- Requires-Dist: dill==0.3.8
26
- Requires-Dist: simplejson==3.20.1
27
- Dynamic: author
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
1
+ Metadata-Version: 2.4
2
+ Name: pytest-api-framework-alpha
3
+ Version: 0.3.25
4
+ Author: alpha
5
+ Author-email:
6
+ Requires-Python: >=3.6
7
+ Requires-Dist: allure-pytest==2.13.1
8
+ Requires-Dist: allure-python-commons==2.13.1
9
+ Requires-Dist: cn2an==0.5.19
10
+ Requires-Dist: DBUtils==3.1.0
11
+ Requires-Dist: Faker==18.3.2
12
+ Requires-Dist: jsonpath==0.82
13
+ Requires-Dist: pytest==7.2.2
14
+ Requires-Dist: python-dotenv==1.0.1
15
+ Requires-Dist: PyYAML==6.0.1
16
+ Requires-Dist: python-box==7.2.0
17
+ Requires-Dist: pycryptodome==3.21.0
18
+ Requires-Dist: pyotp==2.9.0
19
+ Requires-Dist: pytest-order==1.3.0
20
+ Requires-Dist: PyMySQL==1.1.0
21
+ Requires-Dist: redis==3.5.3
22
+ Requires-Dist: requests==2.25.1
23
+ Requires-Dist: requests-toolbelt==1.0.0
24
+ Requires-Dist: retry==0.9.2
25
+ Requires-Dist: dill==0.3.8
26
+ Requires-Dist: simplejson==3.20.1
27
+ Dynamic: author
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
@@ -20,7 +20,7 @@ from framework.utils.date_util import DateUtil
20
20
  from framework.retry_assert import RetryAssert
21
21
  from framework.assert_webhook import AssertWebhook
22
22
  from framework.http_client import ResponseUtil, HttpClient
23
- from framework.utils.encrypt import b64_encode, b64_decode
23
+ from framework.utils.encrypt import b64_encode
24
24
  from framework.utils.common import snake_to_pascal, SingletonFaker
25
25
  from framework.global_attribute import GlobalAttribute, _FRAMEWORK_CONTEXT, CONTEXT
26
26
  from framework.exceptions import ValidateException, RenderException, RequestException, GetAccountError, GetAppHttpError
@@ -67,8 +67,8 @@ class BaseTestCase(ExtendBaseTestCase):
67
67
  app_http = getattr(self.http, app)
68
68
  except AttributeError as e:
69
69
  raise GetAppHttpError(e)
70
- domain = self.context.get(app).get("domain")
71
70
  data = RenderData(data).render()
71
+ domain = self.context.get(app).get("domain")
72
72
  data.request.url = self.replace_domain(data.request.url, domain)
73
73
  try:
74
74
  self.response = getattr(app_http, account, HttpClient()).request(data=data, **kwargs)
@@ -112,29 +112,30 @@ class BaseTestCase(ExtendBaseTestCase):
112
112
  traceback.print_exc()
113
113
  pytest.fail(str(e))
114
114
 
115
- def post(self, app, account, url, data=None, json=None, **kwargs) -> ResponseUtil:
116
- domain = self.context.get(app).get("domain")
117
- request = {"url": urljoin(domain, url), "data": data, "json": json}
118
- request.update({"method": "post", "headers": {}, **kwargs})
119
- return self.request(app=app, account=account, data=Box({"request": request}))
120
-
121
- def get(self, app, account, url, params=None, **kwargs) -> ResponseUtil:
122
- domain = self.context.get(app).get("domain")
123
- request = {"url": urljoin(domain, url), "params": params}
124
- request.update({"method": "get", "headers": {}, **kwargs})
125
- return self.request(app=app, account=account, data=Box({"request": request}))
126
-
127
- def put(self, app, account, url, data=None, json=None, **kwargs) -> ResponseUtil:
128
- domain = self.context.get(app).get("domain")
129
- request = {"url": urljoin(domain, url), "data": data, "json": json}
130
- request.update({"method": "put", "headers": {}, **kwargs})
131
- return self.request(app=app, account=account, data=Box({"request": request}))
132
-
133
- def delete(self, app, account, url, **kwargs) -> ResponseUtil:
134
- domain = self.context.get(app).get("domain")
135
- request = {"url": urljoin(domain, url)}
136
- request.update({"method": "delete", "headers": {}, **kwargs})
137
- return self.request(app=app, account=account, data=Box({"request": request}))
115
+ def _request(self, method, app, account, url, **kwargs) -> ResponseUtil:
116
+ request = {"method": method, "url": url, "headers": {}, **kwargs}
117
+ if app:
118
+ domain = self.context.get(app).get("domain")
119
+ request["url"] = urljoin(domain, url)
120
+ return self.request(app=app, account=account, data=Box({"request": request}))
121
+
122
+ # 不需要token发送请求的
123
+ data = RenderData(Box({"request": request})).render()
124
+ del kwargs["data"]
125
+ self.response = HttpClient().request(data=data, **kwargs)
126
+ return self.response
127
+
128
+ def get(self, app=None, *, account, url, params=None, **kwargs) -> ResponseUtil:
129
+ return self._request("get", app, account, url, params=params, **kwargs)
130
+
131
+ def post(self, app=None, *, account="", url, data=None, json=None, **kwargs):
132
+ return self._request("post", app, account, url, data=data, json=json, **kwargs)
133
+
134
+ def put(self, app=None, *, account, url, data=None, json=None, **kwargs) -> ResponseUtil:
135
+ return self._request("put", app, account, url, data=data, json=json, **kwargs)
136
+
137
+ def delete(self, app=None, *, account, url, **kwargs) -> ResponseUtil:
138
+ return self._request("delete", app, account, url, **kwargs)
138
139
 
139
140
  def mysql_conn(self, db, app=None) -> MysqlDB:
140
141
  try:
@@ -366,4 +367,4 @@ class BaseTestCase(ExtendBaseTestCase):
366
367
  "expiry": 0
367
368
  }
368
369
  )
369
- assert self.response.code == 200
370
+ assert self.response.code == 200
@@ -2,7 +2,6 @@ import os
2
2
  import re
3
3
  import sys
4
4
  import copy
5
- import time
6
5
  import platform
7
6
  import threading
8
7
  import importlib
@@ -14,8 +13,9 @@ from collections import OrderedDict
14
13
  from datetime import datetime, timedelta
15
14
  from concurrent.futures import ThreadPoolExecutor, as_completed
16
15
 
17
- import dill
18
16
  import json
17
+
18
+ import requests
19
19
  import retry
20
20
  import allure
21
21
  import pytest
@@ -32,7 +32,7 @@ from framework.utils.lark_util import LarkUtil
32
32
  from framework.utils.yaml_util import CachedYamlLoader
33
33
  from framework.exceptions import MysqlDBError, RedisDBError
34
34
  from framework.global_attribute import CONTEXT, CONFIG, _FRAMEWORK_CONTEXT
35
- from framework.utils.common import snake_to_pascal, get_apps, convert_numbers_to_decimal
35
+ from framework.utils.common import snake_to_pascal, get_apps, convert_numbers_to_decimal, get_short_timestamp,now_iso8601
36
36
 
37
37
  all_app = get_apps()
38
38
  module = importlib.import_module("test_case.conftest")
@@ -159,6 +159,7 @@ def pytest_generate_tests(metafunc):
159
159
  case_data["_scenario"] = {"data": {}}
160
160
  case_data["_belong_app"] = belong_app
161
161
  metafunc.parametrize("data", [case_data, ], ids=[f'{case_data.get("title", "")}#'], scope="function")
162
+ logger.info(f"{node_id}")
162
163
  return
163
164
  if case_data.get("request") is None:
164
165
  case_data["request"] = dict()
@@ -389,7 +390,7 @@ def pytest_sessionfinish(session, exitstatus):
389
390
 
390
391
 
391
392
  def pytest_runtest_setup(item):
392
- allure.dynamic.sub_suite(item.allure_suite_mark)
393
+ allure.dynamic.sub_suite(getattr(item,"allure_suite_mark"))
393
394
  if item.funcargs.get("first"):
394
395
  test_object = item.instance
395
396
  test_object.context = CONTEXT
@@ -418,8 +419,23 @@ def pytest_runtest_call(item):
418
419
  :param item:
419
420
  :return:
420
421
  """
422
+ # 获取原始测试数据
421
423
  origin_data = item.funcargs.get("data")
422
424
  ignore_failed = origin_data.get("_ignore_failed")
425
+ # 函数式测试用例添加参数data, belong_app
426
+ http = item.funcargs.get("http")
427
+ item.funcargs["data"] = item.instance.data = Box(origin_data)
428
+ item.funcargs["scenario"] = item.instance.scenario = Box(json.loads(json.dumps(
429
+ convert_numbers_to_decimal(origin_data.get("_scenario")))))
430
+ _belong_app = origin_data.get("_belong_app")
431
+ item.funcargs["belong_app"] = item.instance.belong_app = _belong_app
432
+ item.funcargs["config"] = item.instance.config = CONFIG
433
+ item.funcargs["context"] = item.instance.context = CONTEXT
434
+ item.funcargs["retry_assert"] = item.instance.retry_assert = RetryAssert.eventually
435
+ # 类式测试用例添加参数http,data, belong_app
436
+ item.instance.http = http
437
+ logger.info(f"执行用例: {item.nodeid}")
438
+
423
439
  if not ignore_failed:
424
440
  # setup方法执行失败,则主动标记用例执行失败,不会执行用例
425
441
  if item.funcargs.get("setup_success") is False:
@@ -428,10 +444,14 @@ def pytest_runtest_call(item):
428
444
  index = item.session.items.index(item)
429
445
  pattern = re.compile(r'^(?P<prefix>.*)::[^:\[\]]+\[.*#(?P<index>\d+)\]$')
430
446
  current_match = pattern.match(item.nodeid)
447
+ if not current_match:
448
+ return
431
449
  current_cls_name = current_match.group('prefix')
432
450
  current_index = current_match.group('index')
433
451
  prev_item = item.session.items[index - 1]
434
452
  prev_match = pattern.match(prev_item.nodeid)
453
+ if not prev_match:
454
+ return
435
455
  prev_cls_name = prev_match.group("prefix")
436
456
  prev_index = prev_match.group("index")
437
457
  # 确保是同一个类,并且索引相同
@@ -446,22 +466,6 @@ def pytest_runtest_call(item):
446
466
  elif status == "failed":
447
467
  pytest.skip("the previous method execute failed")
448
468
 
449
- # 获取原始测试数据
450
- origin_data = item.funcargs.get("data")
451
- logger.info(f"执行用例: {item.nodeid}")
452
- # 函数式测试用例添加参数data, belong_app
453
- http = item.funcargs.get("http")
454
- item.funcargs["data"] = item.instance.data = Box(origin_data)
455
- item.funcargs["scenario"] = item.instance.scenario = Box(json.loads(json.dumps(
456
- convert_numbers_to_decimal(origin_data.get("_scenario")))))
457
- _belong_app = origin_data.get("_belong_app")
458
- item.funcargs["belong_app"] = item.instance.belong_app = _belong_app
459
- item.funcargs["config"] = item.instance.config = CONFIG
460
- item.funcargs["context"] = item.instance.context = CONTEXT
461
- item.funcargs["retry_assert"] = item.instance.retry_assert = RetryAssert.eventually
462
- # 类式测试用例添加参数http,data, belong_app
463
- item.instance.http = http
464
-
465
469
  # 判断token是否过期,过期则重新登录
466
470
  expire_time = _FRAMEWORK_CONTEXT.get(app=_belong_app, key="expire_time")
467
471
  if expire_time:
@@ -516,14 +520,16 @@ def pytest_runtest_makereport(item, call):
516
520
  item.status = report.outcome # 'passed', 'failed', or 'skipped'
517
521
 
518
522
 
519
- def pytest_terminal_summary(terminalreporter, exitstatus, config):
520
- """在 pytest 结束后修改统计数据或添加自定义报告"""
521
- stats = terminalreporter.stats
522
- # 统计各种测试结果
523
+ def pytest_sessionfinish(session, exitstatus):
524
+ terminalreporter = session.config.pluginmanager.get_plugin("terminalreporter")
525
+ stats = terminalreporter.stats if terminalreporter else {}
526
+
523
527
  passed = len(stats.get("passed", []))
524
528
  failed = len(stats.get("failed", []))
529
+ error = len(stats.get("error", []))
525
530
  skipped = len(stats.get("skipped", []))
526
531
  total = passed + failed + skipped
532
+
527
533
  try:
528
534
  pass_rate = round(passed / (total - skipped) * 100, 2)
529
535
  except ZeroDivisionError:
@@ -560,6 +566,32 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
560
566
  job_name=CONTEXT.get("mark"),
561
567
  env=CONTEXT.get("env")
562
568
  )
569
+ try:
570
+ requests.request(
571
+ method="POST",
572
+ url=settings.REPORT_URL,
573
+ headers={"Content-Type": "application/json", "Authorization": f"Token {settings.TOKEN}"},
574
+ json={
575
+ "total": total,
576
+ "passed": passed,
577
+ "failed": failed,
578
+ "skipped": skipped,
579
+ "pass_rate": pass_rate,
580
+ "env": CONTEXT.get("env"),
581
+ "mark": CONTEXT.get("mark"),
582
+ "command": CONTEXT.get("command"),
583
+ "start_time": CONTEXT.get("start_time"),
584
+ "start_time_format": CONTEXT.get("start_time_format"),
585
+ "end_time": get_short_timestamp(),
586
+ "end_time_format": now_iso8601(),
587
+ "duration": int(get_short_timestamp()) - int(CONTEXT.get("start_time")),
588
+ "link": os.environ.get("ALLURE_REPORT_URL")
589
+ }
590
+
591
+ )
592
+ except:
593
+ logger.error("报告同步失败")
594
+
563
595
 
564
596
 
565
597
  def pytest_exception_interact(node, call, report):
@@ -877,4 +909,4 @@ def sort(case_items):
877
909
 
878
910
  all_item_list += item_list
879
911
 
880
- return all_item_list
912
+ return all_item_list
@@ -52,10 +52,8 @@ class GlobalAttribute(object):
52
52
 
53
53
  def get(self, key, app=None):
54
54
  if app:
55
- obj = getattr(self, app, None)
56
- else:
57
- obj = self
58
- value = getattr(obj, key, None)
55
+ return self.get_by_chain(f"{app}.{key}")
56
+ value = getattr(self, key, None)
59
57
  return Box(value) if isinstance(value, dict) else self.list2box(value) if isinstance(value, list) else value
60
58
 
61
59
  def set(self, key, value, app=None):
@@ -66,6 +64,21 @@ class GlobalAttribute(object):
66
64
 
67
65
  setattr(self, key, value)
68
66
 
67
+ def get_by_chain(self, key_chain, default=None):
68
+ """
69
+ 链式格式的key进行get
70
+ :param key_chain: 链式key,例如 "app.module.key"
71
+ :param default: 找不到时的默认值
72
+ :return:
73
+ """
74
+ keys = key_chain.split(".")
75
+ obj = self
76
+ for key in keys:
77
+ obj = getattr(obj, key, None)
78
+ if obj is None:
79
+ return default
80
+ return Box(obj) if isinstance(obj, dict) else self.list2box(obj) if isinstance(obj, list) else obj
81
+
69
82
  def set_by_chain(self, key_chain, value):
70
83
  """
71
84
  链式格式的key进行set
@@ -18,7 +18,7 @@ from framework.utils.log_util import logger
18
18
  from framework.global_attribute import CONTEXT
19
19
  from config.settings import CONSOLE_DETAILED_LOG
20
20
  from framework.utils.common import convert_numbers_to_decimal
21
- from framework.exceptions import RequestException
21
+ from framework.exceptions import RequestException, ValidateException
22
22
 
23
23
 
24
24
  class ResponseUtil(object):
@@ -197,24 +197,26 @@ class HttpClient(object):
197
197
  try:
198
198
  data["request"]["headers"].update(kwargs.get("headers", {}))
199
199
  return self.__send_request(data)
200
+ except ValidateException as e:
201
+ raise ValidateException(e)
200
202
  except Exception as e:
201
203
  raise RequestException(e)
202
204
 
203
205
  return ResponseUtil(requests.request(headers=self.headers, **kwargs))
204
206
 
205
- def post(self, app, url, data=None, json=None, **kwargs):
207
+ def post(self, app=None,*, url, data=None, json=None, **kwargs):
206
208
  return self.request(method="post", url=urljoin(CONTEXT.get(app=app, key="domain"), url), data=data,
207
209
  json=json, **kwargs)
208
210
 
209
- def get(self, app, url, params=None, **kwargs):
211
+ def get(self, app=None,*, url, params=None, **kwargs):
210
212
  return self.request(method="get", url=urljoin(CONTEXT.get(app=app, key="domain"), url), params=params,
211
213
  **kwargs)
212
214
 
213
- def put(self, app, url, data=None, **kwargs):
215
+ def put(self, app=None,*, url, data=None, **kwargs):
214
216
  return self.request(method="put", url=urljoin(CONTEXT.get(app=app, key="domain"), url), data=data,
215
217
  **kwargs)
216
218
 
217
- def delete(self, app, url, **kwargs):
219
+ def delete(self, app=None,*, url, **kwargs):
218
220
  return self.request(method="delete", url=urljoin(CONTEXT.get(app=app, key="domain"), url), **kwargs)
219
221
 
220
222
  def update_headers(self, headers):
@@ -2,10 +2,11 @@ import decimal
2
2
  import re
3
3
  import os
4
4
  import time
5
+ import base64
5
6
  import binascii
6
7
  from typing import Any
7
8
  from decimal import Decimal
8
- from datetime import datetime
9
+ from datetime import datetime,timezone,timedelta
9
10
  from urllib.parse import unquote, quote, quote_plus, unquote_plus
10
11
 
11
12
  import pyotp
@@ -147,10 +148,16 @@ def valid_b64_format(s):
147
148
  :param s:
148
149
  :return:
149
150
  """
150
- b64_re = re.compile(r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")
151
- if b64_re.match(s):
151
+ """
152
+ 校验字符串是否为合法 base64
153
+ """
154
+ try:
155
+ if not isinstance(s, str):
156
+ return False
157
+ base64.b64decode(s, validate=True)
152
158
  return True
153
- return False
159
+ except (binascii.Error, ValueError):
160
+ return False
154
161
 
155
162
 
156
163
  def hex_to_bytes(hex_str):
@@ -261,3 +268,7 @@ def convert_numbers_to_decimal(obj: Any) -> Any:
261
268
  def remove_spaces(s: str) -> str:
262
269
  """去掉字符串中的所有空格"""
263
270
  return s.replace(" ", "")
271
+
272
+ def now_iso8601(tz_offset_hours: int = 8) -> str:
273
+ tz = timezone(timedelta(hours=tz_offset_hours))
274
+ return datetime.now(tz).isoformat(timespec='seconds')
@@ -1,29 +1,29 @@
1
- Metadata-Version: 2.4
2
- Name: pytest-api-framework-alpha
3
- Version: 0.3.23
4
- Author: alpha
5
- Author-email:
6
- Requires-Python: >=3.6
7
- Requires-Dist: allure-pytest==2.13.1
8
- Requires-Dist: allure-python-commons==2.13.1
9
- Requires-Dist: cn2an==0.5.19
10
- Requires-Dist: DBUtils==3.1.0
11
- Requires-Dist: Faker==18.3.2
12
- Requires-Dist: jsonpath==0.82
13
- Requires-Dist: pytest==7.2.2
14
- Requires-Dist: python-dotenv==1.0.1
15
- Requires-Dist: PyYAML==6.0.1
16
- Requires-Dist: python-box==7.2.0
17
- Requires-Dist: pycryptodome==3.21.0
18
- Requires-Dist: pyotp==2.9.0
19
- Requires-Dist: pytest-order==1.3.0
20
- Requires-Dist: PyMySQL==1.1.0
21
- Requires-Dist: redis==3.5.3
22
- Requires-Dist: requests==2.25.1
23
- Requires-Dist: requests-toolbelt==1.0.0
24
- Requires-Dist: retry==0.9.2
25
- Requires-Dist: dill==0.3.8
26
- Requires-Dist: simplejson==3.20.1
27
- Dynamic: author
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
1
+ Metadata-Version: 2.4
2
+ Name: pytest-api-framework-alpha
3
+ Version: 0.3.25
4
+ Author: alpha
5
+ Author-email:
6
+ Requires-Python: >=3.6
7
+ Requires-Dist: allure-pytest==2.13.1
8
+ Requires-Dist: allure-python-commons==2.13.1
9
+ Requires-Dist: cn2an==0.5.19
10
+ Requires-Dist: DBUtils==3.1.0
11
+ Requires-Dist: Faker==18.3.2
12
+ Requires-Dist: jsonpath==0.82
13
+ Requires-Dist: pytest==7.2.2
14
+ Requires-Dist: python-dotenv==1.0.1
15
+ Requires-Dist: PyYAML==6.0.1
16
+ Requires-Dist: python-box==7.2.0
17
+ Requires-Dist: pycryptodome==3.21.0
18
+ Requires-Dist: pyotp==2.9.0
19
+ Requires-Dist: pytest-order==1.3.0
20
+ Requires-Dist: PyMySQL==1.1.0
21
+ Requires-Dist: redis==3.5.3
22
+ Requires-Dist: requests==2.25.1
23
+ Requires-Dist: requests-toolbelt==1.0.0
24
+ Requires-Dist: retry==0.9.2
25
+ Requires-Dist: dill==0.3.8
26
+ Requires-Dist: simplejson==3.20.1
27
+ Dynamic: author
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pytest-api-framework-alpha", # 包名(必须唯一)
5
- version="0.3.23",
5
+ version="0.3.25",
6
6
  packages=find_packages(),
7
7
  author="alpha",
8
8
  author_email="",