omlish 0.0.0.dev25__py3-none-any.whl → 0.0.0.dev27__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.
Files changed (44) hide show
  1. omlish/__about__.py +8 -5
  2. omlish/bootstrap/diag.py +25 -0
  3. omlish/bootstrap/harness.py +3 -2
  4. omlish/check.py +1 -1
  5. omlish/collections/__init__.py +1 -0
  6. omlish/collections/utils.py +7 -2
  7. omlish/dataclasses/__init__.py +4 -4
  8. omlish/dataclasses/impl/api.py +5 -3
  9. omlish/dataclasses/impl/exceptions.py +2 -2
  10. omlish/dataclasses/impl/fields.py +7 -4
  11. omlish/dataclasses/impl/init.py +8 -8
  12. omlish/dataclasses/impl/metadata.py +3 -3
  13. omlish/dataclasses/impl/params.py +4 -3
  14. omlish/diag/replserver/server.py +17 -2
  15. omlish/docker.py +74 -6
  16. omlish/formats/yaml.py +10 -0
  17. omlish/graphs/dot/__init__.py +31 -19
  18. omlish/graphs/dot/make.py +16 -0
  19. omlish/graphs/dot/rendering.py +9 -8
  20. omlish/http/cookies.py +2 -1
  21. omlish/inject/impl/scopes.py +2 -0
  22. omlish/inject/keys.py +1 -1
  23. omlish/inject/multis.py +4 -4
  24. omlish/inject/providers.py +1 -1
  25. omlish/lang/__init__.py +8 -0
  26. omlish/lang/classes/__init__.py +3 -0
  27. omlish/lang/classes/restrict.py +25 -4
  28. omlish/lang/classes/simple.py +0 -4
  29. omlish/lang/classes/virtual.py +6 -4
  30. omlish/lang/datetimes.py +9 -0
  31. omlish/lang/resources.py +29 -10
  32. omlish/lite/check.py +6 -0
  33. omlish/lite/logs.py +30 -25
  34. omlish/lite/secrets.py +3 -1
  35. omlish/logs/configs.py +2 -2
  36. omlish/marshal/dataclasses.py +1 -1
  37. omlish/secrets/secrets.py +1 -1
  38. omlish/stats.py +1 -1
  39. {omlish-0.0.0.dev25.dist-info → omlish-0.0.0.dev27.dist-info}/METADATA +1 -1
  40. {omlish-0.0.0.dev25.dist-info → omlish-0.0.0.dev27.dist-info}/RECORD +44 -42
  41. {omlish-0.0.0.dev25.dist-info → omlish-0.0.0.dev27.dist-info}/WHEEL +1 -1
  42. /omlish/{_manifests.json → .manifests.json} +0 -0
  43. {omlish-0.0.0.dev25.dist-info → omlish-0.0.0.dev27.dist-info}/LICENSE +0 -0
  44. {omlish-0.0.0.dev25.dist-info → omlish-0.0.0.dev27.dist-info}/top_level.txt +0 -0
omlish/inject/multis.py CHANGED
@@ -32,14 +32,14 @@ def _check_set_multi_key(mk: Key) -> bool:
32
32
  @dc.dataclass(frozen=True)
33
33
  @dc.extra_params(cache_hash=True)
34
34
  class SetBinding(Element, lang.Final):
35
- multi_key: Key = dc.xfield(check=_check_set_multi_key)
35
+ multi_key: Key = dc.xfield(validate=_check_set_multi_key)
36
36
  dst: Key = dc.xfield(coerce=check.of_isinstance(Key))
37
37
 
38
38
 
39
39
  @dc.dataclass(frozen=True)
40
40
  @dc.extra_params(cache_hash=True)
41
41
  class SetProvider(Provider):
42
- multi_key: Key = dc.xfield(check=_check_set_multi_key)
42
+ multi_key: Key = dc.xfield(validate=_check_set_multi_key)
43
43
 
