qa-testing-utils 0.0.8__tar.gz → 0.0.10__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.
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/PKG-INFO +3 -3
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/pyproject.toml +3 -3
- qa_testing_utils-0.0.10/src/qa_testing_utils/__init__.py +1 -0
- qa_testing_utils-0.0.10/src/qa_testing_utils/logger.py +177 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/object_utils.py +9 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/thread_utils.py +14 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/logger_tests.py +2 -2
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/self_tests.py +2 -2
- qa_testing_utils-0.0.8/src/qa_testing_utils/__init__.py +0 -1
- qa_testing_utils-0.0.8/src/qa_testing_utils/logger.py +0 -155
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/README.md +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/conftest_helpers.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/exception_utils.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/exceptions.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/file_utils.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/logging.ini +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/matchers.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/stream_utils.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/string_utils.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/src/qa_testing_utils/tuple_utils.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/__init__.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/assertion_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/exception_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/file_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/matchers_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/object_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/stream_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/string_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/thread_utils_tests.py +0 -0
- {qa_testing_utils-0.0.8 → qa_testing_utils-0.0.10}/tests/tuple_utils_tests.py +0 -0
@@ -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
|
@@ -23,15 +23,15 @@ authors = [
|
|
23
23
|
readme = "README.md"
|
24
24
|
requires-python = ">=3.13"
|
25
25
|
dependencies = [
|
26
|
-
"pytest==8.
|
26
|
+
"pytest==8.4.0",
|
27
27
|
"PyHamcrest==2.1.0",
|
28
28
|
"pyfunctional==1.5.0",
|
29
29
|
"ppretty==1.3",
|
30
|
-
"allure-pytest==2.14.
|
30
|
+
"allure-pytest==2.14.3",
|
31
31
|
"more-itertools==10.7.0",
|
32
32
|
"returns==0.25.0",
|
33
33
|
]
|
34
|
-
version = "0.0.
|
34
|
+
version = "0.0.10"
|
35
35
|
|
36
36
|
[project.license]
|
37
37
|
text = "Apache-2.0"
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = '0.0.10'
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Adrian Herscu
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
import inspect
|
7
|
+
import logging
|
8
|
+
from functools import cached_property, wraps
|
9
|
+
from typing import Callable, ClassVar, ParamSpec, TypeVar, cast, final
|
10
|
+
|
11
|
+
import allure
|
12
|
+
from qa_testing_utils.object_utils import classproperty
|
13
|
+
from qa_testing_utils.string_utils import EMPTY_STRING, LF
|
14
|
+
from qa_testing_utils.thread_utils import ThreadLocal
|
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
|
97
|
+
|
98
|
+
def trace[T](value: T) -> T:
|
99
|
+
"""Logs at debug level using the invoking module name as the logger."""
|
100
|
+
frame = inspect.currentframe()
|
101
|
+
try:
|
102
|
+
if frame is not None:
|
103
|
+
caller_frame = frame.f_back
|
104
|
+
if caller_frame is not None:
|
105
|
+
caller_module = inspect.getmodule(caller_frame)
|
106
|
+
logger_name = caller_module.__name__ if caller_module else '__main__'
|
107
|
+
logger = logging.getLogger(logger_name)
|
108
|
+
logger.debug(f"=== {value}")
|
109
|
+
else:
|
110
|
+
logging.getLogger(__name__).debug(f"=== {value}")
|
111
|
+
else:
|
112
|
+
logging.getLogger(__name__).debug(f"=== {value}")
|
113
|
+
finally:
|
114
|
+
del frame
|
115
|
+
|
116
|
+
return value
|
117
|
+
|
118
|
+
|
119
|
+
def logger[T:type](cls: T) -> T:
|
120
|
+
"""
|
121
|
+
Class decorator that injects a logger into annotated class.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
cls (type): automatically provided by the runtime
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
_type_: the decorated class
|
128
|
+
"""
|
129
|
+
cls._logger = logging.getLogger(cls.__name__)
|
130
|
+
|
131
|
+
@property
|
132
|
+
def log(self: T) -> logging.Logger:
|
133
|
+
return cast(logging.Logger, getattr(self, '_logger', None))
|
134
|
+
|
135
|
+
cls.log = log
|
136
|
+
|
137
|
+
return cls
|
138
|
+
|
139
|
+
|
140
|
+
class LoggerMixin:
|
141
|
+
"""
|
142
|
+
Mixin that provides a `log` property for convenient class-based logging.
|
143
|
+
|
144
|
+
Inherit from this mixin to get a `self.log` logger named after the class.
|
145
|
+
Useful for adding debug/info/error logging to any class without boilerplate.
|
146
|
+
|
147
|
+
Example:
|
148
|
+
class MyClass(LoggerMixin):
|
149
|
+
def do_something(self):
|
150
|
+
self.log.info("Doing something")
|
151
|
+
"""
|
152
|
+
@final
|
153
|
+
@cached_property
|
154
|
+
def log(self) -> logging.Logger:
|
155
|
+
return logging.getLogger(self.__class__.__name__)
|
156
|
+
|
157
|
+
@final
|
158
|
+
def trace[T](self, value: T) -> T:
|
159
|
+
"""
|
160
|
+
Logs value at DEBUG level using this logger.
|
161
|
+
|
162
|
+
Use to log something as a value, usually in a lambda expression::
|
163
|
+
|
164
|
+
then.eventually_assert_that(
|
165
|
+
lambda: self.trace(...call some API...),
|
166
|
+
greater_that(0)) \
|
167
|
+
|
168
|
+
.and_....other verifications may follow...
|
169
|
+
|
170
|
+
Args:
|
171
|
+
value (T): the value
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
T: the value
|
175
|
+
"""
|
176
|
+
self.log.debug(f"=== {value}")
|
177
|
+
return value
|
@@ -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)
|
@@ -17,7 +17,7 @@ def should_trace():
|
|
17
17
|
|
18
18
|
@to_string()
|
19
19
|
class Foo(LoggerMixin):
|
20
|
-
@traced
|
20
|
+
@Context.traced
|
21
21
|
def run(self, message: Message) -> Self:
|
22
22
|
self.log.debug(f"{message}")
|
23
23
|
return self
|
@@ -85,7 +85,7 @@ def should_return_value_and_log_with_logger_mixin_trace():
|
|
85
85
|
def should_log_entry_and_exit_with_traced_decorator():
|
86
86
|
calls: list[tuple[int, int]] = []
|
87
87
|
|
88
|
-
@traced
|
88
|
+
@Context.traced
|
89
89
|
def foo(x: int, y: int) -> int:
|
90
90
|
calls.append((x, y))
|
91
91
|
return x + y
|
@@ -10,7 +10,7 @@ import attr
|
|
10
10
|
from hamcrest import assert_that, is_ # type: ignore
|
11
11
|
import pytest
|
12
12
|
from tenacity import before_sleep_log, retry, retry_if_exception_type, stop_after_attempt, wait_fixed
|
13
|
-
from qa_testing_utils.logger import
|
13
|
+
from qa_testing_utils.logger import Context
|
14
14
|
from qa_testing_utils.logger import *
|
15
15
|
from qa_testing_utils.exceptions import *
|
16
16
|
from qa_testing_utils.thread_utils import *
|
@@ -28,7 +28,7 @@ class SelfTests(LoggerMixin):
|
|
28
28
|
"""Test that print statement works (placeholder/self-test)."""
|
29
29
|
print("hello")
|
30
30
|
|
31
|
-
@traced
|
31
|
+
@Context.traced
|
32
32
|
def should_assert_true(self):
|
33
33
|
"""Test that a traced assertion passes (decorator coverage)."""
|
34
34
|
assert True
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = '0.0.8'
|
@@ -1,155 +0,0 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Adrian Herscu
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
import inspect
|
6
|
-
import logging
|
7
|
-
from functools import cached_property, wraps
|
8
|
-
from typing import Callable, ParamSpec, TypeVar, cast, final
|
9
|
-
|
10
|
-
import allure
|
11
|
-
from qa_testing_utils.string_utils import EMPTY_STRING, LF
|
12
|
-
|
13
|
-
|
14
|
-
def trace[T](value: T) -> T:
|
15
|
-
"""Logs at debug level using the invoking module name as the logger."""
|
16
|
-
frame = inspect.currentframe()
|
17
|
-
try:
|
18
|
-
if frame is not None:
|
19
|
-
caller_frame = frame.f_back
|
20
|
-
if caller_frame is not None:
|
21
|
-
caller_module = inspect.getmodule(caller_frame)
|
22
|
-
logger_name = caller_module.__name__ if caller_module else '__main__'
|
23
|
-
logger = logging.getLogger(logger_name)
|
24
|
-
logger.debug(f"=== {value}")
|
25
|
-
else:
|
26
|
-
logging.getLogger(__name__).debug(f"=== {value}")
|
27
|
-
else:
|
28
|
-
logging.getLogger(__name__).debug(f"=== {value}")
|
29
|
-
finally:
|
30
|
-
del frame
|
31
|
-
|
32
|
-
return value
|
33
|
-
|
34
|
-
|
35
|
-
def logger[T:type](cls: T) -> T:
|
36
|
-
"""
|
37
|
-
Class decorator that injects a logger into annotated class.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
cls (type): automatically provided by the runtime
|
41
|
-
|
42
|
-
Returns:
|
43
|
-
_type_: the decorated class
|
44
|
-
"""
|
45
|
-
cls._logger = logging.getLogger(cls.__name__)
|
46
|
-
|
47
|
-
@property
|
48
|
-
def log(self: T) -> logging.Logger:
|
49
|
-
return cast(logging.Logger, getattr(self, '_logger', None))
|
50
|
-
|
51
|
-
cls.log = log
|
52
|
-
|
53
|
-
return cls
|
54
|
-
|
55
|
-
|
56
|
-
class LoggerMixin:
|
57
|
-
"""
|
58
|
-
Mixin that provides a `log` property for convenient class-based logging.
|
59
|
-
|
60
|
-
Inherit from this mixin to get a `self.log` logger named after the class.
|
61
|
-
Useful for adding debug/info/error logging to any class without boilerplate.
|
62
|
-
|
63
|
-
Example:
|
64
|
-
class MyClass(LoggerMixin):
|
65
|
-
def do_something(self):
|
66
|
-
self.log.info("Doing something")
|
67
|
-
"""
|
68
|
-
@final
|
69
|
-
@cached_property
|
70
|
-
def log(self) -> logging.Logger:
|
71
|
-
return logging.getLogger(self.__class__.__name__)
|
72
|
-
|
73
|
-
@final
|
74
|
-
def trace[T](self, value: T) -> T:
|
75
|
-
"""
|
76
|
-
Logs value at DEBUG level using this logger.
|
77
|
-
|
78
|
-
Use to log something as a value, usually in a lambda expression::
|
79
|
-
|
80
|
-
then.eventually_assert_that(
|
81
|
-
lambda: self.trace(...call some API...),
|
82
|
-
greater_that(0)) \
|
83
|
-
|
84
|
-
.and_....other verifications may follow...
|
85
|
-
|
86
|
-
Args:
|
87
|
-
value (T): the value
|
88
|
-
|
89
|
-
Returns:
|
90
|
-
T: the value
|
91
|
-
"""
|
92
|
-
self.log.debug(f"=== {value}")
|
93
|
-
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|