omlish 0.0.0.dev237__py3-none-any.whl → 0.0.0.dev239__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 (37) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/collections/__init__.py +5 -5
  3. omlish/collections/ranked.py +79 -0
  4. omlish/configs/classes.py +4 -0
  5. omlish/daemons/services.py +74 -0
  6. omlish/daemons/targets.py +10 -22
  7. omlish/dataclasses/__init__.py +6 -2
  8. omlish/dataclasses/static.py +189 -0
  9. omlish/dataclasses/utils.py +0 -9
  10. omlish/graphs/trees.py +8 -8
  11. omlish/lang/__init__.py +6 -3
  12. omlish/lang/descriptors.py +8 -7
  13. omlish/lang/imports.py +0 -43
  14. omlish/lite/dataclasses.py +18 -0
  15. omlish/lite/imports.py +47 -0
  16. omlish/manifests/__init__.py +0 -2
  17. omlish/manifests/base.py +1 -0
  18. omlish/manifests/load.py +1 -0
  19. omlish/manifests/static.py +20 -0
  20. omlish/manifests/types.py +2 -1
  21. omlish/metadata.py +153 -0
  22. omlish/sql/abc.py +7 -0
  23. omlish/sql/api/__init__.py +0 -0
  24. omlish/sql/api/base.py +89 -0
  25. omlish/sql/api/columns.py +90 -0
  26. omlish/sql/api/dbapi.py +105 -0
  27. omlish/sql/api/errors.py +24 -0
  28. omlish/sql/api/funcs.py +73 -0
  29. omlish/sql/api/queries.py +63 -0
  30. omlish/sql/api/rows.py +48 -0
  31. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/METADATA +1 -1
  32. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/RECORD +36 -24
  33. omlish/collections/indexed.py +0 -73
  34. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/LICENSE +0 -0
  35. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/WHEEL +0 -0
  36. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/entry_points.txt +0 -0
  37. {omlish-0.0.0.dev237.dist-info → omlish-0.0.0.dev239.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev237'
2
- __revision__ = 'e8c58292f1ec88727fd832f6496545435d992e9d'
1
+ __version__ = '0.0.0.dev239'
2
+ __revision__ = 'f241f36977bf74d1ea680d415782228917ec3669'
3
3
 
4
4
 
5
5
  #
@@ -57,11 +57,6 @@ from .identity import ( # noqa
57
57
  IdentityWeakSet,
58
58
  )
59
59
 
60
- from .indexed import ( # noqa
61
- IndexedSeq,
62
- IndexedSetSeq,
63
- )
64
-
65
60
  from .mappings import ( # noqa
66
61
  MissingDict,
67
62
  TypeMap,
@@ -91,6 +86,11 @@ else:
91
86
  'new_treap_map',
92
87
  ])
93
88
 
89
+ from .ranked import ( # noqa
90
+ RankedSeq,
91
+ RankedSetSeq,
92
+ )
93
+
94
94
  if _ta.TYPE_CHECKING:
