omlish 0.0.0.dev22__py3-none-any.whl → 0.0.0.dev24__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 (46) hide show
  1. omlish/__about__.py +10 -3
  2. omlish/asyncs/bridge.py +3 -0
  3. omlish/bootstrap/__init__.py +39 -0
  4. omlish/bootstrap/base.py +3 -1
  5. omlish/bootstrap/diag.py +48 -1
  6. omlish/bootstrap/main.py +2 -1
  7. omlish/bootstrap/marshal.py +18 -0
  8. omlish/check.py +305 -37
  9. omlish/collections/__init__.py +2 -2
  10. omlish/collections/utils.py +3 -3
  11. omlish/concurrent/threadlets.py +5 -0
  12. omlish/dataclasses/__init__.py +1 -0
  13. omlish/dataclasses/impl/init.py +10 -2
  14. omlish/dataclasses/impl/metadata.py +3 -3
  15. omlish/dataclasses/impl/reflect.py +1 -1
  16. omlish/dataclasses/utils.py +16 -3
  17. omlish/diag/asts.py +132 -0
  18. omlish/diag/pycharm.py +139 -0
  19. omlish/docker.py +19 -11
  20. omlish/genmachine.py +59 -0
  21. omlish/graphs/trees.py +1 -1
  22. omlish/lang/__init__.py +13 -1
  23. omlish/lang/cached.py +5 -2
  24. omlish/lang/descriptors.py +33 -16
  25. omlish/lang/resources.py +60 -0
  26. omlish/lite/logs.py +133 -4
  27. omlish/logs/__init__.py +17 -2
  28. omlish/logs/configs.py +13 -1
  29. omlish/logs/formatters.py +0 -1
  30. omlish/marshal/__init__.py +6 -0
  31. omlish/marshal/base64.py +4 -0
  32. omlish/marshal/helpers.py +27 -0
  33. omlish/marshal/primitives.py +6 -0
  34. omlish/marshal/standard.py +4 -0
  35. omlish/marshal/unions.py +101 -0
  36. omlish/matchfns.py +3 -3
  37. omlish/specs/jsonschema/keywords/base.py +2 -2
  38. omlish/specs/jsonschema/keywords/parse.py +1 -1
  39. omlish/sql/__init__.py +18 -0
  40. omlish/sql/qualifiedname.py +82 -0
  41. omlish/stats.py +1 -0
  42. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/METADATA +1 -1
  43. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/RECORD +46 -39
  44. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/LICENSE +0 -0
  45. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/WHEEL +0 -0
  46. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,60 @@
1
+ import functools
2
+ import importlib.resources
3
+ import os.path
4
+ import typing as ta
5
+
6
+
7
+ class RelativeResource(ta.NamedTuple):
8
+ name: str
9
+ is_file: bool
10
+ read_bytes: ta.Callable[[], bytes]
11
+
12
+
13
+ def get_relative_resources(
14
+ prefix: str | None = None,
15
+ *,
16
+ globals: ta.Mapping[str, ta.Any] | None = None, # noqa
17
+ package: str | None = None,
18
+ file: str | None = None,
19
+ ) -> ta.Mapping[str, RelativeResource]:
20
+ if globals is not None:
21
+ if not package:
22
+ package = globals.get('__package__')
23
+ if not file:
24
+ file = globals.get('__file__')
25
+
26
+ lst: list[RelativeResource] = []
27
+
28
+ if package:
29
+ anchor = package
30
+ if prefix:
31
+ anchor += '.' + prefix.replace(os.sep, '.')
32
+
33
+ for pf in importlib.resources.files(anchor).iterdir():
34
+ lst.append(RelativeResource(
35
+ name=pf.name,
36
+ is_file=pf.is_file(),
37
+ read_bytes=pf.read_bytes if pf.is_file() else None, # type: ignore
38
+ ))
39
+
40
+ elif file:
41
+ path = os.path.dirname(file)
42
+ if prefix:
43
+ path = os.path.join(path, prefix.replace('.', os.sep))
44
+
45
+ def _read_file(fp: str) -> bytes:
46
+ with open(fp, 'rb') as f:
47
+ return f.read()
48
+
49
+ for ff in os.listdir(path):
50
+ ff = os.path.join(path, ff)
51
+ lst.append(RelativeResource(
52
+ name=os.path.basename(ff),
53
+ is_file=os.path.isfile(ff),
54
+ read_bytes=functools.partial(_read_file, ff),
55
+ ))
56
+
57
+ else:
58
+ raise RuntimeError('no package or file specified')
59
+
60
+ return {r.name: r for r in lst}
omlish/lite/logs.py CHANGED
@@ -97,13 +97,134 @@ class StandardLogFormatter(logging.Formatter):
97
97
  ##