44
44
 
45
45
  ##
@@ -52,7 +52,7 @@ def _check_map_multi_key(mk: Key) -> bool:
52
52
  @dc.dataclass(frozen=True)
53
53
  @dc.extra_params(cache_hash=True)
54
54
  class MapBinding(Element, lang.Final):
55
- multi_key: Key = dc.xfield(check=_check_map_multi_key)
55
+ multi_key: Key = dc.xfield(validate=_check_map_multi_key)
56
56
  map_key: ta.Any = dc.xfield()
57
57
  dst: Key = dc.xfield(coerce=check.of_isinstance(Key))
58
58
 
@@ -60,7 +60,7 @@ class MapBinding(Element, lang.Final):
60
60
  @dc.dataclass(frozen=True)
61
61
  @dc.extra_params(cache_hash=True)
62
62
  class MapProvider(Provider):
63
- multi_key: Key = dc.xfield(check=_check_map_multi_key)
63
+ multi_key: Key = dc.xfield(validate=_check_map_multi_key)
64
64
 
65
65
 
66
66
  ##
@@ -17,7 +17,7 @@ class Provider(lang.Abstract):
17
17
  @dc.dataclass(frozen=True)
18
18
  @dc.extra_params(cache_hash=True)
19
19
  class FnProvider(Provider):
20
- fn: ta.Any = dc.xfield(check=callable)
20
+ fn: ta.Any = dc.xfield(validate=callable)
21
21
 
22
22
 
23
23
  @dc.dataclass(frozen=True)
