qa-testing-utils 0.0.8__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.
@@ -1 +1 @@
1
- __version__ = '0.0.8'
1
+ __version__ = '0.0.10'
@@ -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
@@ -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)
@@ -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.8
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.3.5
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.2
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.8.dist-info/METADATA,sha256=Fb_o3Wqn223rmxw0BGZbUhft6KElTGYe93juDctw-14,479
2
- qa_testing_utils-0.0.8.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- qa_testing_utils-0.0.8.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- qa_testing_utils/__init__.py,sha256=QaKrrCeLmUWRaUA8pjWo9GWzxTk9cCTRKQXEuQX5viQ,21
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=840RhoO5AHewqEBNa6A2WbWb8DF-AFrasMPDSPH6JNM,4625
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=CNbRVB3RAmjeGDRQe9cDsSF_iuZY6vENvZj2pwB_mS0,7393
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=3Ecyg-bnkcPyT_r9xVWS79uv1BTgXxeIJJZhT3tXorM,416
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.8.dist-info/RECORD,,
17
+ qa_testing_utils-0.0.10.dist-info/RECORD,,