ominfra 0.0.0.dev175__py3-none-any.whl → 0.0.0.dev177__py3-none-any.whl

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