omlish 0.0.0.dev23__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.
omlish/genmachine.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """
2
- https://github.com/pytransitions/transitions
2
+ See:
3
+ - https://github.com/pytransitions/transitions
3
4
  """
4
5
  import typing as ta
5
6
 
omlish/graphs/trees.py CHANGED
@@ -190,7 +190,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
190
190
  e: ta.Any
191
191
  d: ta.Any
192
192
  if identity:
193
- e, d = id, col.unique_map(((id(n), n) for n, _ in pairs), strict=True)
193
+ e, d = id, col.make_map(((id(n), n) for n, _ in pairs), strict=True)
194
194
  else:
195
195
  e, d = lang.identity, lang.identity
196
196
  tsd = {e(n): {e(p)} for n, p in parents_by_node.items()}
omlish/lang/__init__.py CHANGED
@@ -79,7 +79,7 @@ from .descriptors import ( # noqa
79
79
  unwrap_func,
80
80
  unwrap_func_with_partials,
81
81
  unwrap_method_descriptors,
82
- update_wrapper_except_dict,
82
+ update_wrapper,
83
83
  )
84
84
 
85
85
  from .exceptions import ( # noqa
@@ -154,6 +154,18 @@ from .objects import ( # noqa
154
154
  super_meta,
155
155
  )
156
156
 
157
+ from .resolving import ( # noqa
158
+ Resolvable,
159
+ ResolvableClassNameError,
160
+ get_cls_fqcn,
161
+ get_fqcn_cls,
162
+ )
163
+
164
+ from .resources import ( # noqa
165
+ RelativeResource,
166
+ get_relative_resources,
167
+ )
168
+
157
169
  from .strings import ( # noqa
158
170
  BOOL_FALSE_STRINGS,
159
171
  BOOL_STRINGS,
omlish/lang/cached.py CHANGED
@@ -220,7 +220,7 @@ def cached_function(fn=None, **kwargs): # noqa
220
220
  ##
221
221
 
222
222
 
223
- class _CachedProperty:
223
+ class _CachedProperty(property):
224
224
  def __init__(
225
225
  self,
226
226
  fn,
@@ -229,9 +229,9 @@ class _CachedProperty:
229
229
  ignore_if=lambda _: False,
230
230
  clear_on_init=False,
231
231
  ):
232
- super().__init__()
233
232
  if isinstance(fn, property):
234
233
  fn = fn.fget
234
+ super().__init__(fn)
235
235
  self._fn = fn
236
236
  self._ignore_if = ignore_if
237
237
  self._name = name
@@ -265,6 +265,9 @@ class _CachedProperty:
265
265
  return
266
266
  raise TypeError(self._name)
267
267
 
268
+ def __delete__(self, instance):
269
+ raise TypeError
270
+
268
271
 
269
272
  def cached_property(fn=None, **kwargs): # noqa
270
273
  if fn is None:
@@ -91,21 +91,38 @@ def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functo
91
91
  ##
92
92
 
93
93
 
94
- WRAPPER_UPDATES_EXCEPT_DICT = tuple(a for a in functools.WRAPPER_UPDATES if a != '__dict__')
94
+ def update_wrapper(
95
+ wrapper: T,
96
+ wrapped: ta.Any,
97
+ assigned: ta.Iterable[str] = functools.WRAPPER_ASSIGNMENTS,
98
+ updated: ta.Iterable[str] = functools.WRAPPER_UPDATES,
99
+ *,
100
+ filter: ta.Iterable[str] | None = None, # noqa
101
+ getattr: ta.Callable = getattr, # noqa
102
+ setattr: ta.Callable = setattr, # noqa
103
+ ) -> T:
104
+ if filter:
105
+ if isinstance(filter, str):
106
+ filter = [filter] # noqa
107
+ assigned = tuple(a for a in assigned if a not in filter)
108
+ updated = tuple(a for a in updated if a not in filter)
109
+
110
+ for attr in assigned:
111
+ try:
112
+ value = getattr(wrapped, attr)
113
+ except AttributeError:
114
+ pass
115
+ else:
116
+ setattr(wrapper, attr, value)
117
+
118
+ for attr in updated:
119
+ getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
95
120
 
121
+ # Issue #17482: set __wrapped__ last so we don't inadvertently copy it from the wrapped function when updating
122
+ # __dict__
123
+ setattr(wrapper, '__wrapped__', wrapped)
96
124
 
97
- def update_wrapper_except_dict(
98
- wrapper,
99
- wrapped,
100
- assigned=functools.WRAPPER_ASSIGNMENTS,
101
- updated=WRAPPER_UPDATES_EXCEPT_DICT,
102
- ):
103
- return functools.update_wrapper(
104
- wrapper,
105
- wrapped,
106
- assigned=assigned,
107
- updated=updated,
108
- )
125
+ return wrapper
109
126
 
110
127
 
111
128
  ##
@@ -118,7 +135,7 @@ class _decorator_descriptor: # noqa
118
135
  if not _DECORATOR_HANDLES_UNBOUND_METHODS:
119
136
  def __init__(self, wrapper, fn):
120
137
  self._wrapper, self._fn = wrapper, fn
121
- update_wrapper_except_dict(self, fn)
138
+ update_wrapper(self, fn, filter='__dict__')
122
139
 
123
140
  def __get__(self, instance, owner=None):
124
141
  return functools.update_wrapper(functools.partial(self._wrapper, fn := self._fn.__get__(instance, owner)), fn) # noqa
@@ -127,7 +144,7 @@ class _decorator_descriptor: # noqa
127
144
  def __init__(self, wrapper, fn):
128
145
  self._wrapper, self._fn = wrapper, fn
129
146
  self._md = _has_method_descriptor(fn)
130
- update_wrapper_except_dict(self, fn)
147
+ update_wrapper(self, fn, filter='__dict__')
131
148
 
132
149
  def __get__(self, instance, owner=None):
133
150
  fn = self._fn.__get__(instance, owner)
@@ -157,7 +174,7 @@ class _decorator_descriptor: # noqa
157
174
  class _decorator: # noqa
158
175
  def __init__(self, wrapper):
159
176
  self._wrapper = wrapper
160
- update_wrapper_except_dict(self, wrapper)
177
+ update_wrapper(self, wrapper, filter='__dict__')
161
178
 
162
179
  def __repr__(self):
163
180
  return f'{self.__class__.__name__}<{self._wrapper}>'
@@ -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
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
@@ -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/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.dev23
3
+ Version: 0.0.0.dev24
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause