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.
- omlish/__about__.py +2 -2
- omlish/collections/__init__.py +5 -5
- omlish/collections/ranked.py +79 -0
- omlish/configs/classes.py +17 -35
- omlish/configs/processing/all.py +4 -0
- omlish/configs/processing/merging.py +33 -0
- omlish/configs/shadow.py +92 -0
- omlish/daemons/daemon.py +13 -9
- omlish/daemons/services.py +108 -0
- omlish/daemons/targets.py +60 -4
- omlish/dataclasses/__init__.py +6 -2
- omlish/dataclasses/static.py +176 -0
- omlish/dataclasses/utils.py +0 -9
- omlish/graphs/trees.py +8 -8
- omlish/lang/__init__.py +1 -0
- omlish/lang/descriptors.py +8 -7
- omlish/lang/imports.py +15 -0
- omlish/lite/dataclasses.py +18 -0
- omlish/manifests/__init__.py +0 -2
- omlish/manifests/base.py +1 -0
- omlish/manifests/load.py +1 -0
- omlish/manifests/static.py +20 -0
- omlish/manifests/types.py +1 -0
- omlish/metadata.py +153 -0
- omlish/os/filemodes.py +42 -0
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/RECORD +31 -25
- omlish/collections/indexed.py +0 -73
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev236.dist-info → omlish-0.0.0.dev238.dist-info}/top_level.txt +0 -0
@@ -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')
|
omlish/dataclasses/utils.py
CHANGED
@@ -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.
|
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.
|
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.
|
94
|
-
[(n, self.
|
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.
|
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.
|
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.
|
228
|
-
return self.
|
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
omlish/lang/descriptors.py
CHANGED
@@ -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.
|
51
|
+
def unwrap_method_descriptors(fn: ta.Any) -> ta.Any:
|
52
52
|
while is_method_descriptor(fn):
|
53
|
-
fn = fn.__func__
|
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.
|
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__
|
64
|
+
fn = fn.__func__
|
65
65
|
|
66
66
|
elif hasattr(fn, '__wrapped__'):
|
67
67
|
nxt = fn.__wrapped__
|
68
|
-
|
69
|
-
|
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.
|
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")
|
omlish/lite/dataclasses.py
CHANGED
@@ -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],
|
omlish/manifests/__init__.py
CHANGED
omlish/manifests/base.py
CHANGED
omlish/manifests/load.py
CHANGED
@@ -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
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:
|