omlish 0.0.0.dev236__py3-none-any.whl → 0.0.0.dev238__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.
@@ -0,0 +1,176 @@
1
+ import abc
2
+ import copy
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+ from .. import lang
7
+ from ..lite.dataclasses import is_immediate_dataclass
8
+ from .impl.api import dataclass
9
+
10
+
11
+ ##
12
+
13
+
14
+ class Static(lang.Abstract):
15
+ __static_dataclass_class__: ta.ClassVar[type]
16
+ __static_dataclass_instance__: ta.ClassVar[ta.Any]
17
+
18
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
19
+ super().__init_subclass__(**kwargs)
20
+
21
+ for a in ('__new__', '__init__'):
22
+ if a in cls.__dict__ and not (cls.__dict__[a] == getattr(Static, a)):
23
+ raise TypeError(f'Static base class {cls} must not implement {a}')
24
+
25
+ if cls.__init__ is not Static.__init__:
26
+ # This is necessary to make type-checking work (by allowing it to accept zero). This isn't strictly
27
+ # necessary, but since it's useful to do sometimes it might as well be done everywhere to prevent clashing.
28
+ raise TypeError(f'Static.__init__ should be first in mro of {cls}')
29
+
30
+ #
31
+
32
+ b_dc_lst = []
33
+
34
+ def b_dc_rec(b_cls: type) -> None: # noqa
35
+ if issubclass(b_cls, Static):
36
+ return
37
+ elif is_immediate_dataclass(b_cls):
38
+ b_dc_lst.append(b_cls)
39
+ elif dc.is_dataclass(b_cls):
40
+ for sb_cls in b_cls.__bases__:
41
+ b_dc_rec(sb_cls)
42
+
43
+ for b_cls in cls.__bases__:
44
+ b_dc_rec(b_cls)
45
+
46
+ b_sdc_dc_lst = [
47
+ b_cls.__static_dataclass_class__
48
+ for b_cls in cls.__bases__
49
+ if issubclass(b_cls, Static)
50
+ and b_cls is not Static
51
+ ]
52
+
53
+ sdc_dc_set = {*b_dc_lst, *b_sdc_dc_lst}
54
+ if not sdc_dc_set:
55
+ raise TypeError(f'Static dataclass {cls} inherits from no dataclass')
56
+
57
+ # Base static dataclasses of various types are allowed as long as there is exactly one final subclass involved.
58
+ # For example, a ModAttrManifest dataclass with an abstract StaticModAttrManifest subclass which sets a default
59
+ # mod_name may be mixed in with a further down use-case specific Manifest subclass.
60
+ sdc_dc_set -= {
61
+ m_cls
62
+ for s_cls in sdc_dc_set
63
+ for m_cls in s_cls.__mro__
64
+ if m_cls is not s_cls
65
+ }
66
+
67
+ if len(sdc_dc_set) > 1:
68
+ raise TypeError(f'Static dataclass {cls} inherits from multiple dataclasses: {sdc_dc_set!r}')
69
+ [sdc_cls] = sdc_dc_set
70
+
71
+ if '__static_dataclass_class__' in cls.__dict__:
72
+ raise AttributeError
73
+ setattr(cls, '__static_dataclass_class__', sdc_cls)
74
+
75
+ #
76
+
77
+ expected_fld_order: ta.Sequence[str] | None = None
78
+ is_abstract = lang.is_abstract_class(cls) or abc.ABC in cls.__bases__
79
+ if not is_abstract:
80
+ if is_immediate_dataclass(cls):
81
+ raise TypeError(cls)
82
+
83
+ flds_dct = {}
84
+ for b_cls in cls.__mro__:
85
+ if not dc.is_dataclass(b_cls):
86
+ continue
87
+ b_flds = dc.fields(b_cls) # noqa
88
+ for fld in b_flds:
89
+ if fld.name in flds_dct:
90
+ continue
91
+ flds_dct[fld.name] = fld
92
+
93
+ # Annotations are the authoritative source of field order.
94
+ new_anns = {}
95
+
96
+ for fld in flds_dct.values():
97
+ new_fld = copy.copy(fld)
98
+
99
+ try:
100
+ v = cls.__dict__[fld.name]
101
+ except KeyError:
102
+ if fld.default is dc.MISSING and fld.default_factory is dc.MISSING:
103
+ raise TypeError(f'Field {fld.name!r} of class {cls} is not set and has no default') from None
104
+ else:
105
+ if isinstance(v, dc.Field):
106
+ raise TypeError(f'Static dataclass {cls} may not introduce new fields: {fld.name}: {v}')
107
+
108
+ new_fld.default = dc.MISSING
109
+ # Use a default_factory to allow unsafe (mutable) values.
110
+ new_fld.default_factory = (lambda v2: lambda: v2)(v) # noqa
111
+
112
+ setattr(cls, fld.name, new_fld)
113
+ new_anns[fld.name] = fld.type
114
+
115
+ # Non-abstract static dataclasses may not introduce new fields.
116
+ expected_fld_order = list(flds_dct)
117
+
118
+ new_anns.update({
119
+ k: v
120
+ for k, v in getattr(cls, '__annotations__', {}).items()
121
+ if k not in new_anns
122
+ })
123
+
124
+ cls.__annotations__ = new_anns
125
+
126
+ else:
127
+ for b_cls in cls.__bases__:
128
+ if hasattr(b_cls, '__static_dataclass_instance__'):
129
+ raise TypeError(
130
+ f'Abstract static dataclass {cls} may not inherit from non-abstract static dataclass {b_cls}',
131
+ )
132
+
133
+ # Explicitly forbid dc transforms that rebuild the class, such as slots.
134
+ if (dc_cls := dataclass(cls, frozen=True)) is not cls:
135
+ raise TypeError(dc_cls)
136
+
137
+ dc_flds = dc.fields(cls) # type: ignore[arg-type] # noqa
138
+
139
+ if expected_fld_order is not None:
140
+ dc_fld_order = [f.name for f in dc_flds]
141
+ if dc_fld_order != expected_fld_order:
142
+ raise TypeError(
143
+ f'Static dataclass field order {dc_fld_order!r} != expected field order {expected_fld_order!r}',
144
+ )
145
+
146
+ if not is_abstract:
147
+ # This is the only time the Statices are ever actually instantiated, and it's only to produce the
148
+ # kwargs passed to the underlying dataclass.
149
+ tmp_inst = cls()
150
+ inst_kw = dc.asdict(tmp_inst) # type: ignore[call-overload] # noqa
151
+ inst = sdc_cls(**inst_kw)
152
+
153
+ cls.__static_dataclass_instance__ = inst
154
+
155
+ # Make all field values available via static `Class.field` access, even those created via a factory. Note
156
+ # that further inheritance of this non-abstract Static will continue to inherit dc.Field instances
157
+ # (including things like their metadata) via dc.fields() access, which does not reference `cls.__dict__`.
158
+ for fld in dc_flds:
159
+ v = getattr(inst, fld.name)
160
+ setattr(cls, fld.name, v)
161
+
162
+ def __new__(new_cls, *new_args, **new_kwargs): # noqa
163
+ try:
164
+ return new_cls.__dict__['__static_dataclass_instance__']
165
+ except KeyError:
166
+ return super().__new__(new_cls)
167
+
168
+ cls.__new__ = __new__ # type: ignore
169
+
170
+ cls.__init__ = Static.__init__ # type: ignore
171
+
172
+ @ta.final
173
+ def __init__(self) -> None:
174
+ # This stub serves to allow `StaticSubclass()` to typecheck by allowing it to accept only zero arguments. Note
175
+ # that this is only the case when `Static` is first in mro.
176
+ 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
@@ -135,6 +135,7 @@ from .generators import ( # noqa
135
135
 
136
136
  from .imports import ( # noqa
137
137
  can_import,
138
+ get_real_module_name,
138
139
  import_all,
139
140
  import_attr,
140
141
  import_module,
@@ -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
@@ -384,3 +384,18 @@ def proxy_init(
384
384
  raise Exception(f'Wrong init name: {pi.name_package=} != {init_name_package=}')
385
385
 
386
386
  pi.add(package, attrs)
387
+
388
+
389
+ ##
390
+
391
+
392
+ def get_real_module_name(globals: ta.Mapping[str, ta.Any]) -> str: # noqa
393
+ module = sys.modules[globals['__name__']]
394
+
395
+ if module.__spec__ and module.__spec__.name:
396
+ return module.__spec__.name
397
+
398
+ if module.__package__:
399
+ return module.__package__
400
+
401
+ raise RuntimeError("Can't determine real module name")
@@ -3,6 +3,18 @@ import dataclasses as dc
3
3
  import typing as ta
4
4
 
5
5
 
6
+ ##
7
+
8
+
9
+ def is_immediate_dataclass(cls: type) -> bool:
10
+ if not isinstance(cls, type):
11
+ raise TypeError(cls)
12
+ return dc._FIELDS in cls.__dict__ # type: ignore[attr-defined] # noqa
13
+
14
+
15
+ ##
16
+
17
+
6
18
  def dataclass_cache_hash(
7
19
  *,
8
20
  cached_hash_attr: str = '__dataclass_hash__',
@@ -33,6 +45,9 @@ def dataclass_cache_hash(
33
45
  return inner
34
46
 
35
47
 
48
+ ##
49
+
50
+
36
51
  def dataclass_maybe_post_init(sup: ta.Any) -> bool:
37
52
  if not isinstance(sup, super):
38
53
  raise TypeError(sup)
@@ -44,6 +59,9 @@ def dataclass_maybe_post_init(sup: ta.Any) -> bool:
44
59
  return True
45
60
 
46
61
 
62
+ ##
63
+
64
+
47
65
  def dataclass_repr_filtered(
48
66
  obj: ta.Any,
49
67
  fn: ta.Callable[[ta.Any, dc.Field, ta.Any], bool],
@@ -1,2 +0,0 @@
1
- # @omlish-lite
2
- from .types import Manifest # noqa
omlish/manifests/base.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
2
3
  import dataclasses as dc
3
4
  import typing as ta
4
5
 
omlish/manifests/load.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
2
3
  """
3
4
  Should be kept somewhat lightweight - used in cli entrypoints.
4
5
 
@@ -0,0 +1,20 @@
1
+ import abc
2
+ import typing as ta
3
+
4
+ from .. import dataclasses as dc
5
+ from .. import lang
6
+ from .base import ModAttrManifest
7
+
8
+
9
+ ##
10
+
11
+
12
+ class StaticModAttrManifest(dc.Static, ModAttrManifest, abc.ABC):
13
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
14
+ if (
15
+ not (lang.is_abstract_class(cls) or abc.ABC in cls.__bases__) and
16
+ 'mod_name' not in cls.__dict__
17
+ ):
18
+ setattr(cls, 'mod_name', cls.__module__)
19
+
20
+ super().__init_subclass__(**kwargs)
omlish/manifests/types.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
2
3
  import dataclasses as dc
3
4
  import typing as ta
4
5
 
omlish/metadata.py ADDED
@@ -0,0 +1,153 @@
1
+ """
2
+ These attempt to punch through *all* wrappers. The the usecases involve having things like bound methods, classmethods,
3
+ functools.partial instances, and needing to get the metadata of the underlying thing the end-user wrote.
4
+
5
+ TODO:
6
+ - re type targets:
7
+ - unwrap instances of objects to their types?
8
+ - merge mro?
9
+ - are these better left up to callers? too usecase-specific to favor either way?
10
+ """
11
+ import types
12
+ import typing as ta
13
+
14
+ from . import check
15
+ from . import lang
16
+
17
+
18
+ T = ta.TypeVar('T')
19
+
20
+
21
+ ##
22
+
23
+
24
+ class ObjectMetadata(lang.Abstract):
25
+ pass
26
+
27
+
28
+ class ObjectMetadataTarget(lang.Abstract):
29
+ pass
30
+
31
+
32
+ ##
33
+
34
+
35
+ _VALID_OBJECT_METADATA_TARGET_TYPES: tuple[type, ...] = (
36
+ type,
37
+
38
+ types.FunctionType,
39
+ # *not* types.MethodType - must unwrap to unbound class func
40
+ # *not* functools.partial - must unwrap to underlying func
41
+
42
+ ObjectMetadataTarget,
43
+ )
44
+
45
+
46
+ class ObjectMetadataTargetTypeError(TypeError):
47
+ pass
48
+
49
+
50
+ def _unwrap_object_metadata_target(obj: ta.Any) -> ta.Any:
51
+ tgt: ta.Any = obj
52
+ tgt = lang.unwrap_func(tgt)
53
+
54
+ if not isinstance(tgt, _VALID_OBJECT_METADATA_TARGET_TYPES):
55
+ raise ObjectMetadataTargetTypeError(tgt)
56
+
57
+ return tgt
58
+
59
+
60
+ ##
61
+
62
+
63
+ _OBJECT_METADATA_ATTR = '__' + __name__.replace('.', '_') + '__metadata__'
64
+
65
+
66
+ def append_object_metadata(obj: T, *mds: ObjectMetadata) -> T:
67
+ for md in mds:
68
+ check.isinstance(md, ObjectMetadata)
69
+
70
+ tgt = _unwrap_object_metadata_target(obj)
71
+ dct = tgt.__dict__
72
+
73
+ if isinstance(dct, types.MappingProxyType):
74
+ for _ in range(2):
75
+ try:
76
+ lst = dct[_OBJECT_METADATA_ATTR]
77
+ except KeyError:
78
+ setattr(tgt, _OBJECT_METADATA_ATTR, [])
79
+ else:
80
+ break
81
+ else:
82
+ raise RuntimeError
83
+
84
+ else:
85
+ lst = dct.setdefault(_OBJECT_METADATA_ATTR, [])
86
+
87
+ lst.extend(mds)
88
+ return obj
89
+
90
+
91
+ def get_object_metadata(obj: ta.Any, *, strict: bool = False) -> ta.Sequence[ObjectMetadata]:
92
+ try:
93
+ tgt = _unwrap_object_metadata_target(obj)
94
+ except ObjectMetadataTargetTypeError:
95
+ if not strict:
96
+ return ()
97
+ raise
98
+
99
+ try:
100
+ dct = tgt.__dict__
101
+ except AttributeError:
102
+ return ()
103
+
104
+ return dct.get(_OBJECT_METADATA_ATTR, ())
105
+
106
+
107
+ ##
108
+
109
+
110
+ class DecoratorObjectMetadata(ObjectMetadata, lang.Abstract):
111
+ _OBJECT_METADATA_TARGET_TYPES: ta.ClassVar[tuple[type, ...] | None] = None
112
+
113
+ def __init_subclass__(
114
+ cls,
115
+ *,
116
+ object_metadata_target_types: ta.Iterable[type] | None = None,
117
+ **kwargs: ta.Any,
118
+ ) -> None:
119
+ super().__init_subclass__(**kwargs)
120
+
121
+ if object_metadata_target_types is not None:
122
+ tts = tuple(object_metadata_target_types)
123
+ for tt in tts:
124
+ check.issubclass(tt, _VALID_OBJECT_METADATA_TARGET_TYPES)
125
+ setattr(cls, '_OBJECT_METADATA_TARGET_TYPES', tts)
126
+
127
+ def __call__(self, obj: T) -> T:
128
+ tgt: ta.Any = obj
129
+ if (tts := type(self)._OBJECT_METADATA_TARGET_TYPES) is not None: # noqa
130
+ tgt = _unwrap_object_metadata_target(tgt)
131
+ check.isinstance(tgt, tts)
132
+
133
+ append_object_metadata(tgt, self)
134
+ return obj
135
+
136
+
137
+ #
138
+
139
+
140
+ class ClassDecoratorObjectMetadata(
141
+ DecoratorObjectMetadata,
142
+ lang.Abstract,
143
+ object_metadata_target_types=[type],
144
+ ):
145
+ pass
146
+
147
+
148
+ class FunctionDecoratorObjectMetadata(
149
+ DecoratorObjectMetadata,
150
+ lang.Abstract,
151
+ object_metadata_target_types=[types.FunctionType],
152
+ ):
153
+ pass
omlish/os/filemodes.py CHANGED
@@ -77,6 +77,48 @@ class FileMode:
77
77
  )
78
78
  )
79
79
 
80
+ @classmethod
81
+ def from_flags(cls, i: int) -> 'FileMode':
82
+ if i & os.O_RDWR:
83
+ i &= ~os.O_RDWR
84
+ read = write = True
85
+ elif i & os.O_WRONLY:
86
+ i &= ~os.O_WRONLY
87
+ write = True
88
+ read = False
89
+ else:
90
+ read = True
91
+ write = False
92
+
93
+ create = False
94
+ if i & os.O_CREAT:
95
+ i &= ~os.O_CREAT
96
+ create = True
97
+
98
+ exists: str | None = None
99
+ if i & os.O_TRUNC:
100
+ i &= ~os.O_TRUNC
101
+ exists = check.replacing_none(exists, 'truncate')
102
+ if i & os.O_EXCL:
103
+ i &= ~os.O_EXCL
104
+ exists = check.replacing_none(exists, 'fail')
105
+ if i & os.O_APPEND:
106
+ i &= ~os.O_APPEND
107
+ exists = check.replacing_none(exists, 'append')
108
+ if exists is None:
109
+ exists = 'beginning'
110
+
111
+ if i:
112
+ raise ValueError(i)
113
+
114
+ return FileMode(
115
+ read=read,
116
+ write=write,
117
+ create=create,
118
+ exists=exists, # type: ignore
119
+ binary=True,
120
+ )
121
+
80
122
  #
81
123
 
82
124
  def render(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev236
3
+ Version: 0.0.0.dev238
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause