omlish 0.0.0.dev18__py3-none-any.whl → 0.0.0.dev20__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 +3 -3
- omlish/asyncs/anyio.py +13 -6
- omlish/bootstrap/__init__.py +3 -0
- omlish/bootstrap/__main__.py +4 -0
- omlish/bootstrap/base.py +38 -0
- omlish/bootstrap/diag.py +128 -0
- omlish/bootstrap/harness.py +81 -0
- omlish/bootstrap/main.py +183 -0
- omlish/bootstrap/sys.py +361 -0
- omlish/dataclasses/__init__.py +2 -0
- omlish/dataclasses/impl/metadata.py +2 -1
- omlish/dataclasses/utils.py +45 -0
- omlish/defs.py +29 -5
- omlish/fnpairs.py +1 -1
- omlish/formats/json.py +1 -0
- omlish/lang/descriptors.py +2 -0
- omlish/lite/logs.py +2 -2
- omlish/logs/configs.py +11 -23
- omlish/logs/noisy.py +15 -0
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/dataclasses.py +16 -3
- omlish/marshal/helpers.py +22 -0
- omlish/marshal/objects.py +33 -14
- omlish/multiprocessing.py +32 -0
- omlish/specs/__init__.py +0 -0
- omlish/specs/jsonschema/__init__.py +0 -0
- omlish/specs/jsonschema/keywords/__init__.py +42 -0
- omlish/specs/jsonschema/keywords/base.py +86 -0
- omlish/specs/jsonschema/keywords/core.py +26 -0
- omlish/specs/jsonschema/keywords/metadata.py +22 -0
- omlish/specs/jsonschema/keywords/parse.py +69 -0
- omlish/specs/jsonschema/keywords/render.py +47 -0
- omlish/specs/jsonschema/keywords/validation.py +68 -0
- omlish/specs/jsonschema/schemas/__init__.py +0 -0
- omlish/specs/jsonschema/schemas/draft202012/__init__.py +0 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/__init__.py +0 -0
- omlish/specs/jsonschema/types.py +10 -0
- omlish/testing/pytest/plugins/__init__.py +8 -3
- omlish/testing/pytest/plugins/depskip.py +81 -0
- omlish/testing/pytest/plugins/utils.py +14 -0
- {omlish-0.0.0.dev18.dist-info → omlish-0.0.0.dev20.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev18.dist-info → omlish-0.0.0.dev20.dist-info}/RECORD +45 -22
- omlish/bootstrap.py +0 -746
- {omlish-0.0.0.dev18.dist-info → omlish-0.0.0.dev20.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev18.dist-info → omlish-0.0.0.dev20.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev18.dist-info → omlish-0.0.0.dev20.dist-info}/top_level.txt +0 -0
omlish/bootstrap/sys.py
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import contextlib
|
3
|
+
import dataclasses as dc
|
4
|
+
import enum
|
5
|
+
import faulthandler
|
6
|
+
import gc
|
7
|
+
import importlib
|
8
|
+
import logging
|
9
|
+
import os
|
10
|
+
import pwd
|
11
|
+
import resource
|
12
|
+
import signal
|
13
|
+
import sys
|
14
|
+
import typing as ta
|
15
|
+
|
16
|
+
from .. import lang
|
17
|
+
from .base import Bootstrap
|
18
|
+
from .base import ContextBootstrap
|
19
|
+
from .base import SimpleBootstrap
|
20
|
+
|
21
|
+
|
22
|
+
if ta.TYPE_CHECKING:
|
23
|
+
from .. import libc
|
24
|
+
from .. import logs
|
25
|
+
from .. import os as osu
|
26
|
+
from ..formats import dotenv
|
27
|
+
|
28
|
+
else:
|
29
|
+
libc = lang.proxy_import('..libc', __package__)
|
30
|
+
logs = lang.proxy_import('..logs', __package__)
|
31
|
+
osu = lang.proxy_import('..os', __package__)
|
32
|
+
dotenv = lang.proxy_import('.formats.dotenv', __package__)
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
class CwdBootstrap(ContextBootstrap['CwdBootstrap.Config']):
|
39
|
+
@dc.dataclass(frozen=True)
|
40
|
+
class Config(Bootstrap.Config):
|
41
|
+
path: ta.Optional[str] = None
|
42
|
+
|
43
|
+
@contextlib.contextmanager
|
44
|
+
def enter(self) -> ta.Iterator[None]:
|
45
|
+
if self._config.path is not None:
|
46
|
+
prev = os.getcwd()
|
47
|
+
os.chdir(self._config.path)
|
48
|
+
else:
|
49
|
+
prev = None
|
50
|
+
|
51
|
+
try:
|
52
|
+
yield
|
53
|
+
|
54
|
+
finally:
|
55
|
+
if prev is not None:
|
56
|
+
os.chdir(prev)
|
57
|
+
|
58
|
+
|
59
|
+
##
|
60
|
+
|
61
|
+
|
62
|
+
class SetuidBootstrap(SimpleBootstrap['SetuidBootstrap.Config']):
|
63
|
+
@dc.dataclass(frozen=True)
|
64
|
+
class Config(Bootstrap.Config):
|
65
|
+
user: ta.Optional[str] = None
|
66
|
+
|
67
|
+
def run(self) -> None:
|
68
|
+
if self._config.user is not None:
|
69
|
+
user = pwd.getpwnam(self._config.user)
|
70
|
+
os.setuid(user.pw_uid)
|
71
|
+
|
72
|
+
|
73
|
+
##
|
74
|
+
|
75
|
+
|
76
|
+
class GcDebugFlag(enum.Enum):
|
77
|
+
STATS = gc.DEBUG_STATS
|
78
|
+
COLLECTABLE = gc.DEBUG_COLLECTABLE
|
79
|
+
UNCOLLECTABLE = gc.DEBUG_UNCOLLECTABLE
|
80
|
+
SAVEALL = gc.DEBUG_SAVEALL
|
81
|
+
LEAK = gc.DEBUG_LEAK
|
82
|
+
|
83
|
+
|
84
|
+
class GcBootstrap(ContextBootstrap['GcBootstrap.Config']):
|
85
|
+
@dc.dataclass(frozen=True)
|
86
|
+
class Config(Bootstrap.Config):
|
87
|
+
disable: bool = False
|
88
|
+
debug: ta.Optional[int] = None
|
89
|
+
|
90
|
+
@contextlib.contextmanager
|
91
|
+
def enter(self) -> ta.Iterator[None]:
|
92
|
+
prev_enabled = gc.isenabled()
|
93
|
+
if self._config.disable:
|
94
|
+
gc.disable()
|
95
|
+
|
96
|
+
if self._config.debug is not None:
|
97
|
+
prev_debug = gc.get_debug()
|
98
|
+
gc.set_debug(self._config.debug)
|
99
|
+
else:
|
100
|
+
prev_debug = None
|
101
|
+
|
102
|
+
try:
|
103
|
+
yield
|
104
|
+
|
105
|
+
finally:
|
106
|
+
if prev_enabled:
|
107
|
+
gc.enable()
|
108
|
+
|
109
|
+
if prev_debug is not None:
|
110
|
+
gc.set_debug(prev_debug)
|
111
|
+
|
112
|
+
|
113
|
+
##
|
114
|
+
|
115
|
+
|
116
|
+
class NiceBootstrap(SimpleBootstrap['NiceBootstrap.Config']):
|
117
|
+
@dc.dataclass(frozen=True)
|
118
|
+
class Config(Bootstrap.Config):
|
119
|
+
nice: ta.Optional[int] = None
|
120
|
+
|
121
|
+
def run(self) -> None:
|
122
|
+
if self._config.nice is not None:
|
123
|
+
os.nice(self._config.nice)
|
124
|
+
|
125
|
+
|
126
|
+
##
|
127
|
+
|
128
|
+
|
129
|
+
class LogBootstrap(ContextBootstrap['LogBootstrap.Config']):
|
130
|
+
@dc.dataclass(frozen=True)
|
131
|
+
class Config(Bootstrap.Config):
|
132
|
+
level: ta.Union[str, int, None] = None
|
133
|
+
json: bool = False
|
134
|
+
|
135
|
+
@contextlib.contextmanager
|
136
|
+
def enter(self) -> ta.Iterator[None]:
|
137
|
+
if self._config.level is None:
|
138
|
+
yield
|
139
|
+
return
|
140
|
+
|
141
|
+
handler = logs.configure_standard_logging(
|
142
|
+
self._config.level,
|
143
|
+
json=self._config.json,
|
144
|
+
)
|
145
|
+
|
146
|
+
try:
|
147
|
+
yield
|
148
|
+
|
149
|
+
finally:
|
150
|
+
if handler is not None:
|
151
|
+
logging.root.removeHandler(handler)
|
152
|
+
|
153
|
+
|
154
|
+
##
|
155
|
+
|
156
|
+
|
157
|
+
class FaulthandlerBootstrap(ContextBootstrap['FaulthandlerBootstrap.Config']):
|
158
|
+
@dc.dataclass(frozen=True)
|
159
|
+
class Config(Bootstrap.Config):
|
160
|
+
enabled: ta.Optional[bool] = None
|
161
|
+
|
162
|
+
@contextlib.contextmanager
|
163
|
+
def enter(self) -> ta.Iterator[None]:
|
164
|
+
if self._config.enabled is None:
|
165
|
+
yield
|
166
|
+
return
|
167
|
+
|
168
|
+
prev = faulthandler.is_enabled()
|
169
|
+
if self._config.enabled:
|
170
|
+
faulthandler.enable()
|
171
|
+
else:
|
172
|
+
faulthandler.disable()
|
173
|
+
|
174
|
+
try:
|
175
|
+
yield
|
176
|
+
|
177
|
+
finally:
|
178
|
+
if prev:
|
179
|
+
faulthandler.enable()
|
180
|
+
else:
|
181
|
+
faulthandler.disable()
|
182
|
+
|
183
|
+
|
184
|
+
##
|
185
|
+
|
186
|
+
|
187
|
+
SIGNALS_BY_NAME = {
|
188
|
+
a[len('SIG'):]: v # noqa
|
189
|
+
for a in dir(signal)
|
190
|
+
if a.startswith('SIG')
|
191
|
+
and not a.startswith('SIG_')
|
192
|
+
and a == a.upper()
|
193
|
+
and isinstance((v := getattr(signal, a)), int)
|
194
|
+
}
|
195
|
+
|
196
|
+
|
197
|
+
class PrctlBootstrap(SimpleBootstrap['PrctlBootstrap.Config']):
|
198
|
+
@dc.dataclass(frozen=True)
|
199
|
+
class Config(Bootstrap.Config):
|
200
|
+
dumpable: bool = False
|
201
|
+
deathsig: ta.Union[int, str, None] = None
|
202
|
+
|
203
|
+
def run(self) -> None:
|
204
|
+
if self._config.dumpable:
|
205
|
+
libc.prctl(libc.PR_SET_DUMPABLE, 1, 0, 0, 0, 0)
|
206
|
+
|
207
|
+
if self._config.deathsig is not None:
|
208
|
+
if isinstance(self._config.deathsig, int):
|
209
|
+
sig = self._config.deathsig
|
210
|
+
else:
|
211
|
+
sig = SIGNALS_BY_NAME[self._config.deathsig.upper()]
|
212
|
+
libc.prctl(libc.PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
|
213
|
+
|
214
|
+
|
215
|
+
##
|
216
|
+
|
217
|
+
|
218
|
+
RLIMITS_BY_NAME = {
|
219
|
+
a[len('RLIMIT_'):]: v # noqa
|
220
|
+
for a in dir(resource)
|
221
|
+
if a.startswith('RLIMIT_')
|
222
|
+
and a == a.upper()
|
223
|
+
and isinstance((v := getattr(resource, a)), int)
|
224
|
+
}
|
225
|
+
|
226
|
+
|
227
|
+
class RlimitBootstrap(ContextBootstrap['RlimitBootstrap.Config']):
|
228
|
+
@dc.dataclass(frozen=True)
|
229
|
+
class Config(Bootstrap.Config):
|
230
|
+
limits: ta.Optional[ta.Mapping[str, ta.Tuple[ta.Optional[int], ta.Optional[int]]]] = None
|
231
|
+
|
232
|
+
@contextlib.contextmanager
|
233
|
+
def enter(self) -> ta.Iterator[None]:
|
234
|
+
if not self._config.limits:
|
235
|
+
yield
|
236
|
+
return
|
237
|
+
|
238
|
+
def or_infin(l: ta.Optional[int]) -> int:
|
239
|
+
return l if l is not None else resource.RLIM_INFINITY
|
240
|
+
|
241
|
+
prev = {}
|
242
|
+
for k, (s, h) in self._config.limits.items():
|
243
|
+
i = RLIMITS_BY_NAME[k.upper()]
|
244
|
+
prev[i] = resource.getrlimit(i)
|
245
|
+
resource.setrlimit(i, (or_infin(s), or_infin(h)))
|
246
|
+
|
247
|
+
try:
|
248
|
+
yield
|
249
|
+
|
250
|
+
finally:
|
251
|
+
for i, (s, h) in prev.items():
|
252
|
+
resource.setrlimit(i, (s, h))
|
253
|
+
|
254
|
+
|
255
|
+
##
|
256
|
+
|
257
|
+
|
258
|
+
class ImportBootstrap(SimpleBootstrap['ImportBootstrap.Config']):
|
259
|
+
@dc.dataclass(frozen=True)
|
260
|
+
class Config(Bootstrap.Config):
|
261
|
+
modules: ta.Optional[ta.Sequence[str]] = None
|
262
|
+
|
263
|
+
def run(self) -> None:
|
264
|
+
for m in self._config.modules or ():
|
265
|
+
importlib.import_module(m)
|
266
|
+
|
267
|
+
|
268
|
+
##
|
269
|
+
|
270
|
+
|
271
|
+
class EnvBootstrap(ContextBootstrap['EnvBootstrap.Config']):
|
272
|
+
@dc.dataclass(frozen=True)
|
273
|
+
class Config(Bootstrap.Config):
|
274
|
+
vars: ta.Optional[ta.Mapping[str, ta.Optional[str]]] = None
|
275
|
+
files: ta.Optional[ta.Sequence[str]] = None
|
276
|
+
|
277
|
+
@contextlib.contextmanager
|
278
|
+
def enter(self) -> ta.Iterator[None]:
|
279
|
+
if not (self._config.vars or self._config.files):
|
280
|
+
yield
|
281
|
+
return
|
282
|
+
|
283
|
+
new = dict(self._config.vars or {})
|
284
|
+
for f in self._config.files or ():
|
285
|
+
new.update(dotenv.dotenv_values(f, env=os.environ))
|
286
|
+
|
287
|
+
prev: ta.Dict[str, ta.Optional[str]] = {k: os.environ.get(k) for k in new}
|
288
|
+
|
289
|
+
def do(k: str, v: ta.Optional[str]) -> None:
|
290
|
+
if v is not None:
|
291
|
+
os.environ[k] = v
|
292
|
+
else:
|
293
|
+
del os.environ[k]
|
294
|
+
|
295
|
+
for k, v in new.items():
|
296
|
+
do(k, v)
|
297
|
+
|
298
|
+
try:
|
299
|
+
yield
|
300
|
+
|
301
|
+
finally:
|
302
|
+
for k, v in prev.items():
|
303
|
+
do(k, v)
|
304
|
+
|
305
|
+
|
306
|
+
##
|
307
|
+
|
308
|
+
|
309
|
+
class PidfileBootstrap(ContextBootstrap['PidfileBootstrap.Config']):
|
310
|
+
@dc.dataclass(frozen=True)
|
311
|
+
class Config(Bootstrap.Config):
|
312
|
+
path: ta.Optional[str] = None
|
313
|
+
|
314
|
+
@contextlib.contextmanager
|
315
|
+
def enter(self) -> ta.Iterator[None]:
|
316
|
+
if self._config.path is None:
|
317
|
+
yield
|
318
|
+
return
|
319
|
+
|
320
|
+
with osu.Pidfile(self._config.path) as pf:
|
321
|
+
pf.write()
|
322
|
+
yield
|
323
|
+
|
324
|
+
|
325
|
+
##
|
326
|
+
|
327
|
+
|
328
|
+
class FdsBootstrap(SimpleBootstrap['FdsBootstrap.Config']):
|
329
|
+
@dc.dataclass(frozen=True)
|
330
|
+
class Config(Bootstrap.Config):
|
331
|
+
redirects: ta.Optional[ta.Mapping[int, ta.Union[int, str, None]]] = None
|
332
|
+
|
333
|
+
def run(self) -> None:
|
334
|
+
for dst, src in (self._config.redirects or {}).items():
|
335
|
+
if src is None:
|
336
|
+
src = '/dev/null'
|
337
|
+
if isinstance(src, int):
|
338
|
+
os.dup2(src, dst)
|
339
|
+
elif isinstance(src, str):
|
340
|
+
sfd = os.open(src, os.O_RDWR)
|
341
|
+
os.dup2(sfd, dst)
|
342
|
+
os.close(sfd)
|
343
|
+
else:
|
344
|
+
raise TypeError(src)
|
345
|
+
|
346
|
+
|
347
|
+
##
|
348
|
+
|
349
|
+
|
350
|
+
class PrintPidBootstrap(SimpleBootstrap['PrintPidBootstrap.Config']):
|
351
|
+
@dc.dataclass(frozen=True)
|
352
|
+
class Config(Bootstrap.Config):
|
353
|
+
enable: bool = False
|
354
|
+
pause: bool = False
|
355
|
+
|
356
|
+
def run(self) -> None:
|
357
|
+
if not (self._config.enable or self._config.pause):
|
358
|
+
return
|
359
|
+
print(str(os.getpid()), file=sys.stderr)
|
360
|
+
if self._config.pause:
|
361
|
+
input()
|
omlish/dataclasses/__init__.py
CHANGED
omlish/dataclasses/utils.py
CHANGED
@@ -12,6 +12,9 @@ from .impl.params import get_field_extras
|
|
12
12
|
T = ta.TypeVar('T')
|
13
13
|
|
14
14
|
|
15
|
+
#
|
16
|
+
|
17
|
+
|
15
18
|
def maybe_post_init(sup: ta.Any) -> bool:
|
16
19
|
try:
|
17
20
|
fn = sup.__post_init__
|
@@ -21,10 +24,16 @@ def maybe_post_init(sup: ta.Any) -> bool:
|
|
21
24
|
return True
|
22
25
|
|
23
26
|
|
27
|
+
#
|
28
|
+
|
29
|
+
|
24
30
|
def opt_repr(o: ta.Any) -> str | None:
|
25
31
|
return repr(o) if o is not None else None
|
26
32
|
|
27
33
|
|
34
|
+
#
|
35
|
+
|
36
|
+
|
28
37
|
class field_modifier: # noqa
|
29
38
|
def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
|
30
39
|
super().__init__()
|
@@ -58,6 +67,42 @@ def update_field_extras(f: dc.Field, *, unless_non_default: bool = False, **kwar
|
|
58
67
|
})
|
59
68
|
|
60
69
|
|
70
|
+
def update_fields(
|
71
|
+
fn: ta.Callable[[str, dc.Field], dc.Field],
|
72
|
+
fields: ta.Iterable[str] | None = None,
|
73
|
+
) -> ta.Callable[[type[T]], type[T]]:
|
74
|
+
def inner(cls):
|
75
|
+
if fields is None:
|
76
|
+
for a, v in list(cls.__dict__.items()):
|
77
|
+
if isinstance(v, dc.Field):
|
78
|
+
setattr(cls, a, fn(a, v))
|
79
|
+
|
80
|
+
else:
|
81
|
+
for a in fields:
|
82
|
+
v = cls.__dict__[a]
|
83
|
+
if not isinstance(v, dc.Field):
|
84
|
+
v = dc.field(default=v)
|
85
|
+
setattr(cls, a, fn(a, v))
|
86
|
+
|
87
|
+
return cls
|
88
|
+
|
89
|
+
check.not_isinstance(fields, str)
|
90
|
+
return inner
|
91
|
+
|
92
|
+
|
93
|
+
def update_fields_metadata(
|
94
|
+
nmd: ta.Mapping,
|
95
|
+
fields: ta.Iterable[str] | None = None,
|
96
|
+
) -> ta.Callable[[type[T]], type[T]]:
|
97
|
+
def inner(a: str, f: dc.Field) -> dc.Field:
|
98
|
+
return update_field_metadata(f, nmd)
|
99
|
+
|
100
|
+
return update_fields(inner, fields)
|
101
|
+
|
102
|
+
|
103
|
+
#
|
104
|
+
|
105
|
+
|
61
106
|
def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
|
62
107
|
if not args:
|
63
108
|
return o
|
omlish/defs.py
CHANGED
@@ -5,7 +5,6 @@ remain @property's for type annotation, tool assistance, debugging, and otherwis
|
|
5
5
|
certain circumstances (the real-world alternative usually being simply not adding them).
|
6
6
|
"""
|
7
7
|
# ruff: noqa: ANN201
|
8
|
-
|
9
8
|
import abc
|
10
9
|
import functools
|
11
10
|
import operator
|
@@ -22,6 +21,7 @@ BASICS = {}
|
|
22
21
|
def _basic(fn):
|
23
22
|
if fn.__name__ in BASICS:
|
24
23
|
raise NameError(fn.__name__)
|
24
|
+
|
25
25
|
BASICS[fn.__name__] = fn
|
26
26
|
return fn
|
27
27
|
|
@@ -30,6 +30,7 @@ def _basic(fn):
|
|
30
30
|
def basic(cls_dct, *attrs, basics=None):
|
31
31
|
if basics is None:
|
32
32
|
basics = BASICS.keys()
|
33
|
+
|
33
34
|
for k in basics:
|
34
35
|
fn = BASICS[k]
|
35
36
|
fn(*attrs, cls_dct=cls_dct)
|
@@ -43,6 +44,7 @@ def _repr_guard(fn):
|
|
43
44
|
def inner(obj, *args, **kwargs):
|
44
45
|
try:
|
45
46
|
ids = _REPR_SEEN.ids
|
47
|
+
|
46
48
|
except AttributeError:
|
47
49
|
ids = _REPR_SEEN.ids = set()
|
48
50
|
try:
|
@@ -50,6 +52,7 @@ def _repr_guard(fn):
|
|
50
52
|
return fn(obj, *args, **kwargs)
|
51
53
|
finally:
|
52
54
|
del _REPR_SEEN.ids
|
55
|
+
|
53
56
|
else:
|
54
57
|
if id(obj) in ids:
|
55
58
|
return f'<seen:{type(obj).__name__}@{hex(id(obj))[2:]}>'
|
@@ -64,17 +67,27 @@ def build_attr_repr(obj, *, mro=False):
|
|
64
67
|
if mro:
|
65
68
|
attrs = [
|
66
69
|
attr
|
67
|
-
for ty in sorted(
|
68
|
-
|
70
|
+
for ty in sorted( # noqa
|
71
|
+
reversed(type(obj).__mro__),
|
72
|
+
key=lambda _ty: _ty.__dict__.get('__repr_priority__', 0),
|
73
|
+
)
|
74
|
+
for attr in ty.__dict__.get('__repr_attrs__', [])
|
75
|
+
]
|
76
|
+
|
69
77
|
else:
|
70
78
|
attrs = obj.__repr_attrs__
|
79
|
+
|
71
80
|
s = ', '.join(f'{a}={"<self>" if v is obj else _repr(v)}' for a in attrs for v in [getattr(obj, a)])
|
72
81
|
return f'{type(obj).__name__}@{hex(id(obj))[2:]}({s})'
|
73
82
|
|
74
83
|
|
75
84
|
@_repr_guard
|
76
85
|
def build_repr(obj, *attrs):
|
77
|
-
return
|
86
|
+
return (
|
87
|
+
f'{type(obj).__name__}'
|
88
|
+
f'@{hex(id(obj))[2:]}'
|
89
|
+
f'({", ".join(f"{attr}={getattr(obj, attr)!r}" for attr in attrs)})'
|
90
|
+
)
|
78
91
|
|
79
92
|
|
80
93
|
@_basic
|
@@ -86,6 +99,7 @@ def repr(cls_dct, *attrs, mro=False, priority=None): # noqa
|
|
86
99
|
cls_dct['__repr_attrs__'] = attrs
|
87
100
|
if priority is not None:
|
88
101
|
cls_dct['__repr_priority__'] = priority
|
102
|
+
|
89
103
|
cls_dct['__repr__'] = __repr__
|
90
104
|
|
91
105
|
|
@@ -141,9 +155,11 @@ def hash_eq(cls_dct, *attrs):
|
|
141
155
|
def __eq__(self, other): # noqa
|
142
156
|
if type(other) is not type(self):
|
143
157
|
return False
|
158
|
+
|
144
159
|
for attr in attrs: # noqa
|
145
160
|
if getattr(self, attr) != getattr(other, attr):
|
146
161
|
return False
|
162
|
+
|
147
163
|
return True
|
148
164
|
|
149
165
|
cls_dct['__eq__'] = __eq__
|
@@ -162,6 +178,7 @@ def not_implemented(cls_dct, *names, **kwargs):
|
|
162
178
|
wrapper = kwargs.pop('wrapper', lambda _: _)
|
163
179
|
if kwargs:
|
164
180
|
raise TypeError(kwargs)
|
181
|
+
|
165
182
|
ret = []
|
166
183
|
for name in names:
|
167
184
|
@wrapper
|
@@ -171,6 +188,7 @@ def not_implemented(cls_dct, *names, **kwargs):
|
|
171
188
|
not_implemented.__name__ = name
|
172
189
|
cls_dct[name] = not_implemented
|
173
190
|
ret.append(not_implemented)
|
191
|
+
|
174
192
|
return tuple(ret)
|
175
193
|
|
176
194
|
|
@@ -186,4 +204,10 @@ def abstract_property(cls_dct, *names):
|
|
186
204
|
|
187
205
|
@lang.cls_dct_fn()
|
188
206
|
def abstract_hash_eq(cls_dct):
|
189
|
-
return not_implemented(
|
207
|
+
return not_implemented(
|
208
|
+
cls_dct,
|
209
|
+
'__hash__',
|
210
|
+
'__eq__',
|
211
|
+
'__ne__',
|
212
|
+
wrapper=abc.abstractmethod,
|
213
|
+
)
|
omlish/fnpairs.py
CHANGED
omlish/formats/json.py
CHANGED
omlish/lang/descriptors.py
CHANGED
@@ -125,11 +125,13 @@ class _decorator_descriptor: # noqa
|
|
125
125
|
|
126
126
|
def __get__(self, instance, owner=None):
|
127
127
|
fn = self._fn.__get__(instance, owner)
|
128
|
+
|
128
129
|
if self._md or instance is not None:
|
129
130
|
@functools.wraps(fn)
|
130
131
|
def inner(*args, **kwargs):
|
131
132
|
return self._wrapper(fn, *args, **kwargs)
|
132
133
|
return inner
|
134
|
+
|
133
135
|
else:
|
134
136
|
@functools.wraps(fn)
|
135
137
|
def outer(this, *args, **kwargs):
|
omlish/lite/logs.py
CHANGED
@@ -69,8 +69,8 @@ class JsonLogFormatter(logging.Formatter):
|
|
69
69
|
STANDARD_LOG_FORMAT_PARTS = [
|
70
70
|
('asctime', '%(asctime)-15s'),
|
71
71
|
('process', 'pid=%(process)-6s'),
|
72
|
-
('thread', 'tid=%(thread)
|
73
|
-
('levelname', '%(levelname)
|
72
|
+
('thread', 'tid=%(thread)x'),
|
73
|
+
('levelname', '%(levelname)s'),
|
74
74
|
('name', '%(name)s'),
|
75
75
|
('separator', '::'),
|
76
76
|
('message', '%(message)s'),
|
omlish/logs/configs.py
CHANGED
@@ -3,21 +3,16 @@ import logging
|
|
3
3
|
import typing as ta
|
4
4
|
|
5
5
|
from ..lite.logs import configure_standard_logging as configure_lite_standard_logging
|
6
|
+
from .noisy import silence_noisy_loggers
|
6
7
|
|
7
8
|
|
8
9
|
##
|
9
10
|
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
'kazoo.client',
|
16
|
-
'requests.packages.urllib3.connectionpool',
|
17
|
-
}
|
18
|
-
|
19
|
-
|
20
|
-
##
|
12
|
+
FilterConfig = dict[str, ta.Any]
|
13
|
+
FormatterConfig = dict[str, ta.Any]
|
14
|
+
HandlerConfig = dict[str, ta.Any]
|
15
|
+
LoggerConfig = dict[str, ta.Any]
|
21
16
|
|
22
17
|
|
23
18
|
@dc.dataclass()
|
@@ -25,17 +20,11 @@ class DictConfig:
|
|
25
20
|
version: int = 1
|
26
21
|
incremental: bool = False
|
27
22
|
disable_existing_loggers: bool = False
|
28
|
-
filters: dict[str,
|
29
|
-
formatters: dict[str,
|
30
|
-
handlers: dict[str,
|
31
|
-
loggers: dict[str,
|
32
|
-
root:
|
33
|
-
|
34
|
-
|
35
|
-
FilterConfig = dict[str, ta.Any]
|
36
|
-
FormatterConfig = dict[str, ta.Any]
|
37
|
-
HandlerConfig = dict[str, ta.Any]
|
38
|
-
LoggerConfig = dict[str, ta.Any]
|
23
|
+
filters: dict[str, FilterConfig] = dc.field(default_factory=dict)
|
24
|
+
formatters: dict[str, FormatterConfig] = dc.field(default_factory=dict)
|
25
|
+
handlers: dict[str, HandlerConfig] = dc.field(default_factory=dict)
|
26
|
+
loggers: dict[str, LoggerConfig] = dc.field(default_factory=dict)
|
27
|
+
root: LoggerConfig | None = None
|
39
28
|
|
40
29
|
|
41
30
|
##
|
@@ -51,7 +40,6 @@ def configure_standard_logging(
|
|
51
40
|
json=json,
|
52
41
|
)
|
53
42
|
|
54
|
-
|
55
|
-
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
|
43
|
+
silence_noisy_loggers()
|
56
44
|
|
57
45
|
return handler
|
omlish/logs/noisy.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
|
4
|
+
NOISY_LOGGERS: set[str] = {
|
5
|
+
'boto3.resources.action',
|
6
|
+
'datadog.dogstatsd',
|
7
|
+
'elasticsearch',
|
8
|
+
'kazoo.client',
|
9
|
+
'requests.packages.urllib3.connectionpool',
|
10
|
+
}
|
11
|
+
|
12
|
+
|
13
|
+
def silence_noisy_loggers() -> None:
|
14
|
+
for noisy_logger in NOISY_LOGGERS:
|
15
|
+
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
|