omlish 0.0.0.dev236__py3-none-any.whl → 0.0.0.dev238__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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