thds.mops 3.9.20251021234412__py3-none-any.whl → 3.9.20251023163418__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.
Potentially problematic release.
This version of thds.mops might be problematic. Click here for more details.
- thds/mops/_utils/config_tree.py +4 -0
- thds/mops/pure/_magic/api.py +29 -1
- thds/mops/pure/_magic/sauce.py +70 -12
- {thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/METADATA +1 -1
- {thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/RECORD +8 -8
- {thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/WHEEL +0 -0
- {thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/entry_points.txt +0 -0
- {thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/top_level.txt +0 -0
thds/mops/_utils/config_tree.py
CHANGED
|
@@ -154,6 +154,10 @@ class ConfigTree(ty.Generic[V]):
|
|
|
154
154
|
def __setitem__(self, key: str, value: V) -> None:
|
|
155
155
|
self.setv(value, pathable=key)
|
|
156
156
|
|
|
157
|
+
def __contains__(self, key: str) -> bool:
|
|
158
|
+
"""Only answers the specific question - does this exact key exist in the config?"""
|
|
159
|
+
return key in self.registry
|
|
160
|
+
|
|
157
161
|
def load_config(self, config: ty.Mapping[str, ty.Any]) -> None:
|
|
158
162
|
"""Loads things with an inner key matching this name into the config.
|
|
159
163
|
|
thds/mops/pure/_magic/api.py
CHANGED
|
@@ -50,6 +50,11 @@ class _MagicApi:
|
|
|
50
50
|
pipeline_id: str = "",
|
|
51
51
|
calls: ty.Collection[ty.Callable] = tuple(),
|
|
52
52
|
) -> ty.Callable[[ty.Callable[P, R]], sauce.Magic[P, R]]:
|
|
53
|
+
"""This is the main pure.magic() decorator. It is designed to be applied directly
|
|
54
|
+
at the site of function definition, i.e. on the `def`. We dynamically capture
|
|
55
|
+
the fully qualified name of the function being decorated and use that to
|
|
56
|
+
look up the appropriate 'magic' configuration at the time of each call to the function.
|
|
57
|
+
"""
|
|
53
58
|
return sauce.make_magic(_get_config(), shim_or_builder, blob_root, pipeline_id, calls)
|
|
54
59
|
|
|
55
60
|
@staticmethod
|
|
@@ -58,10 +63,29 @@ class _MagicApi:
|
|
|
58
63
|
*,
|
|
59
64
|
blob_root: uris.UriResolvable = "",
|
|
60
65
|
pipeline_id: str = "",
|
|
66
|
+
config_path: str = "",
|
|
61
67
|
) -> ty.Callable[[F], F]: # cleaner type for certain use cases
|
|
68
|
+
"""This alternative API is designed for more dynamic use cases - rather than
|
|
69
|
+
decorating a function def directly, you can use this to create a more generic
|
|
70
|
+
decorator that can be applied within other code (not at module-level).
|
|
71
|
+
|
|
72
|
+
However, you must never apply pure.magic.deco to the same function from multiple
|
|
73
|
+
places, as this means that you have multiple different uses sharing the same
|
|
74
|
+
configuration path, which will lead to subtle bugs.
|
|
75
|
+
|
|
76
|
+
We attempt to detect this and raise an error if it happens. If it does, you should
|
|
77
|
+
provide an explicit unique config_path for each usage.
|
|
78
|
+
"""
|
|
62
79
|
return ty.cast(
|
|
63
80
|
ty.Callable[[F], F],
|
|
64
|
-
|
|
81
|
+
sauce.make_magic(
|
|
82
|
+
_get_config(),
|
|
83
|
+
shim_or_builder,
|
|
84
|
+
blob_root=blob_root,
|
|
85
|
+
pipeline_id=pipeline_id,
|
|
86
|
+
calls=tuple(),
|
|
87
|
+
config_path=config_path,
|
|
88
|
+
),
|
|
65
89
|
)
|
|
66
90
|
|
|
67
91
|
@staticmethod
|
|
@@ -122,6 +146,10 @@ class _MagicApi:
|
|
|
122
146
|
m_config.blob_root.load_config(all_config)
|
|
123
147
|
m_config.pipeline_id.load_config(all_config)
|
|
124
148
|
|
|
149
|
+
@staticmethod
|
|
150
|
+
def config_path(func: ty.Callable) -> str:
|
|
151
|
+
return sauce.make_magic_config_path(func)
|
|
152
|
+
|
|
125
153
|
|
|
126
154
|
magic: ty.Final = _MagicApi()
|
|
127
155
|
# we only instantiate this so we can have a call to magic() that is not __init__.
|
thds/mops/pure/_magic/sauce.py
CHANGED
|
@@ -6,7 +6,7 @@ import typing as ty
|
|
|
6
6
|
|
|
7
7
|
from typing_extensions import ParamSpec
|
|
8
8
|
|
|
9
|
-
from thds.core import futures, stack_context
|
|
9
|
+
from thds.core import futures, log, stack_context
|
|
10
10
|
from thds.mops._utils import config_tree
|
|
11
11
|
|
|
12
12
|
from ..core import file_blob_store, pipeline_id, pipeline_id_mask, uris
|
|
@@ -38,6 +38,8 @@ class _MagicConfig:
|
|
|
38
38
|
self.shim_bld[""] = make_builder(samethread_shim) # default Shim
|
|
39
39
|
self.pipeline_id[""] = "magic" # default pipeline_id
|
|
40
40
|
|
|
41
|
+
self.all_registered_paths: set[str] = set()
|
|
42
|
+
|
|
41
43
|
def __repr__(self) -> str:
|
|
42
44
|
return f"MagicConfig(shim_bld={self.shim_bld}, blob_root={self.blob_root}, pipeline_id={self.pipeline_id})"
|
|
43
45
|
|
|
@@ -68,15 +70,17 @@ class Magic(ty.Generic[P, R]):
|
|
|
68
70
|
self,
|
|
69
71
|
func: ty.Callable[P, R],
|
|
70
72
|
config: _MagicConfig,
|
|
73
|
+
magic_config_path: str,
|
|
71
74
|
calls: ty.Collection[ty.Callable] = frozenset(),
|
|
72
75
|
):
|
|
73
76
|
functools.update_wrapper(self, func)
|
|
74
|
-
self.
|
|
77
|
+
self._magic_config_path = magic_config_path
|
|
75
78
|
|
|
76
79
|
self.config = config
|
|
80
|
+
|
|
77
81
|
if p_id := pipeline_id_mask.extract_from_docstr(func, require=False):
|
|
78
82
|
# this allows the docstring pipeline id to become 'the most specific' config.
|
|
79
|
-
self.config.pipeline_id.setv(p_id, self.
|
|
83
|
+
self.config.pipeline_id.setv(p_id, self._magic_config_path)
|
|
80
84
|
self._shim = stack_context.StackContext[ty.Union[None, ShimName, ShimOrBuilder]](
|
|
81
85
|
str(func) + "_SHIM", None # none means nothing has been set stack-local
|
|
82
86
|
)
|
|
@@ -104,7 +108,7 @@ class Magic(ty.Generic[P, R]):
|
|
|
104
108
|
def _shim_builder_or_off(self) -> ty.Optional[ShimBuilder]:
|
|
105
109
|
if stack_local_shim := self._shim():
|
|
106
110
|
return to_shim_builder(stack_local_shim)
|
|
107
|
-
return self.config.shim_bld.getv(self.
|
|
111
|
+
return self.config.shim_bld.getv(self._magic_config_path)
|
|
108
112
|
|
|
109
113
|
def _is_off(self) -> bool:
|
|
110
114
|
return self._shim_builder_or_off is None
|
|
@@ -117,11 +121,11 @@ class Magic(ty.Generic[P, R]):
|
|
|
117
121
|
return sb(f, args, kwargs)
|
|
118
122
|
|
|
119
123
|
def _get_blob_root(self) -> str:
|
|
120
|
-
return self.config.blob_root.getv(self.
|
|
124
|
+
return self.config.blob_root.getv(self._magic_config_path)()
|
|
121
125
|
|
|
122
126
|
@property
|
|
123
127
|
def _pipeline_id(self) -> str:
|
|
124
|
-
return self.config.pipeline_id.getv(self.
|
|
128
|
+
return self.config.pipeline_id.getv(self._magic_config_path)
|
|
125
129
|
|
|
126
130
|
def submit(self, *args: P.args, **kwargs: P.kwargs) -> futures.PFuture[R]:
|
|
127
131
|
"""A futures-based interface that doesn't block on the result of the wrapped
|
|
@@ -138,26 +142,80 @@ class Magic(ty.Generic[P, R]):
|
|
|
138
142
|
|
|
139
143
|
def __repr__(self) -> str:
|
|
140
144
|
return (
|
|
141
|
-
f"Magic('{self.
|
|
145
|
+
f"Magic('{self._magic_config_path}', shim={self._shim_builder_or_off},"
|
|
142
146
|
f" blob_root='{self._get_blob_root()}', pipeline_id='{self._pipeline_id}')"
|
|
143
147
|
)
|
|
144
148
|
|
|
145
149
|
|
|
150
|
+
def make_magic_config_path(func: ty.Callable) -> str:
|
|
151
|
+
return full_name_and_callable(func)[0].replace("--", ".")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class MagicReregistrationError(ValueError):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
|
|
146
158
|
def make_magic(
|
|
147
159
|
config: _MagicConfig,
|
|
148
160
|
shim_or_builder: ty.Union[ShimName, ShimOrBuilder, None],
|
|
149
161
|
blob_root: uris.UriResolvable,
|
|
150
162
|
pipeline_id: str,
|
|
151
163
|
calls: ty.Collection[ty.Callable],
|
|
164
|
+
*,
|
|
165
|
+
config_path: str = "",
|
|
152
166
|
) -> ty.Callable[[ty.Callable[P, R]], Magic[P, R]]:
|
|
167
|
+
"""config_path is a dot-separated path that must be unique throughout your application.
|
|
168
|
+
|
|
169
|
+
By default it will be set to the thds.other.module.function_name of the decorated function.
|
|
170
|
+
"""
|
|
171
|
+
error_logger = log.auto(__name__, "thds.mops.pure._magic.api").error
|
|
172
|
+
err_msg = (
|
|
173
|
+
"You are probably using pure.magic(.deco) from multiple places on the same function. You will need to specify a unique config_path for each usage."
|
|
174
|
+
if not config_path
|
|
175
|
+
else f"You supplied a config_path ({config_path}) but you reused the decorator on different functions with the same config_path."
|
|
176
|
+
)
|
|
177
|
+
err_msg += " See the comment in mops.pure._magic.sauce for more details."
|
|
178
|
+
|
|
179
|
+
def must_not_remagic_same_func(msg: str) -> None:
|
|
180
|
+
error_logger(f"{msg}; {err_msg}")
|
|
181
|
+
# if you see either of the above messages, consider whether you really need the magic
|
|
182
|
+
# configurability of pure.magic, or whether it might be better to instantiate and use
|
|
183
|
+
# MemoizingPicklingRunner directly without configurability. The reason overwriting
|
|
184
|
+
# configs, by applying pure.magic to the same callable from more than one location is
|
|
185
|
+
# disallowed is that you will get 'spooky action at a distance' between different parts
|
|
186
|
+
# of your application that are overwriting the base config for the same function.
|
|
187
|
+
# Another approach would be to use a wrapper `def` with a static @pure.magic decorator
|
|
188
|
+
# on it that calls the inner function, so that they are completely different functions
|
|
189
|
+
# as far as pure.magic is concerned.
|
|
190
|
+
raise MagicReregistrationError(msg)
|
|
191
|
+
|
|
192
|
+
magic_config_path_cache: set[str] = set()
|
|
193
|
+
# the reason for this cache is that there are cases where you may want to apply the _exact
|
|
194
|
+
# same_ base config to the same function multiple times - just for ease of use. And
|
|
195
|
+
# since this is the exact same config, we should allow it and treat it as though you
|
|
196
|
+
# had only applied it once. Of course, if you later try to configure these
|
|
197
|
+
# applications separately, it won't work - these _are_ the same magic config path, so
|
|
198
|
+
# they're bound together via that config.
|
|
199
|
+
|
|
153
200
|
def deco(func: ty.Callable[P, R]) -> Magic[P, R]:
|
|
154
|
-
fully_qualified_name =
|
|
201
|
+
fully_qualified_name = make_magic_config_path(func)
|
|
202
|
+
magic_config_path = config_path or fully_qualified_name
|
|
203
|
+
|
|
204
|
+
def deco_being_reapplied_to_same_func() -> bool:
|
|
205
|
+
return fully_qualified_name in magic_config_path_cache
|
|
206
|
+
|
|
207
|
+
if magic_config_path in config.all_registered_paths and not deco_being_reapplied_to_same_func():
|
|
208
|
+
must_not_remagic_same_func(f"Cannot re-register {magic_config_path} using pure.magic")
|
|
209
|
+
|
|
155
210
|
if shim_or_builder is not None:
|
|
156
|
-
config.shim_bld[
|
|
211
|
+
config.shim_bld[magic_config_path] = to_shim_builder(shim_or_builder)
|
|
157
212
|
if blob_root: # could be empty string
|
|
158
|
-
config.blob_root[
|
|
213
|
+
config.blob_root[magic_config_path] = uris.to_lazy_uri(blob_root)
|
|
159
214
|
if pipeline_id: # could be empty string
|
|
160
|
-
config.pipeline_id[
|
|
161
|
-
|
|
215
|
+
config.pipeline_id[magic_config_path] = pipeline_id
|
|
216
|
+
|
|
217
|
+
magic_config_path_cache.add(fully_qualified_name)
|
|
218
|
+
config.all_registered_paths.add(magic_config_path)
|
|
219
|
+
return Magic(func, config, magic_config_path, calls)
|
|
162
220
|
|
|
163
221
|
return deco
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thds.mops
|
|
3
|
-
Version: 3.9.
|
|
3
|
+
Version: 3.9.20251023163418
|
|
4
4
|
Summary: ML Ops tools for Trilliant Health
|
|
5
5
|
Author-email: Trilliant Health <info@trillianthealth.com>
|
|
6
6
|
Project-URL: Repository, https://github.com/TrilliantHealth/ds-monorepo
|
|
@@ -5,7 +5,7 @@ thds/mops/config.py,sha256=T62YskXvzAfxNgpq2jMatHgoIHfRV_z4cvJ8Rl_TZ6E,2015
|
|
|
5
5
|
thds/mops/parallel.py,sha256=F6vUhSTO--CY82vyYtWFtspmgd0RxoxQ_EUrCnTm93Q,1039
|
|
6
6
|
thds/mops/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
thds/mops/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
thds/mops/_utils/config_tree.py,sha256=
|
|
8
|
+
thds/mops/_utils/config_tree.py,sha256=Q9mPAAolYPqKe6dkvfetg-wmVARyD7WOx123OLQ8_sU,7326
|
|
9
9
|
thds/mops/_utils/exception.py,sha256=Itj6ceieCdGrKZ2JdW_DIM88Wgvvw104cfbH1RNn6Go,394
|
|
10
10
|
thds/mops/_utils/locked_cache.py,sha256=ROIkwu-_FcXlNyCreWQeE5cyL9XrNW7drWsolTgeajM,2523
|
|
11
11
|
thds/mops/_utils/names.py,sha256=tPPaXCyduUXqmbdvIg3ygevERnKM3YIs868BeaKX5XY,824
|
|
@@ -39,8 +39,8 @@ thds/mops/k8s/tools/krsync.py,sha256=us7pXX0-bRMwD2oAno7Z6BJcPs6FgaUabHW0STyQJYg
|
|
|
39
39
|
thds/mops/k8s/tools/krsync.sh,sha256=fWgwkdzWnJeTbzEA_uBiIIi-bNU4nXAYj3dNovyRluU,747
|
|
40
40
|
thds/mops/pure/__init__.py,sha256=3xLimQ2JWdeq1YgPs7bPwlwOspzPRwaR2w2KX7vfJU0,1624
|
|
41
41
|
thds/mops/pure/_magic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
thds/mops/pure/_magic/api.py,sha256=
|
|
43
|
-
thds/mops/pure/_magic/sauce.py,sha256=
|
|
42
|
+
thds/mops/pure/_magic/api.py,sha256=eG1NEl9FR_FE1yLfN0hiLd0uLWu-gKzw1FCIJR5pMbw,6424
|
|
43
|
+
thds/mops/pure/_magic/sauce.py,sha256=BfzQfEnQarcLuTbKyCKLU8F6BtJ-v6kn1SRIf675cTc,9804
|
|
44
44
|
thds/mops/pure/_magic/shims.py,sha256=CXN8wlHv039oKRzDtp5YFDlwGXmmaheWLCi2I95gSeM,1212
|
|
45
45
|
thds/mops/pure/adls/__init__.py,sha256=fw67xxwnizBurScMa-_zWb94lo5gamEVRt27V4bR0jc,54
|
|
46
46
|
thds/mops/pure/adls/_files.py,sha256=9m35Y4elWF0DjgAXVp4oi5CaY6fXWt8n67PilWxWJns,821
|
|
@@ -109,8 +109,8 @@ thds/mops/pure/tools/summarize/cli.py,sha256=7kDtn24ok8oBO3jFjlMmOK3jnZYpMoE_5Y8
|
|
|
109
109
|
thds/mops/pure/tools/summarize/run_summary.py,sha256=glEN_YxUGADzp2Ofvr4ZDeHvnZ1znNR7HD7EATn1sPI,5644
|
|
110
110
|
thds/mops/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
111
111
|
thds/mops/testing/deferred_imports.py,sha256=f0ezCgQAtzTqW1yAOb0OWgsB9ZrlztLB894LtpWDaVw,3780
|
|
112
|
-
thds_mops-3.9.
|
|
113
|
-
thds_mops-3.9.
|
|
114
|
-
thds_mops-3.9.
|
|
115
|
-
thds_mops-3.9.
|
|
116
|
-
thds_mops-3.9.
|
|
112
|
+
thds_mops-3.9.20251023163418.dist-info/METADATA,sha256=tazNjfQKEr0nFkGcn5FarA96_iLdpUxywauSIBV6vlk,2225
|
|
113
|
+
thds_mops-3.9.20251023163418.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
114
|
+
thds_mops-3.9.20251023163418.dist-info/entry_points.txt,sha256=qKvCAaB80syXfxVR3xx6x9J0YJdaQWkIbVSw-NwFgMw,322
|
|
115
|
+
thds_mops-3.9.20251023163418.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
116
|
+
thds_mops-3.9.20251023163418.dist-info/RECORD,,
|
|
File without changes
|
{thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{thds_mops-3.9.20251021234412.dist-info → thds_mops-3.9.20251023163418.dist-info}/top_level.txt
RENAMED
|
File without changes
|