95
95
  from .sorted.skiplist import ( # noqa
96
96
  SkipList,
@@ -0,0 +1,79 @@
1
+ import typing as ta
2
+
3
+ from .identity import IdentityKeyDict
4
+ from .identity import IdentitySet
5
+
6
+
7
+ T = ta.TypeVar('T')
8
+
9
+
10
+ ##
11
+
12
+
13
+ class RankedSeq(ta.Sequence[T]):
14
+ def __init__(self, it: ta.Iterable[T], *, identity: bool = False) -> None:
15
+ super().__init__()
16
+
17
+ self._lst = list(it)
18
+ self._ranks: ta.Mapping[T, int] = (IdentityKeyDict if identity else dict)((e, i) for i, e in enumerate(self._lst)) # noqa
19
+ if len(self._ranks) != len(self._lst):
20
+ raise ValueError(f'{len(self._ranks)} != {len(self._lst)}')
21
+
22
+ @property
23
+ def debug(self) -> ta.Sequence[T]:
24
+ return self._lst
25
+
26
+ def __iter__(self) -> ta.Iterator[T]:
27
+ return iter(self._lst)
28
+
29
+ def __getitem__(self, rank: int) -> T: # type: ignore
30
+ return self._lst[rank]
31
+
32
+ def __len__(self) -> int:
33
+ return len(self._lst)
34
+
35
+ def __contains__(self, obj: T) -> bool: # type: ignore
36
+ return obj in self._ranks
37
+
38
+ @property
39
+ def ranks(self) -> ta.Mapping[T, int]:
40
+ return self._ranks
41
+
42
+ def rank(self, obj: T) -> int:
43
+ return self._ranks[obj]
44
+
45
+
46
+ ##
47
+
48
+
49
+ class RankedSetSeq(ta.Sequence[ta.AbstractSet[T]]):
50
+ def __init__(self, it: ta.Iterable[ta.Iterable[T]], *, identity: bool = False) -> None:
51
+ super().__init__()
52
+
53
+ self._lst = [(IdentitySet if identity else set)(e) for e in it]
54
+ self._ranks: ta.Mapping[T, int] = (IdentityKeyDict if identity else dict)((e, i) for i, es in enumerate(self._lst) for e in es) # noqa
55
+ if len(self._ranks) != sum(map(len, self._lst)):
56
+ raise ValueError(f'{len(self._ranks)} != {sum(map(len, self._lst))}')
57
+
58
+ @property
59
+ def debug(self) -> ta.Sequence[ta.AbstractSet[T]]:
60
+ return self._lst
61
+
62
+ def __iter__(self) -> ta.Iterator[ta.AbstractSet[T]]:
63
+ return iter(self._lst)
64
+
65
+ def __getitem__(self, rank: int) -> ta.AbstractSet[T]: # type: ignore
66
+ return self._lst[rank]
67
+
68
+ def __len__(self) -> int:
69
+ return len(self._lst)
70
+
71
+ def __contains__(self, obj: T) -> bool: # type: ignore
72
+ return obj in self._ranks
73
+
74
+ @property
75
+ def ranks(self) -> ta.Mapping[T, int]:
76
+ return self._ranks
77
+
78
+ def rank(self, obj: T) -> int:
79
+ return self._ranks[obj]
omlish/configs/classes.py CHANGED
@@ -29,3 +29,7 @@ class Configurable(ta.Generic[ConfigurableConfigT], lang.Abstract):
29
29
  super().__init__()
30
30
 
31
31
  self._config: ConfigurableConfigT = check.isinstance(config, self.Config) # type: ignore[assignment]
32
+
33
+ @property
34
+ def config(self) -> ConfigurableConfigT:
35
+ return self._config
@@ -1,9 +1,15 @@
1
1
  import abc
2
2
  import typing as ta
3
3
 
4
+ from .. import cached
4
5
  from .. import check
6
+ from .. import dataclasses as dc
5
7
  from .. import lang
6
8
  from ..configs.classes import Configurable
9
+ from .daemon import Daemon
10
+ from .targets import Target
11
+ from .targets import TargetRunner
12
+ from .targets import target_runner_for
7
13
 
8
14
 
9
15
  ServiceConfigT = ta.TypeVar('ServiceConfigT', bound='Service.Config')
@@ -32,3 +38,71 @@ class Service(Configurable[ServiceConfigT], lang.Abstract):
32
38
  @classmethod
33
39
  def run_config(cls, config: Config) -> None:
34
40
  return cls.from_config(config).run()
41
+
42
+
43
+ ##
44
+
45
+
46
+ class ServiceTarget(Target):
47
+ svc: Service
48
+
49
+
50
+ class ServiceTargetRunner(TargetRunner, dc.Frozen):
51
+ target: ServiceTarget
52
+
53
+ def run(self) -> None:
54
+ self.target.svc.run()
55
+
56
+
57
+ @target_runner_for.register
58
+ def _(target: ServiceTarget) -> ServiceTargetRunner:
59
+ return ServiceTargetRunner(target)
60
+
61
+
62
+ #
63
+
64
+
65
+ class ServiceConfigTarget(Target):
66
+ cfg: Service.Config
67
+
68
+
69
+ class ServiceConfigTargetRunner(TargetRunner, dc.Frozen):
70
+ target: ServiceConfigTarget
71
+
72
+ def run(self) -> None:
73
+ Service.run_config(self.target.cfg)
74
+
75
+
76
+ @target_runner_for.register
77
+ def _(target: ServiceConfigTarget) -> ServiceConfigTargetRunner:
78
+ return ServiceConfigTargetRunner(target)
79
+
80
+
81
+ ##
82
+
83
+
84
+ @dc.dataclass(frozen=True)
85
+ class ServiceDaemon(lang.Final):
86
+ service: Service | Service.Config
87
+
88
+ @cached.function
89
+ def service_(self) -> Service:
90
+ if isinstance(self.service, Service):
91
+ return self.service
92
+ elif isinstance(self.service, Service.Config):
93
+ return Service.from_config(self.service)
94
+ else:
95
+ raise TypeError(self.service)
96
+
97
+ #
98
+
99
+ daemon: Daemon | Daemon.Config = Daemon.Config()
100
+
101
+ @cached.function
102
+ def daemon_(self) -> Daemon:
103
+ if isinstance(self.daemon, Daemon):
104
+ return self.daemon
105
+ elif isinstance(self.daemon, Daemon.Config):
106
+ return Daemon(Target.of(self.service_()), self.daemon)
107
+ else:
108
+ raise TypeError(self.daemon)
omlish/daemons/targets.py CHANGED
@@ -6,14 +6,18 @@ import typing as ta
6
6
  from .. import check
7
7
  from .. import dataclasses as dc
8
8
  from .. import lang
9
- from .services import Service
10
9
 
11
10
 
12
11
  if ta.TYPE_CHECKING:
13
12
  import runpy
13
+
14
+ from . import services
15
+
14
16
  else:
15
17
  runpy = lang.proxy_import('runpy')
16
18
 
19
+ services = lang.proxy_import('.services', __package__)
20
+
17
21
 
18
22
  ##
19
23
 
@@ -30,8 +34,11 @@ class Target(dc.Case):
30
34
  elif callable(obj):
31
35
  return FnTarget(obj)
32
36
 
33
- elif isinstance(obj, Service.Config):
34
- return ServiceConfigTarget(obj)
37
+ elif isinstance(obj, services.Service):
38
+ return services.ServiceTarget(obj)
39
+
40
+ elif isinstance(obj, services.Service.Config):
41
+ return services.ServiceConfigTarget(obj)
35
42
 
36
43
  else:
37
44
  raise TypeError(obj)
@@ -136,22 +143,3 @@ class ExecTargetRunner(TargetRunner, dc.Frozen):
136
143
  @target_runner_for.register
137
144
  def _(target: ExecTarget) -> ExecTargetRunner:
138
145
  return ExecTargetRunner(target)
139
-
140
-
141
- ##
142
-
143
-
144
- class ServiceConfigTarget(Target):
145
- cfg: Service.Config
146
-
147
-
148
- class ServiceConfigTargetRunner(TargetRunner, dc.Frozen):
149
- target: ServiceConfigTarget
150
-
151
- def run(self) -> None:
152
- Service.run_config(self.target.cfg)
153
-
154
-
155
- @target_runner_for.register
156
- def _(target: ServiceConfigTarget) -> ServiceConfigTargetRunner:
157
- return ServiceConfigTargetRunner(target)
@@ -91,9 +91,11 @@ from .impl.reflect import ( # noqa
91
91
  reflect,
92
92
  )
93
93
 
94
- from .utils import ( # noqa
95
- is_immediate_dataclass,
94
+ from .static import ( # noqa
95
+ Static,
96
+ )
96
97
 
98
+ from .utils import ( # noqa
97
99
  opt_repr,
98
100
  truthy_repr,
99
101
 
@@ -116,5 +118,7 @@ from .utils import ( # noqa
116
118
  ##
117
119
 
118
120
  from ..lite.dataclasses import ( # noqa
121
+ is_immediate_dataclass,
122
+
119
123
  dataclass_maybe_post_init as maybe_post_init,
120
124
  )
@@ -0,0 +1,189 @@
1
+ """
2
+ TODO:
3
+ - metaclass, to forbid __subclasscheck__ / __instancecheck__? Clash with dc.Meta, don't want that lock-in, not
4
+ necessary for functionality, just a helpful misuse prevention.
5
+ """
6
+ import abc
7
+ import copy
8
+ import dataclasses as dc
9
+ import typing as ta
10
+
11
+ from .. import lang
12
+ from ..lite.dataclasses import is_immediate_dataclass
13
+ from .impl.api import dataclass
14
+
15
+
16
+ ##
17
+
18
+
19
+ class Static(lang.Abstract):
20
+ """
21
+ Dataclass mixin for dataclasses in which all fields have class-level defaults and are not intended for any
22
+ instance-level overrides - effectively making their subclasses equivalent to instances. For dataclasses which make
23
+ sense as singletons (such as project specs or manifests), for which dynamic instantiation is not the usecase, this
24
+ can enable more natural syntax. Inheritance is permitted - equivalent to a `functools.partial` or composing
25
+ `**kwargs`, as long as there is only ever one unambiguous underlying non-static dataclass to be instantiated.
26
+ """
27
+
28
+ __static_dataclass_class__: ta.ClassVar[type]
29
+ __static_dataclass_instance__: ta.ClassVar[ta.Any]
30
+
31
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
32
+ super().__init_subclass__(**kwargs)
33
+
34
+ for a in ('__new__', '__init__'):
35
+ if a in cls.__dict__ and not (cls.__dict__[a] == getattr(Static, a)):
36
+ raise TypeError(f'Static base class {cls} must not implement {a}')
37
+
38
+ if cls.__init__ is not Static.__init__:
39
+ # This is necessary to make type-checking work (by allowing it to accept zero args). This isn't strictly
40
+ # necessary, but since it's useful to do sometimes it might as well be done everywhere to prevent clashing.
41
+ raise TypeError(f'Static.__init__ should be first in mro of {cls}')
42
+
43
+ #
44
+
45
+ b_dc_lst = []
46
+
47
+ def b_dc_rec(b_cls: type) -> None: # noqa
48
+ if issubclass(b_cls, Static):
49
+ return
50
+ elif is_immediate_dataclass(b_cls):
51
+ b_dc_lst.append(b_cls)
52
+ elif dc.is_dataclass(b_cls):
53
+ for sb_cls in b_cls.__bases__:
54
+ b_dc_rec(sb_cls)
55
+
56
+ for b_cls in cls.__bases__:
57
+ b_dc_rec(b_cls)
58
+
59
+ b_sdc_dc_lst = [
60
+ b_cls.__static_dataclass_class__
61
+ for b_cls in cls.__bases__
62
+ if issubclass(b_cls, Static)
63
+ and b_cls is not Static
64
+ ]
65
+
66
+ sdc_dc_set = {*b_dc_lst, *b_sdc_dc_lst}
67
+ if not sdc_dc_set:
68
+ raise TypeError(f'Static dataclass {cls} inherits from no dataclass')
69
+
70
+ # Base static dataclasses of various types are allowed as long as there is exactly one final subclass involved.
71
+ # For example, a ModAttrManifest dataclass with an abstract StaticModAttrManifest subclass which sets a default
72
+ # mod_name may be mixed in with a further down use-case specific Manifest subclass.
73
+ sdc_dc_set -= {
74
+ m_cls
75
+ for s_cls in sdc_dc_set
76
+ for m_cls in s_cls.__mro__
77
+ if m_cls is not s_cls
78
+ }
79
+
80
+ if len(sdc_dc_set) > 1:
81
+ raise TypeError(f'Static dataclass {cls} inherits from multiple dataclasses: {sdc_dc_set!r}')
82
+ [sdc_cls] = sdc_dc_set
83
+
84
+ if '__static_dataclass_class__' in cls.__dict__:
85
+ raise AttributeError
86
+ setattr(cls, '__static_dataclass_class__', sdc_cls)
87
+
88
+ #
89
+
90
+ expected_fld_order: ta.Sequence[str] | None = None
91
+ is_abstract = lang.is_abstract_class(cls) or abc.ABC in cls.__bases__
92
+ if not is_abstract:
93
+ if is_immediate_dataclass(cls):
94
+ raise TypeError(cls)
95
+
96
+ flds_dct = {}
97
+ for b_cls in cls.__mro__:
98
+ if not dc.is_dataclass(b_cls):
99
+ continue
100
+ b_flds = dc.fields(b_cls) # noqa
101
+ for fld in b_flds:
102
+ if fld.name in flds_dct:
103
+ continue
104
+ flds_dct[fld.name] = fld
105
+
106
+ # Annotations are the authoritative source of field order.
107
+ new_anns = {}
108
+
109
+ for fld in flds_dct.values():
110
+ new_fld = copy.copy(fld)
111
+
112
+ try:
113
+ v = cls.__dict__[fld.name]
114
+ except KeyError:
115
+ if fld.default is dc.MISSING and fld.default_factory is dc.MISSING:
116
+ raise TypeError(f'Field {fld.name!r} of class {cls} is not set and has no default') from None
117
+ else:
118
+ if isinstance(v, dc.Field):
119
+ raise TypeError(f'Static dataclass {cls} may not introduce new fields: {fld.name}: {v}')
120
+
121
+ new_fld.default = dc.MISSING
122
+ # Use a default_factory to allow unsafe (mutable) values.
123
+ new_fld.default_factory = (lambda v2: lambda: v2)(v) # noqa
124
+
125
+ setattr(cls, fld.name, new_fld)
126
+ new_anns[fld.name] = fld.type
127
+
128
+ # Non-abstract static dataclasses may not introduce new fields.
129
+ expected_fld_order = list(flds_dct)
130
+
131
+ new_anns.update({
132
+ k: v
133
+ for k, v in getattr(cls, '__annotations__', {}).items()
134
+ if k not in new_anns
135
+ })
136
+
137
+ cls.__annotations__ = new_anns
138
+
139
+ else:
140
+ for b_cls in cls.__bases__:
141
+ if hasattr(b_cls, '__static_dataclass_instance__'):
142
+ raise TypeError(
143
+ f'Abstract static dataclass {cls} may not inherit from non-abstract static dataclass {b_cls}',
144
+ )
145
+
146
+ # Explicitly forbid dc transforms that rebuild the class, such as slots.
147
+ if (dc_cls := dataclass(cls, frozen=True)) is not cls:
148
+ raise TypeError(dc_cls)
149
+
150
+ dc_flds = dc.fields(cls) # type: ignore[arg-type] # noqa
151
+
152
+ if expected_fld_order is not None:
153
+ dc_fld_order = [f.name for f in dc_flds]
154
+ if dc_fld_order != expected_fld_order:
155
+ raise TypeError(
156
+ f'Static dataclass field order {dc_fld_order!r} != expected field order {expected_fld_order!r}',
157
+ )
158
+
159
+ if not is_abstract:
160
+ # This is the only time the Statics are ever actually instantiated, and it's only to produce the kwargs
161
+ # passed to the underlying dataclass.
162
+ tmp_inst = cls()
163
+ inst_kw = dc.asdict(tmp_inst) # type: ignore[call-overload] # noqa
164
+ inst = sdc_cls(**inst_kw)
165
+
166
+ cls.__static_dataclass_instance__ = inst
167
+
168
+ # Make all field values available via static `Class.field` access, even those created via a factory. Note
169
+ # that further inheritance of this non-abstract Static will continue to inherit dc.Field instances
170
+ # (including things like their metadata) via dc.fields() access, which does not reference `cls.__dict__`.
171
+ for fld in dc_flds:
172
+ v = getattr(inst, fld.name)
173
+ setattr(cls, fld.name, v)
174
+
175
+ def __new__(new_cls, *new_args, **new_kwargs): # noqa
176
+ try:
177
+ return new_cls.__dict__['__static_dataclass_instance__']
178
+ except KeyError:
179
+ return super().__new__(new_cls)
180
+
181
+ cls.__new__ = __new__ # type: ignore
182
+
183
+ cls.__init__ = Static.__init__ # type: ignore
184
+
185
+ @ta.final
186
+ def __init__(self) -> None:
187
+ # This stub serves to allow `StaticSubclass()` to typecheck by allowing it to accept only zero arguments. Note
188
+ # that this is only the case when `Static` is first in mro.
189
+ raise TypeError('May not instantiate static dataclasses')
@@ -4,7 +4,6 @@ import types
4
4
  import typing as ta
5
5
 
6
6
  from .. import check
7
- from .impl.internals import FIELDS_ATTR
8
7
  from .impl.metadata import METADATA_ATTR
9
8
  from .impl.metadata import UserMetadata
10
9
  from .impl.params import DEFAULT_FIELD_EXTRAS
@@ -18,14 +17,6 @@ T = ta.TypeVar('T')
18
17
  ##
19
18
 
20
19
 
21
- def is_immediate_dataclass(cls: type) -> bool:
22
- check.isinstance(cls, type)
23
- return FIELDS_ATTR in cls.__dict__
24
-
25
-
26
- ##
27
-
28
-
29
20
  def opt_repr(o: ta.Any) -> str | None:
30
21
  return repr(o) if o is not None else None
31
22
 
omlish/graphs/trees.py CHANGED
@@ -56,7 +56,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
56
56
 
57
57
  self._set_fac: ta.Callable[..., ta.MutableSet[NodeT]] = col.IdentitySet if identity else set
58
58
  self._dict_fac: ta.Callable[..., ta.MutableMapping[NodeT, ta.Any]] = col.IdentityKeyDict if identity else dict
59
- self._idx_seq_fac: ta.Callable[..., col.IndexedSeq[NodeT]] = functools.partial(col.IndexedSeq, identity=identity) # type: ignore # noqa
59
+ self._rank_seq_fac: ta.Callable[..., col.RankedSeq[NodeT]] = functools.partial(col.RankedSeq, identity=identity) # type: ignore # noqa
60
60
 
61
61
  def walk(cur: NodeT, parent: NodeT | None) -> None:
62
62
  check.not_none(cur)
@@ -88,10 +88,10 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
88
88
 
89
89
  walk(root, None)
90
90
 
91
- self._nodes = self._idx_seq_fac(nodes)
91
+ self._nodes = self._rank_seq_fac(nodes)
92
92
  self._node_set: ta.AbstractSet[NodeT] = node_set
93
- self._children_by_node: ta.Mapping[NodeT | None, col.IndexedSeq[NodeT]] = self._dict_fac(
94
- [(n, self._idx_seq_fac(cs)) for n, cs in children_by_node.items()])
93
+ self._children_by_node: ta.Mapping[NodeT | None, col.RankedSeq[NodeT]] = self._dict_fac(
94
+ [(n, self._rank_seq_fac(cs)) for n, cs in children_by_node.items()])
95
95
  self._child_sets_by_node: ta.Mapping[NodeT | None, ta.AbstractSet[NodeT]] = child_sets_by_node
96
96
  self._parents_by_node: ta.Mapping[NodeT, NodeT | None] = parents_by_node
97
97
 
@@ -100,7 +100,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
100
100
  return self._root
101
101
 
102
102
  @property
103
- def nodes(self) -> col.IndexedSeq[NodeT]:
103
+ def nodes(self) -> col.RankedSeq[NodeT]:
104
104
  return self._nodes
105
105
 
106
106
  @property
@@ -116,7 +116,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
116
116
  return self._node_set
117
117
 
118
118
  @property
119
- def children_by_node(self) -> ta.Mapping[NodeT | None, col.IndexedSeq[NodeT]]:
119
+ def children_by_node(self) -> ta.Mapping[NodeT | None, col.RankedSeq[NodeT]]:
120
120
  return self._children_by_node
121
121
 
122
122
  @property
@@ -224,8 +224,8 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
224
224
  break
225
225
  yield cur
226
226
 
227
- def get_lineage(self, node: NodeT) -> col.IndexedSeq[NodeT]:
228
- return self._idx_seq_fac(reversed([node, *self.iter_ancestors(node)]))
227
+ def get_lineage(self, node: NodeT) -> col.RankedSeq[NodeT]:
228
+ return self._rank_seq_fac(reversed([node, *self.iter_ancestors(node)]))
229
229
 
230
230
  def get_first_parent_of_type(self, node: NodeT, ty: type[T]) -> T | None:
231
231
  for cur in self.iter_ancestors(node):
omlish/lang/__init__.py CHANGED
@@ -137,9 +137,6 @@ from .imports import ( # noqa
137
137
  can_import,
138
138
  get_real_module_name,
139
139
  import_all,
140
- import_attr,
141
- import_module,
142
- import_module_attr,
143
140
  lazy_import,
144
141
  proxy_import,
145
142
  proxy_init,
@@ -235,6 +232,12 @@ from .typing import ( # noqa
235
232
 
236
233
  ##
237
234
 
235
+ from ..lite.imports import ( # noqa
236
+ import_attr,
237
+ import_module,
238
+ import_module_attr,
239
+ )
240
+
238
241
  from ..lite.timeouts import ( # noqa
239
242
  DeadlineTimeout,
240
243
  InfiniteTimeout,
@@ -48,25 +48,26 @@ def _has_method_descriptor(obj: ta.Any) -> bool:
48
48
  return False
49
49
 
50
50
 
51
- def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
51
+ def unwrap_method_descriptors(fn: ta.Any) -> ta.Any:
52
52
  while is_method_descriptor(fn):
53
- fn = fn.__func__ # type: ignore # noqa
53
+ fn = fn.__func__
54
54
  return fn
55
55
 
56
56
 
57
57
  ##
58
58
 
59
59
 
60
- def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
60
+ def unwrap_func_with_partials(fn: ta.Any) -> tuple[ta.Any, list[functools.partial]]:
61
61
  ps = []
62
62
  while True:
63
63
  if is_method_descriptor(fn) or isinstance(fn, types.MethodType):
64
- fn = fn.__func__ # type: ignore
64
+ fn = fn.__func__
65
65
 
66
66
  elif hasattr(fn, '__wrapped__'):
67
67
  nxt = fn.__wrapped__
68
- if not callable(nxt):
69
- raise TypeError(nxt)
68
+ # FIXME: ?
69
+ # if not callable(nxt):
70
+ # raise TypeError(nxt)
70
71
  if nxt is fn:
71
72
  raise TypeError(fn)
72
73
  fn = nxt
@@ -83,7 +84,7 @@ def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functo
83
84
  return fn, ps
84
85
 
85
86
 
86
- def unwrap_func(fn: ta.Callable) -> ta.Callable:
87
+ def unwrap_func(fn: ta.Any) -> ta.Any:
87
88
  uw, _ = unwrap_func_with_partials(fn)
88
89
  return uw
89
90
 
omlish/lang/imports.py CHANGED
@@ -87,49 +87,6 @@ def proxy_import(
87
87
  ##
88
88
 
89
89
 
90
- def import_module(dotted_path: str) -> types.ModuleType:
91
- if not dotted_path:
92
- raise ImportError(dotted_path)
93
- mod = __import__(dotted_path, globals(), locals(), [])
94
- for name in dotted_path.split('.')[1:]:
95
- try:
96
- mod = getattr(mod, name)
97
- except AttributeError:
98
- raise AttributeError(f'Module {mod!r} has no attribute {name!r}') from None
99
- return mod
100
-
101
-
102
- def import_module_attr(dotted_path: str) -> ta.Any:
103
- module_name, _, class_name = dotted_path.rpartition('.')
104
- mod = import_module(module_name)
105
- try:
106
- return getattr(mod, class_name)
107
- except AttributeError:
108
- raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
109
-
110
-
111
- def import_attr(dotted_path: str) -> ta.Any:
112
- parts = dotted_path.split('.')
113
- mod: ta.Any = None
114
- mod_pos = 0
115
- while mod_pos < len(parts):
116
- mod_name = '.'.join(parts[:mod_pos + 1])
117
- try:
118
- mod = importlib.import_module(mod_name)
119
- except ImportError:
120
- break
121
- mod_pos += 1
122
- if mod is None:
123
- raise ImportError(dotted_path)
124
- obj = mod
125
- for att_pos in range(mod_pos, len(parts)):
126
- obj = getattr(obj, parts[att_pos])
127
- return obj
128
-
129
-
130
- ##
131
-
132
-
133
90
  SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
134
91
  '__init__.py',
135
92
  '__main__.py',