98
98
 
99
99
 
100
+ class ProxyLogFilterer(logging.Filterer):
101
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
102
+ self._underlying = underlying
103
+
104
+ @property
105
+ def underlying(self) -> logging.Filterer:
106
+ return self._underlying
107
+
108
+ @property
109
+ def filters(self):
110
+ return self._underlying.filters
111
+
112
+ @filters.setter
113
+ def filters(self, filters):
114
+ self._underlying.filters = filters
115
+
116
+ def addFilter(self, filter): # noqa
117
+ self._underlying.addFilter(filter)
118
+
119
+ def removeFilter(self, filter): # noqa
120
+ self._underlying.removeFilter(filter)
121
+
122
+ def filter(self, record):
123
+ return self._underlying.filter(record)
124
+
125
+
126
+ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
127
+ def __init__(self, underlying: logging.Handler) -> None: # noqa
128
+ ProxyLogFilterer.__init__(self, underlying)
129
+
130
+ _underlying: logging.Handler
131
+
132
+ @property
133
+ def underlying(self) -> logging.Handler:
134
+ return self._underlying
135
+
136
+ def get_name(self):
137
+ return self._underlying.get_name()
138
+
139
+ def set_name(self, name):
140
+ self._underlying.set_name(name)
141
+
142
+ @property
143
+ def name(self):
144
+ return self._underlying.name
145
+
146
+ @property
147
+ def level(self):
148
+ return self._underlying.level
149
+
150
+ @level.setter
151
+ def level(self, level):
152
+ self._underlying.level = level
153
+
154
+ @property
155
+ def formatter(self):
156
+ return self._underlying.formatter
157
+
158
+ @formatter.setter
159
+ def formatter(self, formatter):
160
+ self._underlying.formatter = formatter
161
+
162
+ def createLock(self):
163
+ self._underlying.createLock()
164
+
165
+ def acquire(self):
166
+ self._underlying.acquire()
167
+
168
+ def release(self):
169
+ self._underlying.release()
170
+
171
+ def setLevel(self, level):
172
+ self._underlying.setLevel(level)
173
+
174
+ def format(self, record):
175
+ return self._underlying.format(record)
176
+
177
+ def emit(self, record):
178
+ self._underlying.emit(record)
179
+
180
+ def handle(self, record):
181
+ return self._underlying.handle(record)
182
+
183
+ def setFormatter(self, fmt):
184
+ self._underlying.setFormatter(fmt)
185
+
186
+ def flush(self):
187
+ self._underlying.flush()
188
+
189
+ def close(self):
190
+ self._underlying.close()
191
+
192
+ def handleError(self, record):
193
+ self._underlying.handleError(record)
194
+
195
+
196
+ ##
197
+
198
+
199
+ class StandardLogHandler(ProxyLogHandler):
200
+ pass
201
+
202
+
203
+ ##
204
+
205
+
100
206
  def configure_standard_logging(
101
207
  level: ta.Union[int, str] = logging.INFO,
102
208
  *,
103
209
  json: bool = False,
104
- ) -> logging.Handler:
210
+ target: ta.Optional[logging.Logger] = None,
211
+ no_check: bool = False,
212
+ ) -> ta.Optional[StandardLogHandler]:
213
+ if target is None:
214
+ target = logging.root
215
+
216
+ #
217
+
218
+ if not no_check:
219
+ if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
220
+ return None
221
+
222
+ #
223
+
105
224
  handler = logging.StreamHandler()
106
225
 
226
+ #
227
+
107
228
  formatter: logging.Formatter
108
229
  if json:
109
230
  formatter = JsonLogFormatter()
