haiway 0.1.0__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.
- haiway/__init__.py +75 -0
- haiway/context/__init__.py +14 -0
- haiway/context/access.py +416 -0
- haiway/context/dependencies.py +61 -0
- haiway/context/metrics.py +329 -0
- haiway/context/state.py +115 -0
- haiway/context/tasks.py +65 -0
- haiway/context/types.py +17 -0
- haiway/helpers/__init__.py +13 -0
- haiway/helpers/asynchronous.py +226 -0
- haiway/helpers/cache.py +326 -0
- haiway/helpers/retry.py +210 -0
- haiway/helpers/throttling.py +133 -0
- haiway/helpers/timeout.py +112 -0
- haiway/py.typed +0 -0
- haiway/state/__init__.py +8 -0
- haiway/state/attributes.py +360 -0
- haiway/state/structure.py +254 -0
- haiway/state/validation.py +125 -0
- haiway/types/__init__.py +11 -0
- haiway/types/frozen.py +5 -0
- haiway/types/missing.py +91 -0
- haiway/utils/__init__.py +23 -0
- haiway/utils/always.py +61 -0
- haiway/utils/env.py +164 -0
- haiway/utils/immutable.py +28 -0
- haiway/utils/logs.py +57 -0
- haiway/utils/mimic.py +77 -0
- haiway/utils/noop.py +24 -0
- haiway/utils/queue.py +89 -0
- haiway-0.1.0.dist-info/LICENSE +21 -0
- haiway-0.1.0.dist-info/METADATA +86 -0
- haiway-0.1.0.dist-info/RECORD +35 -0
- haiway-0.1.0.dist-info/WHEEL +5 -0
- haiway-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
import types
|
2
|
+
import typing
|
3
|
+
from collections.abc import Callable, Sequence
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from haiway import types as _types
|
7
|
+
from haiway.state.attributes import AttributeAnnotation
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"attribute_type_validator",
|
11
|
+
]
|
12
|
+
|
13
|
+
|
14
|
+
def attribute_type_validator(
|
15
|
+
annotation: AttributeAnnotation,
|
16
|
+
/,
|
17
|
+
) -> Callable[[Any], Any]:
|
18
|
+
match annotation.origin:
|
19
|
+
case types.NoneType:
|
20
|
+
return _none_validator
|
21
|
+
|
22
|
+
case _types.Missing:
|
23
|
+
return _missing_validator
|
24
|
+
|
25
|
+
case types.UnionType:
|
26
|
+
return _prepare_union_validator(annotation.arguments)
|
27
|
+
|
28
|
+
case typing.Literal:
|
29
|
+
return _prepare_literal_validator(annotation.arguments)
|
30
|
+
|
31
|
+
case typing.Any:
|
32
|
+
return _any_validator
|
33
|
+
|
34
|
+
case type() as other_type:
|
35
|
+
return _prepare_type_validator(other_type)
|
36
|
+
|
37
|
+
case other:
|
38
|
+
raise TypeError(f"Unsupported type annotation: {other}")
|
39
|
+
|
40
|
+
|
41
|
+
def _none_validator(
|
42
|
+
value: Any,
|
43
|
+
) -> Any:
|
44
|
+
match value:
|
45
|
+
case None:
|
46
|
+
return None
|
47
|
+
|
48
|
+
case _:
|
49
|
+
raise TypeError(f"Type '{type(value)}' is not matching expected type 'None'")
|
50
|
+
|
51
|
+
|
52
|
+
def _missing_validator(
|
53
|
+
value: Any,
|
54
|
+
) -> Any:
|
55
|
+
match value:
|
56
|
+
case _types.Missing():
|
57
|
+
return _types.MISSING
|
58
|
+
|
59
|
+
case _:
|
60
|
+
raise TypeError(f"Type '{type(value)}' is not matching expected type 'Missing'")
|
61
|
+
|
62
|
+
|
63
|
+
def _any_validator(
|
64
|
+
value: Any,
|
65
|
+
) -> Any:
|
66
|
+
return value # any is always valid
|
67
|
+
|
68
|
+
|
69
|
+
def _prepare_union_validator(
|
70
|
+
elements: Sequence[AttributeAnnotation],
|
71
|
+
/,
|
72
|
+
) -> Callable[[Any], Any]:
|
73
|
+
validators: list[Callable[[Any], Any]] = [
|
74
|
+
attribute_type_validator(alternative) for alternative in elements
|
75
|
+
]
|
76
|
+
|
77
|
+
def union_validator(
|
78
|
+
value: Any,
|
79
|
+
) -> Any:
|
80
|
+
errors: list[Exception] = []
|
81
|
+
for validator in validators:
|
82
|
+
try:
|
83
|
+
return validator(value)
|
84
|
+
|
85
|
+
except Exception as exc:
|
86
|
+
errors.append(exc)
|
87
|
+
|
88
|
+
raise ExceptionGroup("Multiple errors", errors)
|
89
|
+
|
90
|
+
return union_validator
|
91
|
+
|
92
|
+
|
93
|
+
def _prepare_literal_validator(
|
94
|
+
elements: Sequence[Any],
|
95
|
+
/,
|
96
|
+
) -> Callable[[Any], Any]:
|
97
|
+
def literal_validator(
|
98
|
+
value: Any,
|
99
|
+
) -> Any:
|
100
|
+
if value in elements:
|
101
|
+
return value
|
102
|
+
|
103
|
+
else:
|
104
|
+
raise ValueError(f"Value '{value}' is not matching expected '{elements}'")
|
105
|
+
|
106
|
+
return literal_validator
|
107
|
+
|
108
|
+
|
109
|
+
def _prepare_type_validator(
|
110
|
+
validated_type: type[Any],
|
111
|
+
/,
|
112
|
+
) -> Callable[[Any], Any]:
|
113
|
+
def type_validator(
|
114
|
+
value: Any,
|
115
|
+
) -> Any:
|
116
|
+
match value:
|
117
|
+
case value if isinstance(value, validated_type):
|
118
|
+
return value
|
119
|
+
|
120
|
+
case _:
|
121
|
+
raise TypeError(
|
122
|
+
f"Type '{type(value)}' is not matching expected type '{validated_type}'"
|
123
|
+
)
|
124
|
+
|
125
|
+
return type_validator
|
haiway/types/__init__.py
ADDED
haiway/types/frozen.py
ADDED
haiway/types/missing.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
from typing import Any, Final, TypeGuard, cast, final
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"MISSING",
|
5
|
+
"Missing",
|
6
|
+
"is_missing",
|
7
|
+
"not_missing",
|
8
|
+
"when_missing",
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
class MissingType(type):
|
13
|
+
_instance: Any = None
|
14
|
+
|
15
|
+
def __call__(cls) -> Any:
|
16
|
+
if cls._instance is None:
|
17
|
+
cls._instance = super().__call__()
|
18
|
+
return cls._instance
|
19
|
+
|
20
|
+
else:
|
21
|
+
return cls._instance
|
22
|
+
|
23
|
+
|
24
|
+
@final
|
25
|
+
class Missing(metaclass=MissingType):
|
26
|
+
"""
|
27
|
+
Type representing absence of a value. Use MISSING constant for its value.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __bool__(self) -> bool:
|
31
|
+
return False
|
32
|
+
|
33
|
+
def __eq__(
|
34
|
+
self,
|
35
|
+
value: object,
|
36
|
+
) -> bool:
|
37
|
+
return value is MISSING
|
38
|
+
|
39
|
+
def __str__(self) -> str:
|
40
|
+
return "MISSING"
|
41
|
+
|
42
|
+
def __repr__(self) -> str:
|
43
|
+
return "MISSING"
|
44
|
+
|
45
|
+
def __getattribute__(
|
46
|
+
self,
|
47
|
+
name: str,
|
48
|
+
) -> Any:
|
49
|
+
raise RuntimeError("Missing has no attributes")
|
50
|
+
|
51
|
+
def __setattr__(
|
52
|
+
self,
|
53
|
+
__name: str,
|
54
|
+
__value: Any,
|
55
|
+
) -> None:
|
56
|
+
raise RuntimeError("Missing can't be modified")
|
57
|
+
|
58
|
+
def __delattr__(
|
59
|
+
self,
|
60
|
+
__name: str,
|
61
|
+
) -> None:
|
62
|
+
raise RuntimeError("Missing can't be modified")
|
63
|
+
|
64
|
+
|
65
|
+
MISSING: Final[Missing] = Missing()
|
66
|
+
|
67
|
+
|
68
|
+
def is_missing(
|
69
|
+
check: Any | Missing,
|
70
|
+
/,
|
71
|
+
) -> TypeGuard[Missing]:
|
72
|
+
return check is MISSING
|
73
|
+
|
74
|
+
|
75
|
+
def not_missing[Value](
|
76
|
+
check: Value | Missing,
|
77
|
+
/,
|
78
|
+
) -> TypeGuard[Value]:
|
79
|
+
return check is not MISSING
|
80
|
+
|
81
|
+
|
82
|
+
def when_missing[Value](
|
83
|
+
check: Value | Missing,
|
84
|
+
/,
|
85
|
+
value: Value,
|
86
|
+
) -> Value:
|
87
|
+
if check is MISSING:
|
88
|
+
return value
|
89
|
+
|
90
|
+
else:
|
91
|
+
return cast(Value, check)
|
haiway/utils/__init__.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
from haiway.utils.always import always, async_always
|
2
|
+
from haiway.utils.env import getenv_bool, getenv_float, getenv_int, getenv_str, load_env
|
3
|
+
from haiway.utils.immutable import freeze
|
4
|
+
from haiway.utils.logs import setup_logging
|
5
|
+
from haiway.utils.mimic import mimic_function
|
6
|
+
from haiway.utils.noop import async_noop, noop
|
7
|
+
from haiway.utils.queue import AsyncQueue
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"always",
|
11
|
+
"async_always",
|
12
|
+
"async_noop",
|
13
|
+
"AsyncQueue",
|
14
|
+
"freeze",
|
15
|
+
"getenv_bool",
|
16
|
+
"getenv_float",
|
17
|
+
"getenv_int",
|
18
|
+
"getenv_str",
|
19
|
+
"load_env",
|
20
|
+
"mimic_function",
|
21
|
+
"noop",
|
22
|
+
"setup_logging",
|
23
|
+
]
|
haiway/utils/always.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
from collections.abc import Callable, Coroutine
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"always",
|
6
|
+
"async_always",
|
7
|
+
]
|
8
|
+
|
9
|
+
|
10
|
+
def always[Value](
|
11
|
+
value: Value,
|
12
|
+
/,
|
13
|
+
) -> Callable[..., Value]:
|
14
|
+
"""
|
15
|
+
Factory method creating functions returning always the same value.
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
value: Value
|
20
|
+
value to be always returned from prepared function
|
21
|
+
|
22
|
+
Returns
|
23
|
+
-------
|
24
|
+
Callable[..., Value]
|
25
|
+
function ignoring arguments and always returning the provided value.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def always_value(
|
29
|
+
*args: Any,
|
30
|
+
**kwargs: Any,
|
31
|
+
) -> Value:
|
32
|
+
return value
|
33
|
+
|
34
|
+
return always_value
|
35
|
+
|
36
|
+
|
37
|
+
def async_always[Value](
|
38
|
+
value: Value,
|
39
|
+
/,
|
40
|
+
) -> Callable[..., Coroutine[None, None, Value]]:
|
41
|
+
"""
|
42
|
+
Factory method creating async functions returning always the same value.
|
43
|
+
|
44
|
+
Parameters
|
45
|
+
----------
|
46
|
+
value: Value
|
47
|
+
value to be always returned from prepared function
|
48
|
+
|
49
|
+
Returns
|
50
|
+
-------
|
51
|
+
Callable[..., Coroutine[None, None, Value]]
|
52
|
+
async function ignoring arguments and always returning the provided value.
|
53
|
+
"""
|
54
|
+
|
55
|
+
async def always_value(
|
56
|
+
*args: Any,
|
57
|
+
**kwargs: Any,
|
58
|
+
) -> Value:
|
59
|
+
return value
|
60
|
+
|
61
|
+
return always_value
|
haiway/utils/env.py
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
from os import environ, getenv
|
2
|
+
from typing import overload
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"getenv_bool",
|
6
|
+
"getenv_int",
|
7
|
+
"getenv_float",
|
8
|
+
"getenv_str",
|
9
|
+
"load_env",
|
10
|
+
]
|
11
|
+
|
12
|
+
|
13
|
+
@overload
|
14
|
+
def getenv_bool(
|
15
|
+
key: str,
|
16
|
+
/,
|
17
|
+
) -> bool | None: ...
|
18
|
+
|
19
|
+
|
20
|
+
@overload
|
21
|
+
def getenv_bool(
|
22
|
+
key: str,
|
23
|
+
/,
|
24
|
+
default: bool,
|
25
|
+
) -> bool: ...
|
26
|
+
|
27
|
+
|
28
|
+
def getenv_bool(
|
29
|
+
key: str,
|
30
|
+
/,
|
31
|
+
default: bool | None = None,
|
32
|
+
) -> bool | None:
|
33
|
+
if value := getenv(key=key):
|
34
|
+
return value.lower() in ("true", "1", "t")
|
35
|
+
else:
|
36
|
+
return default
|
37
|
+
|
38
|
+
|
39
|
+
@overload
|
40
|
+
def getenv_int(
|
41
|
+
key: str,
|
42
|
+
/,
|
43
|
+
) -> int | None: ...
|
44
|
+
|
45
|
+
|
46
|
+
@overload
|
47
|
+
def getenv_int(
|
48
|
+
key: str,
|
49
|
+
/,
|
50
|
+
default: int,
|
51
|
+
) -> int: ...
|
52
|
+
|
53
|
+
|
54
|
+
def getenv_int(
|
55
|
+
key: str,
|
56
|
+
/,
|
57
|
+
default: int | None = None,
|
58
|
+
) -> int | None:
|
59
|
+
if value := getenv(key=key):
|
60
|
+
return int(value)
|
61
|
+
|
62
|
+
else:
|
63
|
+
return default
|
64
|
+
|
65
|
+
|
66
|
+
@overload
|
67
|
+
def getenv_float(
|
68
|
+
key: str,
|
69
|
+
/,
|
70
|
+
) -> float | None: ...
|
71
|
+
|
72
|
+
|
73
|
+
@overload
|
74
|
+
def getenv_float(
|
75
|
+
key: str,
|
76
|
+
/,
|
77
|
+
default: float,
|
78
|
+
) -> float: ...
|
79
|
+
|
80
|
+
|
81
|
+
def getenv_float(
|
82
|
+
key: str,
|
83
|
+
/,
|
84
|
+
default: float | None = None,
|
85
|
+
) -> float | None:
|
86
|
+
if value := getenv(key=key):
|
87
|
+
return float(value)
|
88
|
+
|
89
|
+
else:
|
90
|
+
return default
|
91
|
+
|
92
|
+
|
93
|
+
@overload
|
94
|
+
def getenv_str(
|
95
|
+
key: str,
|
96
|
+
/,
|
97
|
+
) -> str | None: ...
|
98
|
+
|
99
|
+
|
100
|
+
@overload
|
101
|
+
def getenv_str(
|
102
|
+
key: str,
|
103
|
+
/,
|
104
|
+
default: str,
|
105
|
+
) -> str: ...
|
106
|
+
|
107
|
+
|
108
|
+
def getenv_str(
|
109
|
+
key: str,
|
110
|
+
/,
|
111
|
+
default: str | None = None,
|
112
|
+
) -> str | None:
|
113
|
+
if value := getenv(key=key):
|
114
|
+
return value
|
115
|
+
else:
|
116
|
+
return default
|
117
|
+
|
118
|
+
|
119
|
+
def load_env(
|
120
|
+
path: str | None = None,
|
121
|
+
override: bool = True,
|
122
|
+
) -> None:
|
123
|
+
"""\
|
124
|
+
Minimalist implementation of environment variables file loader. \
|
125
|
+
When the file is not available configuration won't be loaded.
|
126
|
+
Allows only subset of formatting:
|
127
|
+
- lines starting with '#' are ignored
|
128
|
+
- other comments are not allowed
|
129
|
+
- each element is in a new line
|
130
|
+
- each element must be a `key=value` pair without whitespaces or additional characters
|
131
|
+
- keys without values are ignored
|
132
|
+
|
133
|
+
Parameters
|
134
|
+
----------
|
135
|
+
path: str
|
136
|
+
custom path to load environment variables, default is '.env'
|
137
|
+
override: bool
|
138
|
+
override existing variables on conflict if True, otherwise keep existing
|
139
|
+
"""
|
140
|
+
|
141
|
+
try:
|
142
|
+
with open(file=path or ".env") as file:
|
143
|
+
for line in file.readlines():
|
144
|
+
if line.startswith("#"):
|
145
|
+
continue # ignore commented
|
146
|
+
|
147
|
+
idx: int # find where key ends
|
148
|
+
for element in enumerate(line):
|
149
|
+
if element[1] == "=":
|
150
|
+
idx: int = element[0]
|
151
|
+
break
|
152
|
+
else: # ignore keys without assignment
|
153
|
+
continue
|
154
|
+
|
155
|
+
if idx >= len(line):
|
156
|
+
continue # ignore keys without values
|
157
|
+
|
158
|
+
key: str = line[0:idx]
|
159
|
+
value: str = line[idx + 1 :].strip()
|
160
|
+
if value and (override or key not in environ):
|
161
|
+
environ[key] = value
|
162
|
+
|
163
|
+
except FileNotFoundError:
|
164
|
+
pass # ignore loading if no .env available
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"freeze",
|
5
|
+
]
|
6
|
+
|
7
|
+
|
8
|
+
def freeze(
|
9
|
+
instance: object,
|
10
|
+
/,
|
11
|
+
) -> None:
|
12
|
+
"""
|
13
|
+
Freeze object instance by replacing __delattr__ and __setattr__ to raising Exceptions.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def frozen_set(
|
17
|
+
__name: str,
|
18
|
+
__value: Any,
|
19
|
+
) -> None:
|
20
|
+
raise RuntimeError(f"{instance.__class__.__qualname__} is frozen and can't be modified")
|
21
|
+
|
22
|
+
def frozen_del(
|
23
|
+
__name: str,
|
24
|
+
) -> None:
|
25
|
+
raise RuntimeError(f"{instance.__class__.__qualname__} is frozen and can't be modified")
|
26
|
+
|
27
|
+
instance.__delattr__ = frozen_del
|
28
|
+
instance.__setattr__ = frozen_set
|
haiway/utils/logs.py
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
from logging.config import dictConfig
|
2
|
+
|
3
|
+
from haiway.utils.env import getenv_bool
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"setup_logging",
|
7
|
+
]
|
8
|
+
|
9
|
+
|
10
|
+
def setup_logging(
|
11
|
+
*loggers: str,
|
12
|
+
debug: bool = getenv_bool("DEBUG_LOGGING", __debug__),
|
13
|
+
) -> None:
|
14
|
+
"""\
|
15
|
+
Setup logging configuration and prepare specified loggers.
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
*loggers: str
|
20
|
+
names of additional loggers to configure.
|
21
|
+
|
22
|
+
NOTE: this function should be run only once on application start
|
23
|
+
"""
|
24
|
+
|
25
|
+
dictConfig(
|
26
|
+
config={
|
27
|
+
"version": 1,
|
28
|
+
"disable_existing_loggers": True,
|
29
|
+
"formatters": {
|
30
|
+
"standard": {
|
31
|
+
"format": "%(asctime)s [%(levelname)-4s] [%(name)s] %(message)s",
|
32
|
+
"datefmt": "%d/%b/%Y:%H:%M:%S +0000",
|
33
|
+
},
|
34
|
+
},
|
35
|
+
"handlers": {
|
36
|
+
"console": {
|
37
|
+
"level": "DEBUG" if debug else "INFO",
|
38
|
+
"formatter": "standard",
|
39
|
+
"class": "logging.StreamHandler",
|
40
|
+
"stream": "ext://sys.stdout",
|
41
|
+
},
|
42
|
+
},
|
43
|
+
"loggers": {
|
44
|
+
name: {
|
45
|
+
"handlers": ["console"],
|
46
|
+
"level": "DEBUG" if debug else "INFO",
|
47
|
+
"propagate": False,
|
48
|
+
}
|
49
|
+
for name in loggers
|
50
|
+
},
|
51
|
+
"root": { # root logger
|
52
|
+
"handlers": ["console"],
|
53
|
+
"level": "DEBUG" if debug else "INFO",
|
54
|
+
"propagate": False,
|
55
|
+
},
|
56
|
+
},
|
57
|
+
)
|
haiway/utils/mimic.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Any, cast, overload
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"mimic_function",
|
6
|
+
]
|
7
|
+
|
8
|
+
|
9
|
+
@overload
|
10
|
+
def mimic_function[**Args, Result](
|
11
|
+
function: Callable[Args, Result],
|
12
|
+
/,
|
13
|
+
within: Callable[..., Any],
|
14
|
+
) -> Callable[Args, Result]: ...
|
15
|
+
|
16
|
+
|
17
|
+
@overload
|
18
|
+
def mimic_function[**Args, Result](
|
19
|
+
function: Callable[Args, Result],
|
20
|
+
/,
|
21
|
+
) -> Callable[[Callable[..., Any]], Callable[Args, Result]]: ...
|
22
|
+
|
23
|
+
|
24
|
+
def mimic_function[**Args, Result](
|
25
|
+
function: Callable[Args, Result],
|
26
|
+
/,
|
27
|
+
within: Callable[..., Result] | None = None,
|
28
|
+
) -> Callable[[Callable[..., Result]], Callable[Args, Result]] | Callable[Args, Result]:
|
29
|
+
def mimic(
|
30
|
+
target: Callable[..., Result],
|
31
|
+
) -> Callable[Args, Result]:
|
32
|
+
# mimic function attributes if able
|
33
|
+
for attribute in (
|
34
|
+
"__module__",
|
35
|
+
"__name__",
|
36
|
+
"__qualname__",
|
37
|
+
"__doc__",
|
38
|
+
"__annotations__",
|
39
|
+
"__type_params__",
|
40
|
+
"__defaults__",
|
41
|
+
"__kwdefaults__",
|
42
|
+
"__globals__",
|
43
|
+
):
|
44
|
+
try:
|
45
|
+
setattr(
|
46
|
+
target,
|
47
|
+
attribute,
|
48
|
+
getattr(
|
49
|
+
function,
|
50
|
+
attribute,
|
51
|
+
),
|
52
|
+
)
|
53
|
+
|
54
|
+
except AttributeError:
|
55
|
+
pass
|
56
|
+
try:
|
57
|
+
target.__dict__.update(function.__dict__)
|
58
|
+
|
59
|
+
except AttributeError:
|
60
|
+
pass
|
61
|
+
|
62
|
+
setattr( # noqa: B010 - mimic functools.wraps behavior for correct signature checks
|
63
|
+
target,
|
64
|
+
"__wrapped__",
|
65
|
+
function,
|
66
|
+
)
|
67
|
+
|
68
|
+
return cast(
|
69
|
+
Callable[Args, Result],
|
70
|
+
target,
|
71
|
+
)
|
72
|
+
|
73
|
+
if target := within:
|
74
|
+
return mimic(target)
|
75
|
+
|
76
|
+
else:
|
77
|
+
return mimic
|
haiway/utils/noop.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"async_noop",
|
5
|
+
"noop",
|
6
|
+
]
|
7
|
+
|
8
|
+
|
9
|
+
def noop(
|
10
|
+
*args: Any,
|
11
|
+
**kwargs: Any,
|
12
|
+
) -> None:
|
13
|
+
"""
|
14
|
+
Placeholder function doing nothing (no operation).
|
15
|
+
"""
|
16
|
+
|
17
|
+
|
18
|
+
async def async_noop(
|
19
|
+
*args: Any,
|
20
|
+
**kwargs: Any,
|
21
|
+
) -> None:
|
22
|
+
"""
|
23
|
+
Placeholder async function doing nothing (no operation).
|
24
|
+
"""
|