ominfra 0.0.0.dev175__py3-none-any.whl → 0.0.0.dev177__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.
ominfra/configs.py CHANGED
@@ -6,7 +6,8 @@ import typing as ta
6
6
 
7
7
  from omdev.toml.parser import toml_loads
8
8
  from omlish.lite.check import check
9
- from omlish.lite.marshal import unmarshal_obj
9
+ from omlish.lite.marshal import OBJ_MARSHALER_MANAGER
10
+ from omlish.lite.marshal import ObjMarshalerManager
10
11
 
11
12
 
12
13
  T = ta.TypeVar('T')
@@ -46,6 +47,7 @@ def read_config_file(
46
47
  cls: ta.Type[T],
47
48
  *,
48
49
  prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
50
+ msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
49
51
  ) -> T:
50
52
  with open(path) as cf:
51
53
  config_dct = parse_config_file(os.path.basename(path), cf)
@@ -53,7 +55,7 @@ def read_config_file(
53
55
  if prepare is not None:
54
56
  config_dct = prepare(config_dct)
55
57
 
56
- return unmarshal_obj(config_dct, cls)
58
+ return msh.unmarshal_obj(config_dct, cls)
57
59
 
58
60
 
59
61
  def build_config_named_children(
@@ -13,7 +13,7 @@ def install_command_marshaling(
13
13
  lambda c: c,
14
14
  lambda c: c.Output,
15
15
  ]:
16
- msh.register_opj_marshaler(
16
+ msh.set_obj_marshaler(
17
17
  fn(Command),
18
18
  PolymorphicObjMarshaler.of([
19
19
  PolymorphicObjMarshaler.Impl(
@@ -20,16 +20,12 @@ class DeployAppManager(DeployPathOwner):
20
20
  def __init__(
21
21
  self,
22
22
  *,
23
- deploy_home: ta.Optional[DeployHome] = None,
24
-
25
23
  conf: DeployConfManager,
26
24
  git: DeployGitManager,
27
25
  venvs: DeployVenvManager,
28
26
  ) -> None:
29
27
  super().__init__()
30
28
 
31
- self._deploy_home = deploy_home
32
-
33
29
  self._conf = conf
34
30
  self._git = git
35
31
  self._venvs = venvs
@@ -70,12 +66,13 @@ class DeployAppManager(DeployPathOwner):
70
66
  async def prepare_app(
71
67
  self,
72
68
  spec: DeployAppSpec,
69
+ home: DeployHome,
73
70
  tags: DeployTagMap,
74
71
  ) -> None:
75
- deploy_home = check.non_empty_str(self._deploy_home)
72
+ check.non_empty_str(home)
76
73
 
77
74
  def build_path(pth: DeployPath) -> str:
78
- return os.path.join(deploy_home, pth.render(tags))
75
+ return os.path.join(home, pth.render(tags))
79
76
 
80
77
  app_dir = build_path(self._APP_DIR)
81
78
  deploy_dir = build_path(self._DEPLOY_DIR)
@@ -85,7 +82,9 @@ class DeployAppManager(DeployPathOwner):
85
82
 
86
83
  os.makedirs(deploy_dir, exist_ok=True)
87
84
 
88
- deploying_link = os.path.join(deploy_home, 'deploys/deploying')
85
+ deploying_link = os.path.join(home, 'deploys/deploying')
86
+ if os.path.exists(deploying_link):
87
+ os.unlink(deploying_link)
89
88
  relative_symlink(
90
89
  deploy_dir,
91
90
  deploying_link,
@@ -130,7 +129,7 @@ class DeployAppManager(DeployPathOwner):
130
129
  # else:
131
130
  # os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
132
131
 
133
- current_link = os.path.join(deploy_home, 'deploys/current')
132
+ current_link = os.path.join(home, 'deploys/current')
134
133
 
135
134
  # if os.path.exists(current_link):
136
135
  # mirror_symlinks(
@@ -147,6 +146,7 @@ class DeployAppManager(DeployPathOwner):
147
146
  app_git_dir = os.path.join(app_dir, 'git')
148
147
  await self._git.checkout(
149
148
  spec.git,
149
+ home,
150
150
  app_git_dir,
151
151
  )
152
152
 
@@ -156,6 +156,7 @@ class DeployAppManager(DeployPathOwner):
156
156
  app_venv_dir = os.path.join(app_dir, 'venv')
157
157
  await self._venvs.setup_venv(
158
158
  spec.venv,
159
+ home,
159
160
  app_git_dir,
160
161
  app_venv_dir,
161
162
  )
@@ -31,21 +31,9 @@ from .tags import DEPLOY_TAG_SEPARATOR
31
31
  from .tags import DeployApp
32
32
  from .tags import DeployConf
33
33
  from .tags import DeployTagMap
34
- from .types import DeployHome
35
34
 
36
35
 
37
36
  class DeployConfManager:
38
- def __init__(
39
- self,
40
- *,
41
- deploy_home: ta.Optional[DeployHome] = None,
42
- ) -> None:
43
- super().__init__()
44
-
45
- self._deploy_home = deploy_home
46
-
47
- #
48
-
49
37
  async def _write_app_conf_file(
50
38
  self,
51
39
  acf: DeployAppConfFile,
@@ -1,6 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import dataclasses as dc
3
- import typing as ta
4
3
 
5
4
 
6
5
  ##
@@ -8,4 +7,4 @@ import typing as ta
8
7
 
9
8
  @dc.dataclass(frozen=True)
10
9
  class DeployConfig:
11
- deploy_home: ta.Optional[str] = None
10
+ pass
@@ -1,7 +1,9 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import datetime
3
+ import os.path
3
4
  import typing as ta
4
5
 
6
+ from omlish.lite.check import check
5
7
  from omlish.lite.typing import Func0
6
8
 
7
9
  from .apps import DeployAppManager
@@ -10,6 +12,7 @@ from .specs import DeploySpec
10
12
  from .tags import DeployAppRev
11
13
  from .tags import DeployTagMap
12
14
  from .tags import DeployTime
15
+ from .types import DeployHome
13
16
 
14
17
 
15
18
  DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
@@ -51,6 +54,15 @@ class DeployManager:
51
54
 
52
55
  #
53
56
 
57
+ hs = check.non_empty_str(spec.home)
58
+ hs = os.path.expanduser(hs)
59
+ hs = os.path.realpath(hs)
60
+ hs = os.path.abspath(hs)
61
+
62
+ home = DeployHome(hs)
63
+
64
+ #
65
+
54
66
  deploy_tags = DeployTagMap(
55
67
  self._make_deploy_time(),
56
68
  spec.key(),
@@ -67,5 +79,6 @@ class DeployManager:
67
79
 
68
80
  await self._apps.prepare_app(
69
81
  app,
82
+ home,
70
83
  app_tags,
71
84
  )
@@ -15,11 +15,11 @@ import typing as ta
15
15
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
16
16
  from omlish.lite.cached import async_cached_nullary
17
17
  from omlish.lite.check import check
18
- from omlish.os.atomics import AtomicPathSwapping
19
18
 
20
19
  from .paths.owners import SingleDirDeployPathOwner
21
20
  from .specs import DeployGitRepo
22
21
  from .specs import DeployGitSpec
22
+ from .tmp import DeployHomeAtomics
23
23
  from .types import DeployHome
24
24
  from .types import DeployRev
25
25
 
@@ -31,12 +31,10 @@ class DeployGitManager(SingleDirDeployPathOwner):
31
31
  def __init__(
32
32
  self,
33
33
  *,
34
- deploy_home: ta.Optional[DeployHome] = None,
35
- atomics: AtomicPathSwapping,
34
+ atomics: DeployHomeAtomics,
36
35
  ) -> None:
37
36
  super().__init__(
38
37
  owned_dir='git',
39
- deploy_home=deploy_home,
40
38
  )
41
39
 
42
40
  self._atomics = atomics
@@ -48,13 +46,15 @@ class DeployGitManager(SingleDirDeployPathOwner):
48
46
  self,
49
47
  git: 'DeployGitManager',
50
48
  repo: DeployGitRepo,
49
+ home: DeployHome,
51
50
  ) -> None:
52
51
  super().__init__()
53
52
 
54
53
  self._git = git
55
54
  self._repo = repo
55
+ self._home = home
56
56
  self._dir = os.path.join(
57
- self._git._make_dir(), # noqa
57
+ self._git._make_dir(home), # noqa
58
58
  check.non_empty_str(repo.host),
59
59
  check.non_empty_str(repo.path),
60
60
  )
@@ -97,7 +97,7 @@ class DeployGitManager(SingleDirDeployPathOwner):
97
97
 
98
98
  async def checkout(self, spec: DeployGitSpec, dst_dir: str) -> None:
99
99
  check.state(not os.path.exists(dst_dir))
100
- with self._git._atomics.begin_atomic_path_swap( # noqa
100
+ with self._git._atomics(self._home).begin_atomic_path_swap( # noqa
101
101
  'dir',
102
102
  dst_dir,
103
103
  auto_commit=True,
@@ -112,16 +112,31 @@ class DeployGitManager(SingleDirDeployPathOwner):
112
112
  await dst_call('git', 'fetch', '--depth=1', 'local', spec.rev)
113
113
  await dst_call('git', 'checkout', spec.rev, *(spec.subtrees or []))
114
114
 
115
- def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
115
+ def get_repo_dir(
116
+ self,
117
+ repo: DeployGitRepo,
118
+ home: DeployHome,
119
+ ) -> RepoDir:
116
120
  try:
117
121
  return self._repo_dirs[repo]
118
122
  except KeyError:
119
- repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
123
+ repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(
124
+ self,
125
+ repo,
126
+ home,
127
+ )
120
128
  return repo_dir
121
129
 
122
130
  async def checkout(
123
131
  self,
124
132
  spec: DeployGitSpec,
133
+ home: DeployHome,
125
134
  dst_dir: str,
126
135
  ) -> None:
127
- await self.get_repo_dir(spec.repo).checkout(spec, dst_dir)
136
+ await self.get_repo_dir(
137
+ spec.repo,
138
+ home,
139
+ ).checkout(
140
+ spec,
141
+ dst_dir,
142
+ )
@@ -1,11 +1,9 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import os.path
3
2
  import typing as ta
4
3
 
5
4
  from omlish.lite.inject import InjectorBindingOrBindings
6
5
  from omlish.lite.inject import InjectorBindings
7
6
  from omlish.lite.inject import inj
8
- from omlish.os.atomics import AtomicPathSwapping
9
7
 
10
8
  from ..commands.inject import bind_command
11
9
  from .apps import DeployAppManager
@@ -19,8 +17,8 @@ from .interp import InterpCommand
19
17
  from .interp import InterpCommandExecutor
20
18
  from .paths.inject import bind_deploy_paths
21
19
  from .paths.owners import DeployPathOwner
20
+ from .tmp import DeployHomeAtomics
22
21
  from .tmp import DeployTmpManager
23
- from .types import DeployHome
24
22
  from .venvs import DeployVenvManager
25
23
 
26
24
 
@@ -55,13 +53,18 @@ def bind_deploy(
55
53
  bind_manager(DeployManager),
56
54
 
57
55
  bind_manager(DeployTmpManager),
58
- inj.bind(AtomicPathSwapping, to_key=DeployTmpManager),
59
56
 
60
57
  bind_manager(DeployVenvManager),
61
58
  ])
62
59
 
63
60
  #
64
61
 
62
+ def provide_deploy_home_atomics(tmp: DeployTmpManager) -> DeployHomeAtomics:
63
+ return DeployHomeAtomics(tmp.get_swapping)
64
+ lst.append(inj.bind(provide_deploy_home_atomics, singleton=True))
65
+
66
+ #
67
+
65
68
  lst.extend([
66
69
  bind_command(DeployCommand, DeployCommandExecutor),
67
70
  bind_command(InterpCommand, InterpCommandExecutor),
@@ -69,8 +72,4 @@ def bind_deploy(
69
72
 
70
73
  #
71
74
 
72
- if (dh := deploy_config.deploy_home) is not None:
73
- dh = os.path.abspath(os.path.expanduser(dh))
74
- lst.append(inj.bind(dh, key=DeployHome))
75
-
76
75
  return inj.as_bindings(*lst)
@@ -3,7 +3,6 @@ import typing as ta
3
3
 
4
4
  from omlish.lite.cached import cached_nullary
5
5
 
6
- from ..types import DeployHome
7
6
  from .owners import DeployPathOwner
8
7
  from .owners import DeployPathOwners
9
8
  from .paths import DeployPath
@@ -14,12 +13,10 @@ class DeployPathsManager:
14
13
  def __init__(
15
14
  self,
16
15
  *,
17
- deploy_home: ta.Optional[DeployHome],
18
16
  deploy_path_owners: DeployPathOwners,
19
17
  ) -> None:
20
18
  super().__init__()
21
19
 
22
- self._deploy_home = deploy_home
23
20
  self._deploy_path_owners = deploy_path_owners
24
21
 
25
22
  @cached_nullary
@@ -3,7 +3,6 @@ import abc
3
3
  import os.path
4
4
  import typing as ta
5
5
 
6
- from omlish.lite.cached import cached_nullary
7
6
  from omlish.lite.check import check
8
7
 
9
8
  from ..types import DeployHome
@@ -24,7 +23,6 @@ class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
24
23
  self,
25
24
  *args: ta.Any,
26
25
  owned_dir: str,
27
- deploy_home: ta.Optional[DeployHome],
28
26
  **kwargs: ta.Any,
29
27
  ) -> None:
30
28
  super().__init__(*args, **kwargs)
@@ -32,17 +30,13 @@ class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
32
30
  check.not_in('/', owned_dir)
33
31
  self._owned_dir: str = check.non_empty_str(owned_dir)
34
32
 
35
- self._deploy_home = deploy_home
36
-
37
33
  self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
38
34
 
39
- @cached_nullary
40
- def _dir(self) -> str:
41
- return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
35
+ def _dir(self, home: DeployHome) -> str:
36
+ return os.path.join(check.non_empty_str(home), self._owned_dir)
42
37
 
43
- @cached_nullary
44
- def _make_dir(self) -> str:
45
- if not os.path.isdir(d := self._dir()):
38
+ def _make_dir(self, home: DeployHome) -> str:
39
+ if not os.path.isdir(d := self._dir(home)):
46
40
  os.makedirs(d, exist_ok=True)
47
41
  return d
48
42
 
@@ -11,6 +11,7 @@ from .tags import DeployApp
11
11
  from .tags import DeployAppKey
12
12
  from .tags import DeployKey
13
13
  from .tags import KeyDeployTag # noqa
14
+ from .types import DeployHome
14
15
  from .types import DeployRev
15
16
 
16
17
 
@@ -153,9 +154,13 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
153
154
 
154
155
  @dc.dataclass(frozen=True)
155
156
  class DeploySpec(DeploySpecKeyed[DeployKey]):
157
+ home: DeployHome
158
+
156
159
  apps: ta.Sequence[DeployAppSpec]
157
160
 
158
161
  def __post_init__(self) -> None:
162
+ check.non_empty_str(self.home)
163
+
159
164
  seen: ta.Set[DeployApp] = set()
160
165
  for a in self.apps:
161
166
  if a.app in seen:
@@ -4,6 +4,8 @@ import dataclasses as dc
4
4
  import typing as ta
5
5
 
6
6
  from omlish.lite.check import check
7
+ from omlish.lite.marshal import SingleFieldObjMarshaler
8
+ from omlish.lite.marshal import register_type_obj_marshaler
7
9
 
8
10
 
9
11
  ##
@@ -82,6 +84,8 @@ def _register_deploy_tag(cls):
82
84
  _DEPLOY_TAGS_BY_NAME[cls.tag_name] = cls
83
85
  _DEPLOY_TAGS_BY_KWARG[cls.tag_kwarg] = cls
84
86
 
87
+ register_type_obj_marshaler(cls, SingleFieldObjMarshaler(cls, 's'))
88
+
85
89
  return cls
86
90
 
87
91
 
@@ -1,10 +1,6 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import typing as ta
3
-
4
- from omlish.lite.cached import cached_nullary
5
2
  from omlish.lite.check import check
6
- from omlish.os.atomics import AtomicPathSwap
7
- from omlish.os.atomics import AtomicPathSwapKind
3
+ from omlish.lite.typing import Func1
8
4
  from omlish.os.atomics import AtomicPathSwapping
9
5
  from omlish.os.atomics import TempDirAtomicPathSwapping
10
6
 
@@ -12,35 +8,20 @@ from .paths.owners import SingleDirDeployPathOwner
12
8
  from .types import DeployHome
13
9
 
14
10
 
11
+ class DeployHomeAtomics(Func1[DeployHome, AtomicPathSwapping]):
12
+ pass
13
+
14
+
15
15
  class DeployTmpManager(
16
16
  SingleDirDeployPathOwner,
17
- AtomicPathSwapping,
18
17
  ):
19
- def __init__(
20
- self,
21
- *,
22
- deploy_home: ta.Optional[DeployHome] = None,
23
- ) -> None:
18
+ def __init__(self) -> None:
24
19
  super().__init__(
25
20
  owned_dir='tmp',
26
- deploy_home=deploy_home,
27
21
  )
28
22
 
29
- @cached_nullary
30
- def _swapping(self) -> AtomicPathSwapping:
23
+ def get_swapping(self, home: DeployHome) -> AtomicPathSwapping:
31
24
  return TempDirAtomicPathSwapping(
32
- temp_dir=self._make_dir(),
33
- root_dir=check.non_empty_str(self._deploy_home),
34
- )
35
-
36
- def begin_atomic_path_swap(
37
- self,
38
- kind: AtomicPathSwapKind,
39
- dst_path: str,
40
- **kwargs: ta.Any,
41
- ) -> AtomicPathSwap:
42
- return self._swapping().begin_atomic_path_swap(
43
- kind,
44
- dst_path,
45
- **kwargs,
25
+ temp_dir=self._make_dir(home),
26
+ root_dir=check.non_empty_str(home),
46
27
  )
@@ -6,29 +6,31 @@ TODO:
6
6
  """
7
7
  import os.path
8
8
 
9
+ from omdev.interp.resolvers import DEFAULT_INTERP_RESOLVER
10
+ from omdev.interp.types import InterpSpecifier
9
11
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
10
- from omlish.os.atomics import AtomicPathSwapping
12
+ from omlish.lite.check import check
11
13
 
12
14
  from .specs import DeployVenvSpec
15
+ from .types import DeployHome
13
16
 
14
17
 
15
18
  class DeployVenvManager:
16
- def __init__(
17
- self,
18
- *,
19
- atomics: AtomicPathSwapping,
20
- ) -> None:
21
- super().__init__()
22
-
23
- self._atomics = atomics
24
-
25
19
  async def setup_venv(
26
20
  self,
27
21
  spec: DeployVenvSpec,
22
+ home: DeployHome,
28
23
  git_dir: str,
29
24
  venv_dir: str,
30
25
  ) -> None:
31
- sys_exe = 'python3'
26
+ if spec.interp is not None:
27
+ i = InterpSpecifier.parse(check.not_none(spec.interp))
28
+ o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i))
29
+ sys_exe = o.exe
30
+ else:
31
+ sys_exe = 'python3'
32
+
33
+ #
32
34
 
33
35
  # !! NOTE: (most) venvs cannot be relocated, so an atomic swap can't be used. it's up to the path manager to
34
36
  # garbage collect orphaned dirs.
ominfra/manage/main.py CHANGED
@@ -66,10 +66,9 @@ class MainCli(ArgparseCli):
66
66
 
67
67
  argparse_arg('--debug', action='store_true'),
68
68
 
69
- argparse_arg('--deploy-home'),
70
-
71
69
  argparse_arg('target'),
72
- argparse_arg('command', nargs='+'),
70
+ argparse_arg('-f', '--command-file', action='append'),
71
+ argparse_arg('command', nargs='*'),
73
72
  )
74
73
  async def run(self) -> None:
75
74
  bs = MainBootstrap(
@@ -79,9 +78,7 @@ class MainCli(ArgparseCli):
79
78
  debug=bool(self.args.debug),
80
79
  ),
81
80
 
82
- deploy_config=DeployConfig(
83
- deploy_home=self.args.deploy_home,
84
- ),
81
+ deploy_config=DeployConfig(),
85
82
 
86
83
  remote_config=RemoteConfig(
87
84
  payload_file=self.args._payload_file, # noqa
@@ -115,13 +112,19 @@ class MainCli(ArgparseCli):
115
112
  #
116
113
 
117
114
  cmds: ta.List[Command] = []
115
+
118
116
  cmd: Command
119
- for c in self.args.command:
117
+
118
+ for c in self.args.command or []:
120
119
  if not c.startswith('{'):
121
120
  c = json.dumps({c: {}})
122
121
  cmd = msh.unmarshal_obj(json.loads(c), Command)
123
122
  cmds.append(cmd)
124
123
 
124
+ for cf in self.args.command_file or []:
125
+ cmd = read_config_file(cf, Command, msh=msh)
126
+ cmds.append(cmd)
127
+
125
128
  #
126
129
 
127
130
  async with injector[ManageTargetConnector].connect(tgt) as ce:
@@ -2686,6 +2686,18 @@ class FieldsObjMarshaler(ObjMarshaler):
2686
2686
  })
2687
2687
 
2688
2688
 
2689
+ @dc.dataclass(frozen=True)
2690
+ class SingleFieldObjMarshaler(ObjMarshaler):
2691
+ ty: type
2692
+ fld: str
2693
+
2694
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
2695
+ return getattr(o, self.fld)
2696
+
2697
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
2698
+ return self.ty(**{self.fld: o})
2699
+
2700
+
2689
2701
  @dc.dataclass(frozen=True)
2690
2702
  class PolymorphicObjMarshaler(ObjMarshaler):
2691
2703
  class Impl(ta.NamedTuple):
@@ -2760,7 +2772,7 @@ _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
2760
2772
  **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
2761
2773
  **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
2762
2774
 
2763
- ta.Any: DynamicObjMarshaler(),
2775
+ **{t: DynamicObjMarshaler() for t in (ta.Any, object)},
2764
2776
 
2765
2777
  **{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
2766
2778
  decimal.Decimal: DecimalObjMarshaler(),
@@ -2785,6 +2797,16 @@ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
2785
2797
  ##
2786
2798
 
2787
2799
 
2800
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE: ta.MutableMapping[type, ObjMarshaler] = weakref.WeakKeyDictionary()
2801
+
2802
+
2803
+ def register_type_obj_marshaler(ty: type, om: ObjMarshaler) -> None:
2804
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE[ty] = om
2805
+
2806
+
2807
+ ##
2808
+
2809
+
2788
2810
  class ObjMarshalerManager:
2789
2811
  def __init__(
2790
2812
  self,
@@ -2794,6 +2816,8 @@ class ObjMarshalerManager:
2794
2816
  default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
2795
2817
  generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
2796
2818
  generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
2819
+
2820
+ registered_obj_marshalers: ta.Mapping[type, ObjMarshaler] = _REGISTERED_OBJ_MARSHALERS_BY_TYPE,
2797
2821
  ) -> None:
2798
2822
  super().__init__()
2799
2823
 
@@ -2802,6 +2826,7 @@ class ObjMarshalerManager:
2802
2826
  self._obj_marshalers = dict(default_obj_marshalers)
2803
2827
  self._generic_mapping_types = generic_mapping_types
2804
2828
  self._generic_iterable_types = generic_iterable_types
2829
+ self._registered_obj_marshalers = registered_obj_marshalers
2805
2830
 
2806
2831
  self._lock = threading.RLock()
2807
2832
  self._marshalers: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
@@ -2817,6 +2842,9 @@ class ObjMarshalerManager:
2817
2842
  non_strict_fields: bool = False,
2818
2843
  ) -> ObjMarshaler:
2819
2844
  if isinstance(ty, type):
2845
+ if (reg := self._registered_obj_marshalers.get(ty)) is not None:
2846
+ return reg
2847
+
2820
2848
  if abc.ABC in ty.__bases__:
2821
2849
  impls = [ity for ity in deep_subclasses(ty) if abc.ABC not in ity.__bases__] # type: ignore
2822
2850
  if all(ity.__qualname__.endswith(ty.__name__) for ity in impls):
@@ -2881,9 +2909,15 @@ class ObjMarshalerManager:
2881
2909
 
2882
2910
  #
2883
2911
 
2884
- def register_opj_marshaler(self, ty: ta.Any, m: ObjMarshaler) -> None:
2912
+ def set_obj_marshaler(
2913
+ self,
2914
+ ty: ta.Any,
2915
+ m: ObjMarshaler,
2916
+ *,
2917
+ override: bool = False,
2918
+ ) -> None:
2885
2919
  with self._lock:
2886
- if ty in self._obj_marshalers:
2920
+ if not override and ty in self._obj_marshalers:
2887
2921
  raise KeyError(ty)
2888
2922
  self._obj_marshalers[ty] = m
2889
2923
 
@@ -2974,7 +3008,7 @@ class ObjMarshalContext:
2974
3008
 
2975
3009
  OBJ_MARSHALER_MANAGER = ObjMarshalerManager()
2976
3010
 
2977
- register_opj_marshaler = OBJ_MARSHALER_MANAGER.register_opj_marshaler
3011
+ set_obj_marshaler = OBJ_MARSHALER_MANAGER.set_obj_marshaler
2978
3012
  get_obj_marshaler = OBJ_MARSHALER_MANAGER.get_obj_marshaler
2979
3013
 
2980
3014
  marshal_obj = OBJ_MARSHALER_MANAGER.marshal_obj
@@ -3259,6 +3293,7 @@ def read_config_file(
3259
3293
  cls: ta.Type[T],
3260
3294
  *,
3261
3295
  prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
3296
+ msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
3262
3297
  ) -> T:
3263
3298
  with open(path) as cf:
3264
3299
  config_dct = parse_config_file(os.path.basename(path), cf)
@@ -3266,7 +3301,7 @@ def read_config_file(
3266
3301
  if prepare is not None:
3267
3302
  config_dct = prepare(config_dct)
3268
3303
 
3269
- return unmarshal_obj(config_dct, cls)
3304
+ return msh.unmarshal_obj(config_dct, cls)
3270
3305
 
3271
3306
 
3272
3307
  def build_config_named_children(