@@ -111,11 +232,19 @@ def configure_standard_logging(
111
232
  formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
112
233
  handler.setFormatter(formatter)
113
234
 
235
+ #
236
+
114
237
  handler.addFilter(TidLogFilter())
115
238
 
116
- logging.root.addHandler(handler)
239
+ #
240
+
241
+ target.addHandler(handler)
242
+
243
+ #
117
244
 
118
245
  if level is not None:
119
- logging.root.setLevel(level)
246
+ target.setLevel(level)
247
+
248
+ #
120
249
 
121
- return handler
250
+ return StandardLogHandler(handler)
omlish/logs/__init__.py CHANGED
@@ -4,8 +4,6 @@ from .configs import ( # noqa
4
4
 
5
5
  from .formatters import ( # noqa
6
6
  ColorLogFormatter,
7
- JsonLogFormatter,
8
- StandardLogFormatter,
9
7
  )
10
8
 
11
9
  from .handlers import ( # noqa
@@ -15,3 +13,20 @@ from .handlers import ( # noqa
15
13
  from .utils import ( # noqa
16
14
  error_logging,
17
15
  )
16
+
17
+
18
+ ##
19
+
20
+
21
+ from ..lite.logs import ( # noqa
22
+ TidLogFilter,
23
+ JsonLogFormatter,
24
+
25
+ STANDARD_LOG_FORMAT_PARTS,
26
+ StandardLogFormatter,
27
+
28
+ ProxyLogFilterer,
29
+ ProxyLogHandler,
30
+
31
+ StandardLogHandler,
32
+ )
omlish/logs/configs.py CHANGED
@@ -2,6 +2,7 @@ import dataclasses as dc
2
2
  import logging
3
3
  import typing as ta
4
4
 
5
+ from ..lite.logs import StandardLogHandler
5
6
  from ..lite.logs import configure_standard_logging as configure_lite_standard_logging
6
7
  from .noisy import silence_noisy_loggers
7
8
 
@@ -34,12 +35,23 @@ def configure_standard_logging(
34
35
  level: ta.Any = None,
35
36
  *,
36
37
  json: bool = False,
37
- ) -> logging.Handler:
38
+ target: logging.Logger | None = None,
39
+ no_check: bool = False,
40
+ ) -> StandardLogHandler | None:
38
41
  handler = configure_lite_standard_logging(
39
42
  level,
40
43
  json=json,
44
+ target=target,
45
+ no_check=no_check,
41
46
  )
42
47
 
48
+ if handler is None:
49
+ return None
50
+
51
+ #
52
+
43
53
  silence_noisy_loggers()
44
54
 
55
+ #
56
+
45
57
  return handler
omlish/logs/formatters.py CHANGED
@@ -3,7 +3,6 @@ import logging
3
3
  import typing as ta
4
4
 
5
5
  from .. import term
6
- from ..lite.logs import JsonLogFormatter # noqa
7
6
  from ..lite.logs import StandardLogFormatter
8
7
 
9
8
 
@@ -49,7 +49,9 @@ from .global_ import ( # noqa
49
49
  )
50
50
 
51
51
  from .helpers import ( # noqa
52
+ update_field_metadata,
52
53
  update_fields_metadata,
54
+ update_object_metadata,
53
55
  )
54
56
 
55
57
  from .objects import ( # noqa
@@ -65,6 +67,10 @@ from .polymorphism import ( # noqa
65
67
  polymorphism_from_subclasses,
66
68
  )
67
69
 
70
+ from .primitives import ( # noqa
71
+ PRIMITIVE_TYPES,
72
+ )
73
+
68
74
  from .registries import ( # noqa
69
75
  Registry,
70
76
  )
omlish/marshal/base64.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ FIXME:
3
+ - don't base64 by default, only for json-esque targets
4
+ """
1
5
  import base64
2
6
  import typing as ta
3
7
 
omlish/marshal/helpers.py CHANGED
@@ -2,11 +2,24 @@ import typing as ta
2
2
 
3
3
  from .. import dataclasses as dc
4
4
  from .objects import FieldMetadata
5
+ from .objects import ObjectMetadata
5
6
 
6
7
 
7
8
  T = ta.TypeVar('T')
8
9
 
9
10
 
11
+ def update_field_metadata(**kwargs: ta.Any) -> dc.field_modifier:
12
+ @dc.field_modifier
13
+ def inner(f: dc.Field) -> dc.Field:
14
+ return dc.update_field_metadata(f, {
15
+ FieldMetadata: dc.replace(
16
+ f.metadata.get(FieldMetadata, FieldMetadata()),
17
+ **kwargs,
18
+ ),
19
+ })
20
+ return inner
21
+
22
+
10
23
  def update_fields_metadata(
11
24
  fields: ta.Iterable[str] | None = None,
12
25
  **kwargs: ta.Any,
@@ -20,3 +33,17 @@ def update_fields_metadata(
20
33
  })
21
34
 
22
35
  return dc.update_fields(inner, fields)
36
+
37
+
38
+ def update_object_metadata(
39
+ cls: type | None = None,
40
+ **kwargs: ta.Any,
41
+ ):
42
+ def inner(cls):
43
+ return dc.update_class_metadata(cls, ObjectMetadata(**kwargs))
44
+
45
+ if cls is not None:
46
+ inner(cls)
47
+ return cls
48
+ else:
49
+ return inner
@@ -9,6 +9,9 @@ from .base import Unmarshaler
9
9
  from .values import Value
10
10
 
11
11
 
12
+ ##
13
+
14
+
12
15
  PRIMITIVE_TYPES: tuple[type, ...] = (
13
16
  bool,
14
17
  int,
@@ -19,6 +22,9 @@ PRIMITIVE_TYPES: tuple[type, ...] = (
19
22
  )
20
23
 
21
24
 
25
+ ##
26
+
27
+
22
28
  class PrimitiveMarshalerUnmarshaler(Marshaler, Unmarshaler):
23
29
  def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
24
30
  if isinstance(o, PRIMITIVE_TYPES):
@@ -27,6 +27,8 @@ from .optionals import OptionalMarshalerFactory
27
27
  from .optionals import OptionalUnmarshalerFactory
28
28
  from .primitives import PRIMITIVE_MARSHALER_FACTORY
29
29
  from .primitives import PRIMITIVE_UNMARSHALER_FACTORY
30
+ from .unions import PrimitiveUnionMarshalerFactory
31
+ from .unions import PrimitiveUnionUnmarshalerFactory
30
32
  from .uuids import UUID_MARSHALER_FACTORY
31
33
  from .uuids import UUID_UNMARSHALER_FACTORY
32
34
 
@@ -37,6 +39,7 @@ from .uuids import UUID_UNMARSHALER_FACTORY
37
39
  STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
38
40
  PRIMITIVE_MARSHALER_FACTORY,
39
41
  OptionalMarshalerFactory(),
42
+ PrimitiveUnionMarshalerFactory(),
40
43
  DataclassMarshalerFactory(),
41
44
  EnumMarshalerFactory(),
42
45
  NUMBERS_MARSHALER_FACTORY,
@@ -66,6 +69,7 @@ def new_standard_marshaler_factory() -> MarshalerFactory:
66
69
  STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
67
70
  PRIMITIVE_UNMARSHALER_FACTORY,
68
71
  OptionalUnmarshalerFactory(),
72
+ PrimitiveUnionUnmarshalerFactory(),
69
73
  DataclassUnmarshalerFactory(),
70
74
  EnumUnmarshalerFactory(),
71
75
  NUMBERS_UNMARSHALER_FACTORY,
@@ -0,0 +1,101 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .. import check
5
+ from .. import matchfns as mfs
6
+ from .. import reflect as rfl
7
+ from .base import MarshalContext
8
+ from .base import Marshaler
9
+ from .base import MarshalerFactory
10
+ from .base import UnmarshalContext
11
+ from .base import Unmarshaler
12
+ from .base import UnmarshalerFactory
13
+ from .values import Value
14
+
15
+
16
+ ##
17
+
18
+
19
+ class MatchUnionMarshaler(Marshaler):
20
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
21
+
22
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
23
+ try:
24
+ m = self.mmf.match(ctx, o)
25
+ except mfs.AmbiguousMatchesError:
26
+ raise ValueError(o) # noqa
27
+ return m.fn(ctx, o)
28
+
29
+
30
+ class MatchUnionUnmarshaler(Unmarshaler):
31
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
32
+
33
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
34
+ try:
35
+ m = self.mmf.match(ctx, v)
36
+ except mfs.AmbiguousMatchesError:
37
+ raise ValueError(v) # noqa
38
+ return m.fn(ctx, v)
39
+
40
+
41
+ ##
42
+
43
+
44
+ PRIMITIVE_UNION_TYPES: tuple[type, ...] = (
45
+ float,
46
+ int,
47
+ str,
48
+ )
49
+
50
+
51
+ #
52
+
53
+
54
+ @dc.dataclass(frozen=True)
55
+ class PrimitiveUnionMarshaler(Marshaler):
56
+ tys: ta.Sequence[type]
57
+
58
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
59
+ raise NotImplementedError
60
+
61
+
62
+ @dc.dataclass(frozen=True)
63
+ class PrimitiveUnionMarshalerFactory(MarshalerFactory):
64
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
65
+
66
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
67
+ return isinstance(rty, rfl.Union) and all(a in self.tys for a in rty.args)
68
+
69
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
70
+ args = check.isinstance(rty, rfl.Union).args
71
+ return PrimitiveUnionMarshaler([t for t in self.tys if t in args])
72
+
73
+
74
+ #
75
+
76
+
77
+ @dc.dataclass(frozen=True)
78
+ class PrimitiveUnionUnmarshaler(Unmarshaler):
79
+ tys: ta.Sequence[type]
80
+
81
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
82
+ raise NotImplementedError
83
+
84
+
85
+ @dc.dataclass(frozen=True)
86
+ class PrimitiveUnionUnmarshalerFactory(UnmarshalerFactory):
87
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
88
+
89
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
90
+ return isinstance(rty, rfl.Union) and all(a in self.tys for a in rty.args)
91
+
92
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
93
+ args = check.isinstance(rty, rfl.Union).args
94
+ return PrimitiveUnionUnmarshaler([t for t in self.tys if t in args])
95
+
96
+
97
+ #
98
+
99
+
100
+ PRIMITIVE_UNION_MARSHALER_FACTORY = PrimitiveUnionMarshalerFactory()
101
+ PRIMITIVE_UNION_UNMARSHALER_FACTORY = PrimitiveUnionUnmarshalerFactory()
omlish/matchfns.py CHANGED
@@ -93,7 +93,7 @@ class MultiMatchFn(MatchFn[P, T]):
93
93
  children: ta.Sequence[MatchFn[P, T]]
94
94
  strict: bool = False
95
95
 
96
- def _match(self, *args: P.args, **kwargs: P.kwargs) -> MatchFn[P, T] | None:
96
+ def match(self, *args: P.args, **kwargs: P.kwargs) -> MatchFn[P, T] | None:
97
97
  matches = []
98
98
  for cur in self.children:
99
99
  if cur.guard(*args, **kwargs):
@@ -109,10 +109,10 @@ class MultiMatchFn(MatchFn[P, T]):
109
109
  return matches[0]
110
110
 
111
111
  def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
112
- return self._match(*args, **kwargs) is not None
112
+ return self.match(*args, **kwargs) is not None
113
113
 
114
114
  def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
115
- if (m := self._match(*args, **kwargs)) is None:
115
+ if (m := self.match(*args, **kwargs)) is None:
116
116
  raise MatchGuardError(*args, **kwargs)
117
117
  return m.fn(*args, **kwargs)
118
118
 
@@ -37,12 +37,12 @@ class Keywords(lang.Final):
37
37
  @cached.property
38
38
  @dc.init
39
39
  def by_type(self) -> ta.Mapping[type[Keyword], Keyword]:
40
- return col.unique_map_by(type, self.lst, strict=True) # noqa
40
+ return col.make_map_by(type, self.lst, strict=True) # noqa
41
41
 
42
42
  @cached.property
43
43
  @dc.init
44
44
  def by_tag(self) -> ta.Mapping[str, Keyword]:
45
- return col.unique_map_by(operator.attrgetter('tag'), self.lst, strict=True) # noqa
45
+ return col.make_map_by(operator.attrgetter('tag'), self.lst, strict=True) # noqa
46
46
 
47
47
  def __getitem__(self, item: type[KeywordT] | str) -> KeywordT:
48
48
  if isinstance(item, type):
@@ -24,7 +24,7 @@ KeywordT = ta.TypeVar('KeywordT', bound=Keyword)
24
24
  ##
25
25
 
26
26
 
27
- KEYWORD_TYPES_BY_TAG: ta.Mapping[str, type[Keyword]] = col.unique_map_by( # noqa
27
+ KEYWORD_TYPES_BY_TAG: ta.Mapping[str, type[Keyword]] = col.make_map_by( # noqa
28
28
  operator.attrgetter('tag'),
29
29
  (cls for cls in lang.deep_subclasses(Keyword) if not lang.is_abstract_class(cls)),
30
30
  strict=True,
omlish/sql/__init__.py CHANGED
@@ -7,3 +7,21 @@ from .asyncs import ( # noqa
7
7
  AsyncTransactionLike,
8
8
  async_adapt,
9
9
  )
10
+
11
+ from .dbs import ( # noqa
12
+ DbLoc,
13
+ DbSpec,
14
+ DbType,
15
+ DbTypes,
16
+ HostDbLoc,
17
+ UrlDbLoc,
18
+ )
19
+
20
+ from .exprs import ( # noqa
21
+ paren,
22
+ )
23
+
24
+ from .qualifiedname import ( # noqa
25
+ QualifiedName,
26
+ qn,
27
+ )
@@ -0,0 +1,82 @@
1
+ import collections.abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class QualifiedName(ta.Sequence[str]):
8
+ parts: ta.Sequence[str]
9
+
10
+ def __post_init__(self) -> None:
11
+ if not (
12
+ self.parts and
13
+ not isinstance(self.parts, str) and
14
+ all(self.parts) and
15
+ all(isinstance(p, str) for p in self.parts)
16
+ ):
17
+ raise ValueError(self)
18
+
19
+ def __repr__(self) -> str:
20
+ return f'{self.__class__.__name__}([{", ".join(map(repr, self.parts))}])'
21
+
22
+ @property
23
+ def dotted(self) -> str:
24
+ return '.'.join(self.parts)
25
+
26
+ def prefixed(self, sz: int) -> tuple[str | None, ...]:
27
+ if len(self) > sz:
28
+ raise ValueError(self)
29
+ return ((None,) * (sz - len(self))) + tuple(self.parts)
30
+
31
+ @property
32
+ def pair(self) -> tuple[str | None, str]:
33
+ return self.prefixed(2) # type: ignore
34
+
35
+ @property
36
+ def triple(self) -> tuple[str | None, str | None, str]:
37
+ return self.prefixed(3) # type: ignore
38
+
39
+ @property
40
+ def quad(self) -> tuple[str | None, str | None, str | None, str]:
41
+ return self.prefixed(4) # type: ignore
42
+
43
+ def __iter__(self) -> ta.Iterator[str]:
44
+ return iter(self.parts)
45
+
46
+ def __len__(self) -> int:
47
+ return len(self.parts)
48
+
49
+ def __getitem__(self, idx: int) -> str: # type: ignore
50
+ return self.parts[idx]
51
+
52
+ @classmethod
53
+ def of_dotted(cls, dotted: str) -> 'QualifiedName':
54
+ return cls(dotted.split('.'))
55
+
56
+ @classmethod
57
+ def of(
58
+ cls,
59
+ obj: ta.Union['QualifiedName', ta.Iterable[str]],
60
+ ) -> 'QualifiedName':
61
+ if isinstance(obj, QualifiedName):
62
+ return obj
63
+ elif isinstance(obj, str):
64
+ raise TypeError(obj)
65
+ elif isinstance(obj, collections.abc.Iterable):
66
+ return cls(list(obj))
67
+ else:
68
+ raise TypeError(obj)
69
+
70
+ @classmethod
71
+ def of_optional(
72
+ cls,
73
+ obj: ta.Union['QualifiedName', ta.Iterable[str], None],
74
+ ) -> ta.Optional['QualifiedName']:
75
+ if obj is None:
76
+ return None
77
+ else:
78
+ return cls.of(obj)
79
+
80
+
81
+ def qn(*args: str) -> QualifiedName:
82
+ return QualifiedName(args)
omlish/stats.py CHANGED
@@ -2,6 +2,7 @@
2
2
  TODO:
3
3
  - reservoir
4
4
  - dep tdigest?
5
+ - struct-of-arrays backed SamplingHistogram
5
6
  """
6
7
  import bisect
7
8
  import collections
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev22
3
+ Version: 0.0.0.dev24
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause