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.

@@ -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
 
@@ -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
- _MagicApi.__call__(shim_or_builder, blob_root=blob_root, pipeline_id=pipeline_id),
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__.
@@ -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._func_config_path = full_name_and_callable(func)[0].replace("--", ".")
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._func_config_path)
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._func_config_path)
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._func_config_path)()
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._func_config_path)
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._func_config_path}', shim={self._shim_builder_or_off},"
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 = full_name_and_callable(func)[0].replace("--", ".")
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[fully_qualified_name] = to_shim_builder(shim_or_builder)
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[fully_qualified_name] = uris.to_lazy_uri(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[fully_qualified_name] = pipeline_id
161
- return Magic(func, config, calls)
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.20251021234412
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=wMwkw81FDOo2Ld9rEmtobZMW2ZCUmxWSI2Bp_p0pwa8,7151
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=kSlediIZQYsmeHB8plP6osjvUuSEVW4NWdY9ADia12Y,5094
43
- thds/mops/pure/_magic/sauce.py,sha256=xLaseOhoGgntmz6nZdj2te85sOaOiWExBLMGdeNmKsI,6870
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.20251021234412.dist-info/METADATA,sha256=QTTSJBqR1RAdWViv4J397KtVvEYHHja7hhImWPkVqnM,2225
113
- thds_mops-3.9.20251021234412.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
- thds_mops-3.9.20251021234412.dist-info/entry_points.txt,sha256=qKvCAaB80syXfxVR3xx6x9J0YJdaQWkIbVSw-NwFgMw,322
115
- thds_mops-3.9.20251021234412.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
116
- thds_mops-3.9.20251021234412.dist-info/RECORD,,
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,,