qa-testing-utils 0.0.9__py3-none-any.whl → 0.0.10__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.
- qa_testing_utils/__init__.py +1 -1
- qa_testing_utils/logger.py +85 -63
- qa_testing_utils/object_utils.py +9 -0
- qa_testing_utils/thread_utils.py +14 -0
- {qa_testing_utils-0.0.9.dist-info → qa_testing_utils-0.0.10.dist-info}/METADATA +3 -3
- {qa_testing_utils-0.0.9.dist-info → qa_testing_utils-0.0.10.dist-info}/RECORD +8 -8
- {qa_testing_utils-0.0.9.dist-info → qa_testing_utils-0.0.10.dist-info}/WHEEL +0 -0
- {qa_testing_utils-0.0.9.dist-info → qa_testing_utils-0.0.10.dist-info}/entry_points.txt +0 -0
qa_testing_utils/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.0.
|
1
|
+
__version__ = '0.0.10'
|
qa_testing_utils/logger.py
CHANGED
@@ -2,14 +2,98 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
from dataclasses import dataclass
|
5
6
|
import inspect
|
6
7
|
import logging
|
7
8
|
from functools import cached_property, wraps
|
8
|
-
from typing import Callable, ParamSpec, TypeVar, cast, final
|
9
|
+
from typing import Callable, ClassVar, ParamSpec, TypeVar, cast, final
|
9
10
|
|
10
11
|
import allure
|
12
|
+
from qa_testing_utils.object_utils import classproperty
|
11
13
|
from qa_testing_utils.string_utils import EMPTY_STRING, LF
|
14
|
+
from qa_testing_utils.thread_utils import ThreadLocal
|
12
15
|
|
16
|
+
P = ParamSpec('P')
|
17
|
+
R = TypeVar('R')
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class Context:
|
21
|
+
"""Per-thread context for reporting and logging, allowing dynamic formatting of messages."""
|
22
|
+
_local: ClassVar[ThreadLocal['Context']]
|
23
|
+
_context_fn: Callable[[str], str]
|
24
|
+
|
25
|
+
@classproperty
|
26
|
+
def _apply(cls) -> Callable[[str], str]:
|
27
|
+
return Context._local.get()._context_fn
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def set(context_fn: Callable[[str], str]) -> None:
|
31
|
+
"""Sets per-thread context function to be used for formatting report and log messages."""
|
32
|
+
return Context._local.set(Context(context_fn))
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def traced(func: Callable[P, R]) -> Callable[P, R]:
|
36
|
+
"""
|
37
|
+
Decorator to log function entry, arguments, and return value at DEBUG level.
|
38
|
+
|
39
|
+
Also adds an Allure step for reporting. Use on methods where tracing is useful
|
40
|
+
for debugging or reporting.
|
41
|
+
|
42
|
+
Example:
|
43
|
+
@Context.traced
|
44
|
+
def my_method(self, x):
|
45
|
+
...
|
46
|
+
|
47
|
+
Args:
|
48
|
+
func (Callable[P, R]): The function to be decorated.
|
49
|
+
*args (Any): Positional arguments to be passed to the function.
|
50
|
+
**kwargs (Any): Keyword arguments to be passed to the function.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
Callable[P, R]: The result of the function call.
|
54
|
+
"""
|
55
|
+
@wraps(func)
|
56
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
57
|
+
# NOTE: each time a decorated function is called this logic will be
|
58
|
+
# re-evaluated.
|
59
|
+
signature = inspect.signature(func)
|
60
|
+
parameters = list(signature.parameters.keys())
|
61
|
+
|
62
|
+
if parameters and parameters[0] == 'self' and len(args) > 0:
|
63
|
+
instance = args[0]
|
64
|
+
logger = logging.getLogger(f"{instance.__class__.__name__}")
|
65
|
+
logger.debug(f">>> "
|
66
|
+
+ Context._apply(
|
67
|
+
f"{func.__name__} "
|
68
|
+
f"{", ".join([str(arg) for arg in args[1:]])} "
|
69
|
+
f"{LF.join(
|
70
|
+
f"{key}={str(value)}"
|
71
|
+
for key, value in kwargs.items()) if kwargs else EMPTY_STRING}"))
|
72
|
+
|
73
|
+
with allure.step( # type: ignore
|
74
|
+
Context._apply(
|
75
|
+
f"{func.__name__} "
|
76
|
+
f"{', '.join([str(arg) for arg in args[1:]])}")):
|
77
|
+
result = func(*args, **kwargs)
|
78
|
+
|
79
|
+
if result == instance:
|
80
|
+
logger.debug(f"<<< " + Context._apply(f"{func.__name__}"))
|
81
|
+
else:
|
82
|
+
logger.debug(f"<<< " + Context._apply(f"{func.__name__} {result}"))
|
83
|
+
|
84
|
+
return result
|
85
|
+
else:
|
86
|
+
logger = logging.getLogger(func.__name__)
|
87
|
+
logger.debug(f">>> {func.__name__} {args} {kwargs}")
|
88
|
+
result = func(*args, **kwargs)
|
89
|
+
logger.debug(f"<<< {func.__name__} {result}")
|
90
|
+
return result
|
91
|
+
|
92
|
+
return wrapper
|
93
|
+
|
94
|
+
|
95
|
+
# NOTE: python does not support static initializers, so we init here.
|
96
|
+
Context._local = ThreadLocal(Context(lambda _: _)) # type: ignore
|
13
97
|
|
14
98
|
def trace[T](value: T) -> T:
|
15
99
|
"""Logs at debug level using the invoking module name as the logger."""
|
@@ -91,65 +175,3 @@ class LoggerMixin:
|
|
91
175
|
"""
|
92
176
|
self.log.debug(f"=== {value}")
|
93
177
|
return value
|
94
|
-
|
95
|
-
|
96
|
-
P = ParamSpec('P')
|
97
|
-
R = TypeVar('R')
|
98
|
-
|
99
|
-
|
100
|
-
def traced(func: Callable[P, R]) -> Callable[P, R]:
|
101
|
-
"""
|
102
|
-
Decorator to log function entry, arguments, and return value at DEBUG level.
|
103
|
-
|
104
|
-
Also adds an Allure step for reporting. Use on methods where tracing is useful
|
105
|
-
for debugging or reporting.
|
106
|
-
|
107
|
-
Example:
|
108
|
-
@traced
|
109
|
-
def my_method(self, x):
|
110
|
-
...
|
111
|
-
|
112
|
-
Args:
|
113
|
-
func (Callable[P, R]): The function to be decorated.
|
114
|
-
*args (Any): Positional arguments to be passed to the function.
|
115
|
-
**kwargs (Any): Keyword arguments to be passed to the function.
|
116
|
-
|
117
|
-
Returns:
|
118
|
-
Callable[P, R]: The result of the function call.
|
119
|
-
"""
|
120
|
-
@wraps(func)
|
121
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
122
|
-
# NOTE: each time a decorated function is called this logic will be
|
123
|
-
# re-evaluated.
|
124
|
-
signature = inspect.signature(func)
|
125
|
-
parameters = list(signature.parameters.keys())
|
126
|
-
|
127
|
-
if parameters and parameters[0] == 'self' and len(args) > 0:
|
128
|
-
instance = args[0]
|
129
|
-
logger = logging.getLogger(f"{instance.__class__.__name__}")
|
130
|
-
logger.debug(
|
131
|
-
f">>> {func.__name__} "
|
132
|
-
f"{", ".join([str(arg) for arg in args[1:]])} "
|
133
|
-
f"{LF.join(
|
134
|
-
f"{key}={str(value)}"
|
135
|
-
for key, value in kwargs.items()) if kwargs else EMPTY_STRING}")
|
136
|
-
|
137
|
-
with allure.step( # type: ignore
|
138
|
-
f"{func.__name__} "
|
139
|
-
f"{', '.join([str(arg) for arg in args[1:]])}"):
|
140
|
-
result = func(*args, **kwargs)
|
141
|
-
|
142
|
-
if result == instance:
|
143
|
-
logger.debug(f"<<< {func.__name__}")
|
144
|
-
else:
|
145
|
-
logger.debug(f"<<< {func.__name__} {result}")
|
146
|
-
|
147
|
-
return result
|
148
|
-
else:
|
149
|
-
logger = logging.getLogger(func.__name__)
|
150
|
-
logger.debug(f">>> {func.__name__} {args} {kwargs}")
|
151
|
-
result = func(*args, **kwargs)
|
152
|
-
logger.debug(f"<<< {func.__name__} {result}")
|
153
|
-
return result
|
154
|
-
|
155
|
-
return wrapper
|
qa_testing_utils/object_utils.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
from typing import Callable, Any
|
5
6
|
import threading
|
6
7
|
from dataclasses import asdict, fields, is_dataclass, replace
|
7
8
|
from enum import Enum
|
@@ -236,3 +237,11 @@ def require_not_none[T](
|
|
236
237
|
if value is None:
|
237
238
|
raise ValueError(message)
|
238
239
|
return value
|
240
|
+
|
241
|
+
|
242
|
+
class classproperty[T]:
|
243
|
+
def __init__(self, fget: Callable[[Any], T]) -> None:
|
244
|
+
self.fget = fget
|
245
|
+
|
246
|
+
def __get__(self, instance: Any, owner: Any) -> T:
|
247
|
+
return self.fget(owner)
|
qa_testing_utils/thread_utils.py
CHANGED
@@ -3,8 +3,10 @@
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
5
|
import concurrent.futures
|
6
|
+
from threading import local
|
6
7
|
import time
|
7
8
|
from datetime import timedelta
|
9
|
+
from typing import cast
|
8
10
|
|
9
11
|
COMMON_EXECUTOR = concurrent.futures.ThreadPoolExecutor()
|
10
12
|
|
@@ -16,3 +18,15 @@ def sleep_for(duration: timedelta):
|
|
16
18
|
duration (timedelta): The amount of time to sleep.
|
17
19
|
"""
|
18
20
|
time.sleep(duration.total_seconds())
|
21
|
+
|
22
|
+
|
23
|
+
class ThreadLocal[T]:
|
24
|
+
def __init__(self, default: T):
|
25
|
+
self._local = local()
|
26
|
+
self._local.value = default
|
27
|
+
|
28
|
+
def set(self, value: T) -> None:
|
29
|
+
self._local.value = value
|
30
|
+
|
31
|
+
def get(self) -> T:
|
32
|
+
return cast(T, self._local.value)
|
@@ -1,15 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qa-testing-utils
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.10
|
4
4
|
Summary: QA testing utilities
|
5
5
|
Author-Email: Adrian Herscu <adrian.herscu@gmail.com>
|
6
6
|
License: Apache-2.0
|
7
7
|
Requires-Python: >=3.13
|
8
|
-
Requires-Dist: pytest==8.
|
8
|
+
Requires-Dist: pytest==8.4.0
|
9
9
|
Requires-Dist: PyHamcrest==2.1.0
|
10
10
|
Requires-Dist: pyfunctional==1.5.0
|
11
11
|
Requires-Dist: ppretty==1.3
|
12
|
-
Requires-Dist: allure-pytest==2.14.
|
12
|
+
Requires-Dist: allure-pytest==2.14.3
|
13
13
|
Requires-Dist: more-itertools==10.7.0
|
14
14
|
Requires-Dist: returns==0.25.0
|
15
15
|
Description-Content-Type: text/markdown
|
@@ -1,17 +1,17 @@
|
|
1
|
-
qa_testing_utils-0.0.
|
2
|
-
qa_testing_utils-0.0.
|
3
|
-
qa_testing_utils-0.0.
|
4
|
-
qa_testing_utils/__init__.py,sha256=
|
1
|
+
qa_testing_utils-0.0.10.dist-info/METADATA,sha256=mz_7SpeT4TUL3rGDpNvD9ZuT8RQsJiKLcz-R_4fvuLs,480
|
2
|
+
qa_testing_utils-0.0.10.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
qa_testing_utils-0.0.10.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
+
qa_testing_utils/__init__.py,sha256=r2Dgx1vmkvInceHz-V7SeB1ZwQnQHbcShDPVM9grMpw,22
|
5
5
|
qa_testing_utils/conftest_helpers.py,sha256=Hcpbc1CFNANFfVDyu9Elf5TB5tX4zSHzHSrRhlsHGsM,1408
|
6
6
|
qa_testing_utils/exception_utils.py,sha256=iPa-EE1gvKLxzEB3KzNMWGHaS7xEH_B3Yxd8KWiMROA,1340
|
7
7
|
qa_testing_utils/exceptions.py,sha256=_s7es20G9-ET2HeLqU0yhuDAXpnQQs_ecjBmztz94Pk,441
|
8
8
|
qa_testing_utils/file_utils.py,sha256=a6VPIbSZQVuOEnrprzlj-YTtfMskNzuClnhECRN2CPw,6444
|
9
|
-
qa_testing_utils/logger.py,sha256=
|
9
|
+
qa_testing_utils/logger.py,sha256=oOwYIKEXphFfjDW_vF_CTZhQOwr30UW4GAaFQtMS8V0,5822
|
10
10
|
qa_testing_utils/logging.ini,sha256=ZcCKCnUiRl3IVB0ZK9fe79PsAXEMag7QwRpxmO4yBHE,834
|
11
11
|
qa_testing_utils/matchers.py,sha256=WOPqtCPt5tFdn6JpyDDqPfPSHQvRMpv0uXCv6-2IqNE,12558
|
12
|
-
qa_testing_utils/object_utils.py,sha256=
|
12
|
+
qa_testing_utils/object_utils.py,sha256=xeh0bO1xoqo55dsx272_qTcKGx-DuirK0FWpdC5XmfY,7623
|
13
13
|
qa_testing_utils/stream_utils.py,sha256=zYa2UDfrWsXwRP6nrG87uGkkUZPBLATHXBgu7GUk3Aw,1536
|
14
14
|
qa_testing_utils/string_utils.py,sha256=tU1VfmzcS_Zqj4hqzdr6kWdM5dpdlRhiBNiwJAbBDVg,2617
|
15
|
-
qa_testing_utils/thread_utils.py,sha256=
|
15
|
+
qa_testing_utils/thread_utils.py,sha256=h9h0ocUV1aBkl6pAqgWtLrTH94db_umjBkXLrtok4xE,733
|
16
16
|
qa_testing_utils/tuple_utils.py,sha256=pIcJntr-PNvaOIP0Pv4sBwO7oIbTVFmGwr9Ic5nJDA0,1851
|
17
|
-
qa_testing_utils-0.0.
|
17
|
+
qa_testing_utils-0.0.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|