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
omlish/__about__.py
CHANGED
omlish/collections/__init__.py
CHANGED
@@ -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
@@ -1,53 +1,35 @@
|
|
1
1
|
import typing as ta
|
2
|
-
import weakref
|
3
2
|
|
4
3
|
from .. import check
|
5
4
|
from .. import dataclasses as dc
|
6
5
|
from .. import lang
|
7
6
|
|
8
7
|
|
9
|
-
|
10
|
-
dc.Data,
|
11
|
-
lang.Abstract,
|
12
|
-
frozen=True,
|
13
|
-
reorder=True,
|
14
|
-
confer=frozenset([
|
15
|
-
'frozen',
|
16
|
-
'reorder',
|
17
|
-
'confer',
|
18
|
-
]),
|
19
|
-
):
|
20
|
-
pass
|
8
|
+
ConfigurableConfigT = ta.TypeVar('ConfigurableConfigT', bound='Configurable.Config')
|
21
9
|
|
22
10
|
|
23
|
-
|
11
|
+
class Configurable(ta.Generic[ConfigurableConfigT], lang.Abstract):
|
12
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
13
|
+
class Config:
|
14
|
+
"""Does not use any dc metaclasses to preserve typechecking."""
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class Configurable(ta.Generic[ConfigT], lang.Abstract):
|
29
|
-
|
30
|
-
# FIXME: https://github.com/python/mypy/issues/5144
|
31
|
-
Config: ta.ClassVar[type[ConfigT]] # type: ignore # noqa
|
16
|
+
configurable_cls: ta.ClassVar[type['Configurable']]
|
32
17
|
|
33
18
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
34
19
|
super().__init_subclass__(**kwargs)
|
35
20
|
|
36
|
-
|
37
|
-
|
38
|
-
|
21
|
+
if not lang.is_abstract_class(cls):
|
22
|
+
check.in_('Config', cls.__dict__)
|
23
|
+
cfg_cls = check.issubclass(cls.Config, Configurable.Config)
|
24
|
+
check.not_in('configurable_cls', cfg_cls.__dict__)
|
25
|
+
check.state(dc.is_immediate_dataclass(cfg_cls))
|
26
|
+
cfg_cls.configurable_cls = cls
|
39
27
|
|
40
|
-
def __init__(self, config:
|
28
|
+
def __init__(self, config: ConfigurableConfigT) -> None:
|
41
29
|
super().__init__()
|
42
30
|
|
43
|
-
self._config:
|
44
|
-
|
31
|
+
self._config: ConfigurableConfigT = check.isinstance(config, self.Config) # type: ignore[assignment]
|
45
32
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
elif isinstance(cfg, Config):
|
50
|
-
cfg_cls = type(cfg)
|
51
|
-
else:
|
52
|
-
raise TypeError(cfg)
|
53
|
-
return _CONFIG_CLS_MAP[cfg_cls]
|
33
|
+
@property
|
34
|
+
def config(self) -> ConfigurableConfigT:
|
35
|
+
return self._config
|
omlish/configs/processing/all.py
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- smarter merging than just dumb dict-squashing
|
6
|
+
"""
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
from ..types import ConfigMap
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
def merge_configs(*ms: ConfigMap) -> ConfigMap:
|
16
|
+
def rec(o, i):
|
17
|
+
for k, v in i.items():
|
18
|
+
try:
|
19
|
+
e = o[k]
|
20
|
+
except KeyError:
|
21
|
+
o[k] = v
|
22
|
+
else:
|
23
|
+
if isinstance(e, ta.Mapping) and isinstance(v, ta.Mapping):
|
24
|
+
rec(e, v) # noqa
|
25
|
+
else:
|
26
|
+
if isinstance(v, ta.Mapping):
|
27
|
+
v = dict(v)
|
28
|
+
o[k] = v
|
29
|
+
|
30
|
+
o: dict = {}
|
31
|
+
for i in ms:
|
32
|
+
rec(o, i)
|
33
|
+
return o
|
omlish/configs/shadow.py
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- cascading?
|
6
|
+
- caching?
|
7
|
+
- purging?
|
8
|
+
- wishlist: piecewise config parts, unmarshal, registered via manifests
|
9
|
+
"""
|
10
|
+
import abc
|
11
|
+
import os.path
|
12
|
+
import typing as ta
|
13
|
+
|
14
|
+
from ..lite.check import check
|
15
|
+
from ..os.mangle import mangle_path
|
16
|
+
from .formats import DEFAULT_CONFIG_LOADERS
|
17
|
+
from .formats import ConfigLoader
|
18
|
+
|
19
|
+
|
20
|
+
ShadowConfig = ta.Mapping[str, ta.Any] # ta.TypeAlias
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
class ShadowConfigs(abc.ABC):
|
27
|
+
@abc.abstractmethod
|
28
|
+
def get_shadow_config(self, path: str) -> ta.Optional[ShadowConfig]:
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
|
32
|
+
class FileShadowConfigs(ShadowConfigs, abc.ABC):
|
33
|
+
@abc.abstractmethod
|
34
|
+
def get_shadow_config_file_path(self, path: str) -> str:
|
35
|
+
raise NotImplementedError
|
36
|
+
|
37
|
+
|
38
|
+
##
|
39
|
+
|
40
|
+
|
41
|
+
class NopShadowConfigs(ShadowConfigs):
|
42
|
+
def get_shadow_config(self, path: str) -> ta.Optional[ShadowConfig]:
|
43
|
+
return None
|
44
|
+
|
45
|
+
|
46
|
+
##
|
47
|
+
|
48
|
+
|
49
|
+
class MangledFilesShadowConfigs(FileShadowConfigs):
|
50
|
+
def __init__(
|
51
|
+
self,
|
52
|
+
path: str,
|
53
|
+
*,
|
54
|
+
create: bool = False,
|
55
|
+
ext: str = 'yml',
|
56
|
+
loader: ta.Optional[ConfigLoader] = None,
|
57
|
+
) -> None: # noqa
|
58
|
+
super().__init__()
|
59
|
+
|
60
|
+
check.arg(not ext.startswith('.'))
|
61
|
+
check.non_empty_str(ext)
|
62
|
+
|
63
|
+
if loader is None:
|
64
|
+
for dl in DEFAULT_CONFIG_LOADERS:
|
65
|
+
if dl.match_file(f'.{ext}'):
|
66
|
+
loader = dl
|
67
|
+
break
|
68
|
+
else:
|
69
|
+
raise KeyError(ext)
|
70
|
+
|
71
|
+
self._path = path
|
72
|
+
self._create = create
|
73
|
+
self._ext = ext
|
74
|
+
self._loader = loader
|
75
|
+
|
76
|
+
def get_shadow_config_file_path(self, path: str) -> str:
|
77
|
+
mangled = '.'.join([mangle_path(os.path.abspath(path)), self._ext])
|
78
|
+
return os.path.abspath(os.path.join(self._path, mangled))
|
79
|
+
|
80
|
+
def get_shadow_config(self, path: str) -> ta.Optional[ShadowConfig]:
|
81
|
+
file_path = self.get_shadow_config_file_path(path)
|
82
|
+
|
83
|
+
if self._create:
|
84
|
+
os.makedirs(self._path, exist_ok=True)
|
85
|
+
|
86
|
+
try:
|
87
|
+
with open(file_path) as f:
|
88
|
+
content = f.read()
|
89
|
+
except FileNotFoundError:
|
90
|
+
return None
|
91
|
+
|
92
|
+
return self._loader.load_str(content).as_map()
|
omlish/daemons/daemon.py
CHANGED
@@ -38,14 +38,9 @@ log = logging.getLogger(__name__)
|
|
38
38
|
|
39
39
|
|
40
40
|
class Daemon:
|
41
|
-
"""
|
42
|
-
Instances of these should most likely be effectively singletons, but that's up to the user.
|
43
|
-
"""
|
44
|
-
|
45
41
|
@dc.dataclass(frozen=True, kw_only=True)
|
46
42
|
class Config:
|
47
|
-
|
48
|
-
spawning: Spawning
|
43
|
+
spawning: Spawning | None = None
|
49
44
|
|
50
45
|
#
|
51
46
|
|
@@ -68,11 +63,20 @@ class Daemon:
|
|
68
63
|
def __post_init__(self) -> None:
|
69
64
|
check.isinstance(self.pid_file, (str, None))
|
70
65
|
|
71
|
-
def __init__(
|
66
|
+
def __init__(
|
67
|
+
self,
|
68
|
+
target: Target,
|
69
|
+
config: Config = Config(),
|
70
|
+
) -> None:
|
72
71
|
super().__init__()
|
73
72
|
|
73
|
+
self._target = target
|
74
74
|
self._config = config
|
75
75
|
|
76
|
+
@property
|
77
|
+
def target(self) -> Target:
|
78
|
+
return self._target
|
79
|
+
|
76
80
|
@property
|
77
81
|
def config(self) -> Config:
|
78
82
|
return self._config
|
@@ -124,8 +128,8 @@ class Daemon:
|
|
124
128
|
|
125
129
|
def launch_no_wait(self) -> None:
|
126
130
|
launcher = Launcher(
|
127
|
-
target=self.
|
128
|
-
spawning=self._config.spawning,
|
131
|
+
target=self._target,
|
132
|
+
spawning=check.not_none(self._config.spawning),
|
129
133
|
|
130
134
|
pid_file=self._config.pid_file,
|
131
135
|
reparent_process=self._config.reparent_process,
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import abc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .. import cached
|
5
|
+
from .. import check
|
6
|
+
from .. import dataclasses as dc
|
7
|
+
from .. import lang
|
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
|
13
|
+
|
14
|
+
|
15
|
+
ServiceConfigT = ta.TypeVar('ServiceConfigT', bound='Service.Config')
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class Service(Configurable[ServiceConfigT], lang.Abstract):
|
22
|
+
class Config(Configurable.Config, lang.Abstract):
|
23
|
+
pass
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def from_config(cls, config: Config) -> 'Service':
|
27
|
+
return check.isinstance(config.configurable_cls(config), cls)
|
28
|
+
|
29
|
+
#
|
30
|
+
|
31
|
+
@abc.abstractmethod
|
32
|
+
def _run(self) -> None:
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
def run(self) -> None:
|
36
|
+
self._run()
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def run_config(cls, config: Config) -> None:
|
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
@@ -10,15 +10,38 @@ from .. import lang
|
|
10
10
|
|
11
11
|
if ta.TYPE_CHECKING:
|
12
12
|
import runpy
|
13
|
+
|
14
|
+
from . import services
|
15
|
+
|
13
16
|
else:
|
14
17
|
runpy = lang.proxy_import('runpy')
|
15
18
|
|
19
|
+
services = lang.proxy_import('.services', __package__)
|
20
|
+
|
16
21
|
|
17
22
|
##
|
18
23
|
|
19
24
|
|
20
25
|
class Target(dc.Case):
|
21
|
-
|
26
|
+
@classmethod
|
27
|
+
def of(cls, obj: ta.Any) -> 'Target':
|
28
|
+
if isinstance(obj, Target):
|
29
|
+
return obj
|
30
|
+
|
31
|
+
elif isinstance(obj, str):
|
32
|
+
return NameTarget(obj)
|
33
|
+
|
34
|
+
elif callable(obj):
|
35
|
+
return FnTarget(obj)
|
36
|
+
|
37
|
+
elif isinstance(obj, services.Service):
|
38
|
+
return services.ServiceTarget(obj)
|
39
|
+
|
40
|
+
elif isinstance(obj, services.Service.Config):
|
41
|
+
return services.ServiceConfigTarget(obj)
|
42
|
+
|
43
|
+
else:
|
44
|
+
raise TypeError(obj)
|
22
45
|
|
23
46
|
|
24
47
|
class TargetRunner(abc.ABC):
|
@@ -36,14 +59,18 @@ def target_runner_for(target: Target) -> TargetRunner:
|
|
36
59
|
|
37
60
|
|
38
61
|
class FnTarget(Target):
|
39
|
-
fn: ta.Callable[[],
|
62
|
+
fn: ta.Callable[[], ta.Any]
|
40
63
|
|
41
64
|
|
42
65
|
class FnTargetRunner(TargetRunner, dc.Frozen):
|
43
66
|
target: FnTarget
|
44
67
|
|
45
68
|
def run(self) -> None:
|
46
|
-
self.target.fn()
|
69
|
+
obj = self.target.fn()
|
70
|
+
if obj is not None:
|
71
|
+
tgt = Target.of(obj)
|
72
|
+
tr = target_runner_for(tgt)
|
73
|
+
tr.run()
|
47
74
|
|
48
75
|
|
49
76
|
@target_runner_for.register
|
@@ -57,6 +84,23 @@ def _(target: FnTarget) -> FnTargetRunner:
|
|
57
84
|
class NameTarget(Target):
|
58
85
|
name: str
|
59
86
|
|
87
|
+
@classmethod
|
88
|
+
def for_obj(
|
89
|
+
cls,
|
90
|
+
obj: ta.Any,
|
91
|
+
*,
|
92
|
+
globals: ta.Mapping[str, ta.Any] | None = None, # noqa
|
93
|
+
no_module_name_lookup: bool = False,
|
94
|
+
) -> 'NameTarget':
|
95
|
+
if globals is None:
|
96
|
+
globals = obj.__globals__ # noqa
|
97
|
+
if not no_module_name_lookup:
|
98
|
+
mn = lang.get_real_module_name(globals)
|
99
|
+
else:
|
100
|
+
mn = globals['__name__']
|
101
|
+
qn = obj.__qualname__
|
102
|
+
return NameTarget('.'.join([mn, qn]))
|
103
|
+
|
60
104
|
|
61
105
|
class NameTargetRunner(TargetRunner, dc.Frozen):
|
62
106
|
target: NameTarget
|
@@ -67,7 +111,14 @@ class NameTargetRunner(TargetRunner, dc.Frozen):
|
|
67
111
|
runpy._run_module_as_main(name) # type: ignore # noqa
|
68
112
|
else:
|
69
113
|
obj = lang.import_attr(self.target.name)
|
70
|
-
obj
|
114
|
+
tgt = Target.of(obj)
|
115
|
+
tr = target_runner_for(tgt)
|
116
|
+
tr.run()
|
117
|
+
|
118
|
+
|
119
|
+
@target_runner_for.register
|
120
|
+
def _(target: NameTarget) -> NameTargetRunner:
|
121
|
+
return NameTargetRunner(target)
|
71
122
|
|
72
123
|
|
73
124
|
##
|
@@ -76,11 +127,16 @@ class NameTargetRunner(TargetRunner, dc.Frozen):
|
|
76
127
|
class ExecTarget(Target):
|
77
128
|
cmd: ta.Sequence[str] = dc.xfield(coerce=check.of_not_isinstance(str))
|
78
129
|
|
130
|
+
cwd: str | None = None
|
131
|
+
|
79
132
|
|
80
133
|
class ExecTargetRunner(TargetRunner, dc.Frozen):
|
81
134
|
target: ExecTarget
|
82
135
|
|
83
136
|
def run(self) -> None:
|
137
|
+
if (cwd := self.target.cwd) is not None:
|
138
|
+
os.chdir(os.path.expanduser(cwd))
|
139
|
+
|
84
140
|
os.execl(*self.target.cmd)
|
85
141
|
|
86
142
|
|
omlish/dataclasses/__init__.py
CHANGED
@@ -91,9 +91,11 @@ from .impl.reflect import ( # noqa
|
|
91
91
|
reflect,
|
92
92
|
)
|
93
93
|
|
94
|
-
from .
|
95
|
-
|
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
|
)
|