omlish/lang/__init__.py CHANGED
@@ -5,6 +5,7 @@ from .cached import ( # noqa
5
5
 
6
6
  from .classes import ( # noqa
7
7
  Abstract,
8
+ AnySensitive,
8
9
  Callable,
9
10
  Descriptor,
10
11
  Final,
@@ -17,8 +18,10 @@ from .classes import ( # noqa
17
18
  NotPicklable,
18
19
  PackageSealed,
19
20
  Picklable,
21
+ SENSITIVE_ATTR,
20
22
  Sealed,
21
23
  SealedError,
24
+ Sensitive,
22
25
  SimpleMetaDict,
23
26
  Singleton,
24
27
  Virtual,
@@ -68,6 +71,11 @@ from .contextmanagers import ( # noqa
68
71
  maybe_managing,
69
72
  )
70
73
 
74
+ from .datetimes import ( # noqa
75
+ utcnow,
76
+ utcfromtimestamp,
77
+ )
78
+
71
79
  from .descriptors import ( # noqa
72
80
  AccessForbiddenError,
73
81
  access_forbidden,
@@ -7,14 +7,17 @@ from .abstract import ( # noqa
7
7
  )
8
8
 
9
9
  from .restrict import ( # noqa
10
+ AnySensitive,
10
11
  Final,
11
12
  FinalError,
12
13
  NoBool,
13
14
  NotInstantiable,
14
15
  NotPicklable,
15
16
  PackageSealed,
17
+ SENSITIVE_ATTR,
16
18
  Sealed,
17
19
  SealedError,
20
+ Sensitive,
18
21
  no_bool,
19
22
  )
20
23
 
@@ -1,3 +1,4 @@
1
+ import abc
1
2
  import functools
2
3
  import typing as ta
3
4
 
@@ -9,7 +10,6 @@ from .abstract import is_abstract
9
10
 
10
11
 
11
12
  class FinalError(TypeError):
12
-
13
13
  def __init__(self, _type: type) -> None:
14
14
  super().__init__()
15
15
 
@@ -49,7 +49,6 @@ class Final(Abstract):
49
49
 
50
50
 
51
51
  class SealedError(TypeError):
52
-
53
52
  def __init__(self, _type) -> None:
54
53
  super().__init__()
55
54
 
@@ -118,13 +117,11 @@ class NotPicklable:
118
117
 
119
118
 
120
119
  class NoBool:
121
-
122
120
  def __bool__(self) -> bool:
123
121
  raise TypeError
124
122
 
125
123
 
126
124
  class _NoBoolDescriptor:
127
-
128
125
  def __init__(self, fn, instance=None, owner=None) -> None:
129
126
  super().__init__()
130
127
  self._fn = fn
@@ -146,3 +143,27 @@ class _NoBoolDescriptor:
146
143
 
147
144
  def no_bool(fn): # noqa
148
145
  return _NoBoolDescriptor(fn)
146
+
147
+
148
+ ##
149
+
150
+
151
+ SENSITIVE_ATTR = '__sensitive__'
152
+
153
+
154
+ class Sensitive:
155
+ __sensitive__ = True
156
+
157
+
158
+ class _AnySensitiveMeta(abc.ABCMeta):
159
+ @classmethod
160
+ def __instancecheck__(cls, instance: object) -> bool:
161
+ return hasattr(instance, SENSITIVE_ATTR) or super().__instancecheck__(AnySensitive, instance)
162
+
163
+ @classmethod
164
+ def __subclasscheck__(cls, subclass: type) -> bool:
165
+ return hasattr(subclass, SENSITIVE_ATTR) or super().__subclasscheck__(AnySensitive, subclass)
166
+
167
+
168
+ class AnySensitive(NotInstantiable, Final, metaclass=_AnySensitiveMeta):
169
+ pass
@@ -42,7 +42,6 @@ _MARKER_NAMESPACE_KEYS: set[str] | None = None
42
42
 
43
43
 
44
44
  class _MarkerMeta(abc.ABCMeta):
45
-
46
45
  def __new__(mcls, name, bases, namespace):
47
46
  global _MARKER_NAMESPACE_KEYS
48
47
 
@@ -74,7 +73,6 @@ class Marker(NotInstantiable, metaclass=_MarkerMeta):
74
73
 
75
74
 
76
75
  class SimpleMetaDict(dict):
77
-
78
76
  def update(self, m: ta.Mapping[K, V], **kwargs: V) -> None: # type: ignore
79
77
  for k, v in m.items():
80
78
  self[k] = v
@@ -109,7 +107,6 @@ def _set_singleton_instance(inst):
109
107
 
110
108
 
111
109
  class Singleton:
112
-
113
110
  def __new__(cls):
114
111
  return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
115
112
 
@@ -119,7 +116,6 @@ class Singleton:
119
116
 
120
117
 
121
118
  class LazySingleton:
122
-
123
119
  def __new__(cls):
124
120
  try:
125
121
  return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
@@ -40,7 +40,12 @@ class _VirtualMeta(abc.ABCMeta):
40
40
  if absv is not v:
41
41
  namespace[k] = absv
42
42
 
43
- reqs = {k: v for k, v in namespace.items() if getattr(v, '__isabstractmethod__', False)}
43
+ reqs = {
44
+ k: v
45
+ for k, v in namespace.items()
46
+ if getattr(v, '__isabstractmethod__', False)
47
+ }
48
+
44
49
  user_subclasshook = namespace.pop('__subclasshook__', None)
45
50
 
46
51
  def get_missing_reqs(cls):
@@ -94,13 +99,11 @@ def virtual_check(virtual: type) -> ta.Callable[[Ty], Ty]:
94
99
 
95
100
 
96
101
  class Descriptor(Virtual):
97
-
98
102
  def __get__(self, instance, owner=None):
99
103
  raise NotImplementedError
100
104
 
101
105
 
102
106
  class Picklable(Virtual):
103
-
104
107
  def __getstate__(self):
105
108
  raise NotImplementedError
106
109
 
@@ -112,7 +115,6 @@ class Picklable(Virtual):
112
115
 
113
116
 
114
117
  class Callable(NotInstantiable, Final, ta.Generic[T]):
115
-
116
118
  def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
117
119
  raise TypeError
118
120
 
@@ -0,0 +1,9 @@
1
+ import datetime
2
+
3
+
4
+ def utcnow() -> datetime.datetime:
5
+ return datetime.datetime.now(tz=datetime.UTC)
6
+
7
+
8
+ def utcfromtimestamp(ts: float) -> datetime.datetime:
9
+ return datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)
omlish/lang/resources.py CHANGED
@@ -11,7 +11,7 @@ class RelativeResource(ta.NamedTuple):
11
11
 
12
12
 
13
13
  def get_relative_resources(
14
- prefix: str | None = None,
14
+ path: str = '',
15
15
  *,
16
16
  globals: ta.Mapping[str, ta.Any] | None = None, # noqa
17
17
  package: str | None = None,
@@ -23,13 +23,29 @@ def get_relative_resources(
23
23
  if not file:
24
24
  file = globals.get('__file__')
25
25
 
26
+ #
27
+
28
+ if os.sep in path:
29
+ raise ValueError(path) # noqa
30
+ if not path.startswith('.'):
31
+ path = '.' + path
32
+ if set(path) - {'.'}:
33
+ dot_pos = next(i for i in range(len(path)) if path[i] != '.')
34
+ num_up = dot_pos - 1
35
+ path_parts = path[dot_pos:].split('.')
36
+ else:
37
+ num_up = len(path) - 1
38
+ path_parts = []
39
+
40
+ #
41
+
26
42
  lst: list[RelativeResource] = []
27
43
 
28
44
  if package:
29
- anchor = package
30
- if prefix:
31
- anchor += '.' + prefix.replace(os.sep, '.')
32
-
45
+ pkg_parts = package.split('.')
46
+ if num_up:
47
+ pkg_parts = pkg_parts[:-num_up]
48
+ anchor = '.'.join([*pkg_parts, *path_parts])
33
49
  for pf in importlib.resources.files(anchor).iterdir():
34
50
  lst.append(RelativeResource(
35
51
  name=pf.name,
@@ -38,16 +54,19 @@ def get_relative_resources(
38
54
  ))
39
55
 
40
56
  elif file:
41
- path = os.path.dirname(file)
42
- if prefix:
43
- path = os.path.join(path, prefix.replace('.', os.sep))
57
+ base_dir = os.path.dirname(file)
58
+ dst_dir = os.path.join(
59
+ base_dir,
60
+ *(['..'] * num_up),
61
+ *path_parts,
62
+ )
44
63
 
45
64
  def _read_file(fp: str) -> bytes:
46
65
  with open(fp, 'rb') as f:
47
66
  return f.read()
48
67
 
49
- for ff in os.listdir(path):
50
- ff = os.path.join(path, ff)
68
+ for ff in os.listdir(dst_dir):
69
+ ff = os.path.join(dst_dir, ff)
51
70
  lst.append(RelativeResource(
52
71
  name=os.path.basename(ff),
53
72
  is_file=os.path.isfile(ff),
omlish/lite/check.py CHANGED
@@ -27,3 +27,9 @@ def check_not(v: ta.Any) -> None:
27
27
  if v:
28
28
  raise ValueError(v)
29
29
  return v
30
+
31
+
32
+ def check_non_empty_str(v: ta.Optional[str]) -> str:
33
+ if not v:
34
+ raise ValueError
35
+ return v
omlish/lite/logs.py CHANGED
@@ -208,43 +208,48 @@ def configure_standard_logging(
208
208
  *,
209
209
  json: bool = False,
210
210
  target: ta.Optional[logging.Logger] = None,
211
- no_check: bool = False,
211
+ force: bool = False,
212
212
  ) -> ta.Optional[StandardLogHandler]:
213
- if target is None:
214
- target = logging.root
213
+ logging._acquireLock() # type: ignore # noqa
214
+ try:
215
+ if target is None:
216
+ target = logging.root
215
217
 
216
- #
218
+ #
217
219
 
218
- if not no_check:
219
- if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
220
- return None
220
+ if not force:
221
+ if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
222
+ return None
221
223
 
222
- #
224
+ #
223
225
 
224
- handler = logging.StreamHandler()
226
+ handler = logging.StreamHandler()
225
227
 
226
- #
228
+ #
227
229
 
228
- formatter: logging.Formatter
229
- if json:
230
- formatter = JsonLogFormatter()
231
- else:
232
- formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
233
- handler.setFormatter(formatter)
230
+ formatter: logging.Formatter
231
+ if json:
232
+ formatter = JsonLogFormatter()
233
+ else:
234
+ formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
235
+ handler.setFormatter(formatter)
236
+
237
+ #
234
238
 
235
- #
239
+ handler.addFilter(TidLogFilter())
236
240
 
237
- handler.addFilter(TidLogFilter())
241
+ #
238
242
 
239
- #
243
+ target.addHandler(handler)
240
244
 
241
- target.addHandler(handler)
245
+ #
242
246
 
243
- #
247
+ if level is not None:
248
+ target.setLevel(level)
244
249
 
245
- if level is not None:
246
- target.setLevel(level)
250
+ #
247
251
 
248
- #
252
+ return StandardLogHandler(handler)
249
253
 
250
- return StandardLogHandler(handler)
254
+ finally:
255
+ logging._releaseLock() # type: ignore # noqa
omlish/lite/secrets.py CHANGED
@@ -3,9 +3,11 @@ import typing as ta
3
3
 
4
4
 
5
5
  class Secret:
6
+ __sensitive__ = True
7
+
6
8
  _VALUE_ATTR = '__secret_value__'
7
9
 
8
- def __init__(self, *, key: ta.Optional[str], value: str) -> None:
10
+ def __init__(self, *, key: ta.Optional[str] = None, value: str) -> None:
9
11
  super().__init__()
10
12
  self._key = key
11
13
  setattr(self, self._VALUE_ATTR, lambda: value)
omlish/logs/configs.py CHANGED
@@ -36,13 +36,13 @@ def configure_standard_logging(
36
36
  *,
37
37
  json: bool = False,
38
38
  target: logging.Logger | None = None,
39
- no_check: bool = False,
39
+ force: bool = False,
40
40
  ) -> StandardLogHandler | None:
41
41
  handler = configure_lite_standard_logging(
42
42
  level,
43
43
  json=json,
44
44
  target=target,
45
- no_check=no_check,
45
+ force=force,
46
46
  )
47
47
 
48
48
  if handler is None:
@@ -24,7 +24,7 @@ from .objects import ObjectUnmarshaler
24
24
 
25
25
 
26
26
  def get_dataclass_metadata(ty: type) -> ObjectMetadata:
27
- return check.optional_single(
27
+ return check.opt_single(
28
28
  e
29
29
  for e in dc.get_merged_metadata(ty).get(dc.UserMetadata, [])
30
30
  if isinstance(e, ObjectMetadata)
omlish/secrets/secrets.py CHANGED
@@ -28,7 +28,7 @@ log = logging.getLogger(__name__)
28
28
  ##
29
29
 
30
30
 
31
- class Secret(lang.NotPicklable, lang.Final):
31
+ class Secret(lang.NotPicklable, lang.Sensitive, lang.Final):
32
32
  _VALUE_ATTR = '__secret_value__'
33
33
 
34
34
  def __init__(self, *, key: str | None, value: str) -> None:
omlish/stats.py CHANGED
@@ -2,7 +2,7 @@
2
2
  TODO:
3
3
  - reservoir
4
4
  - dep tdigest?
5
- - struct-of-arrays backed SamplingHistogram
5
+ - struct-of-arrays - array.array('f', ...) - backed SamplingHistogram
6
6
  """
7
7
  import bisect
8
8
  import collections
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev25
3
+ Version: 0.0.0.dev27
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause