omlish 0.0.0.dev315__py3-none-any.whl → 0.0.0.dev317__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.
- omlish/__about__.py +2 -2
- omlish/collections/utils.py +99 -5
- omlish/formats/json/encoding.py +3 -0
- omlish/formats/json/literals.py +90 -4
- omlish/formats/json/rendering.py +4 -2
- omlish/formats/json5/rendering.py +44 -0
- omlish/http/coro/server.py +1 -2
- omlish/lang/__init__.py +7 -7
- omlish/lang/cached/function.py +66 -9
- omlish/lang/generators.py +3 -5
- omlish/lang/params.py +7 -9
- omlish/lite/maybes.py +163 -17
- omlish/logs/callers.py +3 -0
- omlish/logs/color.py +3 -0
- omlish/logs/filters.py +8 -1
- omlish/logs/handlers.py +3 -0
- omlish/logs/json.py +3 -0
- omlish/logs/noisy.py +3 -0
- omlish/logs/proxy.py +3 -0
- omlish/logs/utils.py +3 -0
- omlish/sockets/ports.py +24 -7
- omlish/testing/pytest/plugins/switches.py +103 -44
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/RECORD +28 -28
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/WHEEL +1 -1
- omlish/lang/maybes.py +0 -157
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev315.dist-info → omlish-0.0.0.dev317.dist-info}/top_level.txt +0 -0
omlish/lite/maybes.py
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
+
# ruff: noqa: UP007
|
1
2
|
import abc
|
2
3
|
import functools
|
3
4
|
import typing as ta
|
4
5
|
|
5
6
|
|
6
7
|
T = ta.TypeVar('T')
|
8
|
+
U = ta.TypeVar('U')
|
7
9
|
|
8
10
|
|
9
11
|
##
|
10
12
|
|
11
13
|
|
14
|
+
@functools.total_ordering
|
12
15
|
class Maybe(ta.Generic[T]):
|
16
|
+
class ValueNotPresentError(BaseException):
|
17
|
+
pass
|
18
|
+
|
19
|
+
#
|
20
|
+
|
13
21
|
@property
|
14
22
|
@abc.abstractmethod
|
15
23
|
def present(self) -> bool:
|
@@ -19,9 +27,102 @@ class Maybe(ta.Generic[T]):
|
|
19
27
|
def must(self) -> T:
|
20
28
|
raise NotImplementedError
|
21
29
|
|
30
|
+
#
|
31
|
+
|
32
|
+
@abc.abstractmethod
|
33
|
+
def __repr__(self) -> str:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def __hash__(self) -> int:
|
38
|
+
raise NotImplementedError
|
39
|
+
|
40
|
+
@abc.abstractmethod
|
41
|
+
def __eq__(self, other) -> bool:
|
42
|
+
raise NotImplementedError
|
43
|
+
|
44
|
+
@abc.abstractmethod
|
45
|
+
def __lt__(self, other) -> bool:
|
46
|
+
raise NotImplementedError
|
47
|
+
|
48
|
+
#
|
49
|
+
|
50
|
+
@ta.final
|
51
|
+
def __ne__(self, other):
|
52
|
+
return not (self == other)
|
53
|
+
|
54
|
+
@ta.final
|
55
|
+
def __iter__(self) -> ta.Iterator[T]:
|
56
|
+
if self.present:
|
57
|
+
yield self.must()
|
58
|
+
|
59
|
+
@ta.final
|
60
|
+
def __bool__(self) -> ta.NoReturn:
|
61
|
+
raise TypeError
|
62
|
+
|
63
|
+
#
|
64
|
+
|
65
|
+
@ta.final
|
66
|
+
def if_present(self, consumer: ta.Callable[[T], None]) -> None:
|
67
|
+
if self.present:
|
68
|
+
consumer(self.must())
|
69
|
+
|
70
|
+
@ta.final
|
71
|
+
def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
|
72
|
+
if self.present and predicate(self.must()):
|
73
|
+
return self
|
74
|
+
else:
|
75
|
+
return Maybe.empty()
|
76
|
+
|
77
|
+
@ta.final
|
78
|
+
def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
|
79
|
+
if self.present:
|
80
|
+
return Maybe.just(mapper(self.must()))
|
81
|
+
else:
|
82
|
+
return Maybe.empty()
|
83
|
+
|
84
|
+
@ta.final
|
85
|
+
def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
|
86
|
+
if self.present:
|
87
|
+
if not isinstance(v := mapper(self.must()), Maybe):
|
88
|
+
raise TypeError(v)
|
89
|
+
return v
|
90
|
+
else:
|
91
|
+
return Maybe.empty()
|
92
|
+
|
93
|
+
@ta.final
|
94
|
+
def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
|
95
|
+
if self.present:
|
96
|
+
return self.must()
|
97
|
+
else:
|
98
|
+
return other
|
99
|
+
|
100
|
+
@ta.final
|
101
|
+
def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
|
102
|
+
if self.present:
|
103
|
+
return self.must()
|
104
|
+
else:
|
105
|
+
return supplier()
|
106
|
+
|
107
|
+
@ta.final
|
108
|
+
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
|
109
|
+
if self.present:
|
110
|
+
return self.must()
|
111
|
+
else:
|
112
|
+
raise exception_supplier()
|
113
|
+
|
114
|
+
#
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
|
118
|
+
if v is not None:
|
119
|
+
return cls.just(v)
|
120
|
+
else:
|
121
|
+
return cls.empty()
|
122
|
+
|
22
123
|
@classmethod
|
23
124
|
def just(cls, v: T) -> 'Maybe[T]':
|
24
|
-
return
|
125
|
+
return _JustMaybe(v)
|
25
126
|
|
26
127
|
_empty: ta.ClassVar['Maybe']
|
27
128
|
|
@@ -30,33 +131,78 @@ class Maybe(ta.Generic[T]):
|
|
30
131
|
return Maybe._empty
|
31
132
|
|
32
133
|
|
33
|
-
|
34
|
-
|
134
|
+
##
|
135
|
+
|
136
|
+
|
137
|
+
class _Maybe(Maybe[T], abc.ABC):
|
138
|
+
def __lt__(self, other):
|
139
|
+
if not isinstance(other, _Maybe):
|
140
|
+
return NotImplemented
|
141
|
+
sp = self.present
|
142
|
+
op = other.present
|
143
|
+
if self.present and other.present:
|
144
|
+
return self.must() < other.must()
|
145
|
+
else:
|
146
|
+
return op and not sp
|
35
147
|
|
36
|
-
|
37
|
-
|
148
|
+
|
149
|
+
class _JustMaybe(_Maybe[T]):
|
150
|
+
__slots__ = ('_v', '_hash')
|
151
|
+
|
152
|
+
def __init__(self, v: T) -> None:
|
153
|
+
super().__init__()
|
154
|
+
|
155
|
+
self._v = v
|
38
156
|
|
39
157
|
@property
|
40
158
|
def present(self) -> bool:
|
41
|
-
return
|
159
|
+
return True
|
42
160
|
|
43
161
|
def must(self) -> T:
|
44
|
-
|
45
|
-
raise ValueError
|
46
|
-
return self[0]
|
162
|
+
return self._v
|
47
163
|
|
164
|
+
#
|
48
165
|
|
49
|
-
|
166
|
+
def __repr__(self) -> str:
|
167
|
+
return f'just({self._v!r})'
|
50
168
|
|
169
|
+
_hash: int
|
51
170
|
|
52
|
-
|
171
|
+
def __hash__(self) -> int:
|
172
|
+
try:
|
173
|
+
return self._hash
|
174
|
+
except AttributeError:
|
175
|
+
pass
|
176
|
+
h = self._hash = hash((_JustMaybe, self._v))
|
177
|
+
return h
|
178
|
+
|
179
|
+
def __eq__(self, other):
|
180
|
+
return (
|
181
|
+
self.__class__ is other.__class__ and
|
182
|
+
self._v == other._v # noqa
|
183
|
+
)
|
184
|
+
|
185
|
+
|
186
|
+
class _EmptyMaybe(_Maybe[T]):
|
187
|
+
__slots__ = ()
|
188
|
+
|
189
|
+
@property
|
190
|
+
def present(self) -> bool:
|
191
|
+
return False
|
192
|
+
|
193
|
+
def must(self) -> T:
|
194
|
+
raise Maybe.ValueNotPresentError
|
195
|
+
|
196
|
+
#
|
197
|
+
|
198
|
+
def __repr__(self) -> str:
|
199
|
+
return 'empty()'
|
53
200
|
|
201
|
+
def __hash__(self) -> int:
|
202
|
+
return hash(_EmptyMaybe)
|
54
203
|
|
55
|
-
|
56
|
-
|
57
|
-
raise TypeError(obj)
|
204
|
+
def __eq__(self, other):
|
205
|
+
return self.__class__ is other.__class__
|
58
206
|
|
59
207
|
|
60
|
-
|
61
|
-
def _(obj: Maybe) -> Maybe:
|
62
|
-
return obj
|
208
|
+
Maybe._empty = _EmptyMaybe() # noqa
|
omlish/logs/callers.py
CHANGED
omlish/logs/color.py
CHANGED
omlish/logs/filters.py
CHANGED
@@ -4,7 +4,14 @@ import logging
|
|
4
4
|
import threading
|
5
5
|
|
6
6
|
|
7
|
+
##
|
8
|
+
|
9
|
+
|
7
10
|
class TidLogFilter(logging.Filter):
|
8
11
|
def filter(self, record):
|
9
|
-
|
12
|
+
# FIXME: handle better - missing from wasm and cosmos
|
13
|
+
if hasattr(threading, 'get_native_id'):
|
14
|
+
record.tid = threading.get_native_id()
|
15
|
+
else:
|
16
|
+
record.tid = '?'
|
10
17
|
return True
|
omlish/logs/handlers.py
CHANGED
omlish/logs/json.py
CHANGED
omlish/logs/noisy.py
CHANGED
omlish/logs/proxy.py
CHANGED
omlish/logs/utils.py
CHANGED
omlish/sockets/ports.py
CHANGED
@@ -16,21 +16,38 @@ DEFAULT_AVAILABLE_PORT_HOST: str = '127.0.0.1'
|
|
16
16
|
|
17
17
|
|
18
18
|
@contextlib.contextmanager
|
19
|
-
def get_available_port_context(
|
19
|
+
def get_available_port_context(
|
20
|
+
host: ta.Optional[str] = None,
|
21
|
+
family: int = socket.AF_INET,
|
22
|
+
type: int = socket.SOCK_STREAM, # noqa
|
23
|
+
*,
|
24
|
+
listen: ta.Union[bool, int, None] = False,
|
25
|
+
) -> ta.Iterator[int]:
|
20
26
|
if host is None:
|
21
27
|
host = DEFAULT_AVAILABLE_PORT_HOST
|
22
28
|
|
23
|
-
|
29
|
+
if listen is not None:
|
30
|
+
if listen is True:
|
31
|
+
listen = 1
|
32
|
+
elif listen is False:
|
33
|
+
listen = None
|
34
|
+
else:
|
35
|
+
listen = check.isinstance(listen, int)
|
36
|
+
|
37
|
+
with socket.socket(family, type) as sock:
|
24
38
|
sock.bind((host, 0))
|
25
|
-
|
39
|
+
|
40
|
+
if listen is not None:
|
41
|
+
sock.listen(listen)
|
42
|
+
|
26
43
|
port = sock.getsockname()[1]
|
44
|
+
|
27
45
|
yield port
|
28
46
|
|
29
47
|
|
30
|
-
def get_available_port(
|
31
|
-
with get_available_port_context(
|
32
|
-
|
33
|
-
return port
|
48
|
+
def get_available_port(*args: ta.Any, **kwargs: ta.Any) -> int:
|
49
|
+
with get_available_port_context(*args, **kwargs) as port:
|
50
|
+
return port
|
34
51
|
|
35
52
|
|
36
53
|
def get_available_ports(
|
@@ -4,29 +4,87 @@ TODO:
|
|
4
4
|
- dynamic registration
|
5
5
|
- dynamic switching (skip docker if not running, skip online if not online, ...)
|
6
6
|
"""
|
7
|
+
import dataclasses as dc
|
7
8
|
import typing as ta
|
8
9
|
|
9
10
|
import pytest
|
10
11
|
|
11
12
|
from .... import check
|
12
13
|
from .... import collections as col
|
13
|
-
from .... import lang
|
14
14
|
from ....docker import all as docker
|
15
15
|
from ._registry import register
|
16
16
|
|
17
17
|
|
18
|
-
Configable = pytest.FixtureRequest | pytest.Config
|
18
|
+
Configable: ta.TypeAlias = pytest.FixtureRequest | pytest.Config
|
19
19
|
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
@dc.dataclass(frozen=True, eq=False)
|
25
|
+
class Switch:
|
26
|
+
name: str
|
27
|
+
_default_enabled: bool | ta.Callable[[], bool]
|
28
|
+
|
29
|
+
_: dc.KW_ONLY
|
30
|
+
|
31
|
+
add_marks: ta.Sequence[ta.Any] | None = None
|
32
|
+
|
33
|
+
def default_enabled(self) -> bool:
|
34
|
+
if isinstance(e := self._default_enabled, bool):
|
35
|
+
return e
|
36
|
+
elif callable(e):
|
37
|
+
return check.isinstance(e(), bool)
|
38
|
+
else:
|
39
|
+
raise TypeError(e)
|
40
|
+
|
41
|
+
@property
|
42
|
+
def attr(self) -> str:
|
43
|
+
return self.name.replace('-', '_')
|
44
|
+
|
45
|
+
|
46
|
+
SWITCHES: ta.Sequence[Switch] = [
|
47
|
+
Switch(
|
48
|
+
'name',
|
49
|
+
docker.has_cli,
|
50
|
+
),
|
51
|
+
|
52
|
+
Switch(
|
53
|
+
'docker-guest',
|
54
|
+
docker.is_likely_in_docker,
|
55
|
+
),
|
56
|
+
|
57
|
+
Switch(
|
58
|
+
'online',
|
59
|
+
True,
|
60
|
+
),
|
28
61
|
|
29
|
-
|
62
|
+
Switch(
|
63
|
+
'integration',
|
64
|
+
True,
|
65
|
+
),
|
66
|
+
|
67
|
+
Switch(
|
68
|
+
'high-mem',
|
69
|
+
True,
|
70
|
+
add_marks=[
|
71
|
+
# https://pytest-xdist.readthedocs.io/en/latest/distribution.html
|
72
|
+
pytest.mark.xdist_group('high-mem'),
|
73
|
+
],
|
74
|
+
),
|
75
|
+
|
76
|
+
Switch(
|
77
|
+
'slow',
|
78
|
+
False,
|
79
|
+
),
|
80
|
+
]
|
81
|
+
|
82
|
+
|
83
|
+
SWITCHES_BY_NAME: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.name, SWITCHES, strict=True)
|
84
|
+
SWITCHES_BY_ATTR: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.attr, SWITCHES, strict=True)
|
85
|
+
|
86
|
+
|
87
|
+
##
|
30
88
|
|
31
89
|
|
32
90
|
SwitchState: ta.TypeAlias = bool | ta.Literal['only']
|
@@ -49,7 +107,7 @@ def _get_obj_config(obj: Configable) -> pytest.Config:
|
|
49
107
|
|
50
108
|
def is_disabled(obj: Configable | None, name: str) -> bool:
|
51
109
|
check.isinstance(name, str)
|
52
|
-
check.in_(name,
|
110
|
+
check.in_(name, SWITCHES_BY_NAME)
|
53
111
|
return obj is not None and _get_obj_config(obj).getoption(f'--no-{name}')
|
54
112
|
|
55
113
|
|
@@ -58,17 +116,17 @@ def skip_if_disabled(obj: Configable | None, name: str) -> None:
|
|
58
116
|
pytest.skip(f'{name} disabled')
|
59
117
|
|
60
118
|
|
61
|
-
def get_specified_switches(obj: Configable) ->
|
62
|
-
ret: dict[
|
119
|
+
def get_specified_switches(obj: Configable) -> dict[Switch, SwitchState]:
|
120
|
+
ret: dict[Switch, SwitchState] = {}
|
63
121
|
for sw in SWITCHES:
|
64
122
|
sts = {
|
65
123
|
st
|
66
124
|
for st, pfx in SWITCH_STATE_OPT_PREFIXES.items()
|
67
|
-
if _get_obj_config(obj).getoption(pfx + sw)
|
125
|
+
if _get_obj_config(obj).getoption(pfx + sw.name)
|
68
126
|
}
|
69
127
|
if sts:
|
70
128
|
if len(sts) > 1:
|
71
|
-
raise Exception(f'Multiple switches specified for {sw}')
|
129
|
+
raise Exception(f'Multiple switches specified for {sw.name}')
|
72
130
|
ret[sw] = check.single(sts)
|
73
131
|
return ret
|
74
132
|
|
@@ -76,49 +134,50 @@ def get_specified_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
|
|
76
134
|
@register
|
77
135
|
class SwitchesPlugin:
|
78
136
|
def pytest_configure(self, config):
|
79
|
-
for sw in
|
80
|
-
config.addinivalue_line('markers', f'{sw}: mark test as {sw}')
|
81
|
-
config.addinivalue_line('markers', f'not_{sw}: mark test as not {sw}')
|
137
|
+
for sw in SWITCHES:
|
138
|
+
config.addinivalue_line('markers', f'{sw.attr}: mark test as {sw.attr}')
|
139
|
+
config.addinivalue_line('markers', f'not_{sw.attr}: mark test as not {sw.attr}')
|
82
140
|
|
83
141
|
def pytest_addoption(self, parser):
|
84
142
|
for sw in SWITCHES:
|
85
|
-
parser.addoption(f'--no-{sw}', action='store_true', default=False, help=f'disable {sw} tests')
|
86
|
-
parser.addoption(f'--{sw}', action='store_true', default=False, help=f'enables {sw} tests')
|
87
|
-
parser.addoption(f'--only-{sw}', action='store_true', default=False, help=f'enables only {sw} tests')
|
88
|
-
|
89
|
-
@lang.cached_function
|
90
|
-
def get_switches(self) -> ta.Mapping[str, SwitchState]:
|
91
|
-
return {
|
92
|
-
k: v() if callable(v) else v
|
93
|
-
for k, v in SWITCHES.items()
|
94
|
-
}
|
143
|
+
parser.addoption(f'--no-{sw.name}', action='store_true', default=False, help=f'disable {sw.name} tests')
|
144
|
+
parser.addoption(f'--{sw.name}', action='store_true', default=False, help=f'enables {sw.name} tests')
|
145
|
+
parser.addoption(f'--only-{sw.name}', action='store_true', default=False, help=f'enables only {sw.name} tests') # noqa
|
95
146
|
|
96
147
|
def pytest_collection_modifyitems(self, config, items):
|
97
|
-
|
98
|
-
**
|
148
|
+
switch_states: dict[Switch, SwitchState] = {
|
149
|
+
**{
|
150
|
+
sw: sw.default_enabled()
|
151
|
+
for sw in SWITCHES
|
152
|
+
},
|
99
153
|
**get_specified_switches(config),
|
100
154
|
}
|
101
155
|
|
102
|
-
|
103
|
-
|
156
|
+
inv_switch_states: dict[SwitchState, list[Switch]] = col.multi_map((st, sw) for sw, st in switch_states.items())
|
157
|
+
true_switches = frozenset(inv_switch_states.get(True, ()))
|
158
|
+
false_switches = frozenset(inv_switch_states.get(False, ()))
|
159
|
+
only_switches = frozenset(inv_switch_states.get('only', ()))
|
104
160
|
|
105
161
|
def process(item):
|
106
|
-
|
107
|
-
|
162
|
+
item_switches = {sw for sw in SWITCHES if sw.attr in item.keywords}
|
163
|
+
item_not_switches = {sw for sw in SWITCHES if ('not_' + sw.attr) in item.keywords}
|
164
|
+
|
165
|
+
for sw in item_switches:
|
166
|
+
for mk in sw.add_marks or []:
|
167
|
+
item.add_marker(mk)
|
108
168
|
|
109
|
-
if
|
110
|
-
if not any(sw in
|
111
|
-
item.add_marker(pytest.mark.skip(reason=f'skipping switches {
|
169
|
+
if only_switches:
|
170
|
+
if not any(sw in only_switches for sw in item_switches):
|
171
|
+
item.add_marker(pytest.mark.skip(reason=f'skipping switches {item_switches}'))
|
112
172
|
return
|
113
173
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
|
174
|
+
for sw in item_switches:
|
175
|
+
if sw in false_switches:
|
176
|
+
item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
|
118
177
|
|
119
|
-
|
120
|
-
|
121
|
-
|
178
|
+
for sw in item_not_switches:
|
179
|
+
if sw in true_switches:
|
180
|
+
item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
|
122
181
|
|
123
182
|
for item in items:
|
124
183
|
process(item)
|