pluto-ml 0.0.22__tar.gz → 0.0.23__tar.gz

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.
Files changed (45) hide show
  1. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/PKG-INFO +1 -1
  2. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/__init__.py +2 -2
  3. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/api.py +7 -3
  4. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/wandb.py +3 -1
  5. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/init.py +8 -2
  6. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/op.py +9 -1
  7. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/util.py +78 -0
  8. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pyproject.toml +2 -1
  9. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/LICENSE +0 -0
  10. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/README.md +0 -0
  11. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/__init__.py +0 -0
  12. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/__main__.py +0 -0
  13. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/compat/__init__.py +0 -0
  14. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/compat/lightning.py +0 -0
  15. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/compat/neptune.py +0 -0
  16. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/compat/torch.py +0 -0
  17. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/mlop/compat/transformers.py +0 -0
  18. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/__main__.py +0 -0
  19. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/_wandb_hook.py +0 -0
  20. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/auth.py +0 -0
  21. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/__init__.py +0 -0
  22. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/_utils.py +0 -0
  23. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/lightning.py +0 -0
  24. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/neptune.py +0 -0
  25. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/neptune_query/__init__.py +0 -0
  26. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/neptune_query/filters.py +0 -0
  27. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/neptune_query/runs.py +0 -0
  28. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/torch.py +0 -0
  29. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/compat/transformers.py +0 -0
  30. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/data.py +0 -0
  31. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/file.py +0 -0
  32. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/iface.py +0 -0
  33. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/log.py +0 -0
  34. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/query.py +0 -0
  35. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sanitize.py +0 -0
  36. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sentry.py +0 -0
  37. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sets.py +0 -0
  38. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/store.py +0 -0
  39. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sync/__init__.py +0 -0
  40. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sync/__main__.py +0 -0
  41. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sync/process.py +0 -0
  42. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sync/retry.py +0 -0
  43. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sync/store.py +0 -0
  44. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/pluto/sys.py +0 -0
  45. {pluto_ml-0.0.22 → pluto_ml-0.0.23}/zzzz_pluto_wandb_hook.pth +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pluto-ml
3
- Version: 0.0.22
3
+ Version: 0.0.23
4
4
  Summary: Pluto ML - Machine Learning Operations Framework
5
5
  License-File: LICENSE
6
6
  Author: jqssun
@@ -41,11 +41,11 @@ __all__ = (
41
41
  'generate_run_id',
42
42
  )
43
43
 
44
- __version__ = '0.0.22'
44
+ __version__ = '0.0.23'
45
45
 
46
46
 
47
47
  # Replaced with the current commit when building the wheels.
48
- _PLUTO_COMMIT_SHA = '4587211b1c6ccebe92f92d1243bf9a213ec1f3dd'
48
+ _PLUTO_COMMIT_SHA = 'b279ceb6e63cc782fe7f3d6a3513cc97924deefe'
49
49
 
50
50
 
51
51
  def _get_git_commit():
@@ -4,7 +4,7 @@ import re
4
4
  import signal
5
5
  from datetime import datetime
6
6
 
7
- from .util import clean_dict, find_node
7
+ from .util import clean_dict, config_json_default, find_node
8
8
 
9
9
  logger = logging.getLogger(f'{__name__.split(".")[0]}')
10
10
  tag = 'API'
@@ -40,7 +40,9 @@ def make_compat_start_v1(config, settings, info, tags=None):
40
40
  'runName': settings._op_name,
41
41
  'projectName': settings.project,
42
42
  'externalId': settings._external_id, # User-provided run ID for multi-node
43
- 'config': json.dumps(config) if config is not None else None,
43
+ 'config': json.dumps(config, default=config_json_default)
44
+ if config is not None
45
+ else None,
44
46
  'loggerSettings': json.dumps(clean_dict(settings.to_dict())),
45
47
  'systemMetadata': json.dumps(info) if info is not None else None,
46
48
  'tags': tags if tags else None,
@@ -97,7 +99,9 @@ def make_compat_update_config_v1(settings, config):
97
99
  return json.dumps(
98
100
  {
99
101
  'runId': settings._op_id,
100
- 'config': json.dumps(config) if config else None,
102
+ 'config': json.dumps(config, default=config_json_default)
103
+ if config
104
+ else None,
101
105
  }
102
106
  ).encode()
103
107
 
@@ -851,7 +851,9 @@ def _make_patched_init(original_init, wandb_module):
851
851
  f'wandb will continue to work normally, but NO DATA will be '
852
852
  f'sent to Pluto. To fix, resolve the error above and retry.'
853
853
  )
854
- logger.error(_msg)
854
+ # exc_info=True attaches the traceback so the log points at the
855
+ # raise site (e.g. the failing json.dumps), not just this handler.
856
+ logger.error(_msg, exc_info=True)
855
857
  # Also print to stderr so it shows up even if logging is not configured
856
858
  import sys
857
859
 
@@ -7,7 +7,7 @@ import pluto
7
7
  from . import sentry as _sentry
8
8
  from .op import Op
9
9
  from .sets import Settings, _classify_run_id, _is_display_id, setup
10
- from .util import deep_merge, gen_id, get_char
10
+ from .util import deep_merge, gen_id, get_char, to_native_config
11
11
 
12
12
  logger = logging.getLogger(f'{__name__.split(".")[0]}')
13
13
  tag = 'Init'
@@ -194,6 +194,12 @@ def init(
194
194
  if inherit_tags is not None:
195
195
  settings._inherit_tags = inherit_tags
196
196
 
197
+ # Normalize the config to JSON-native types up front (e.g. OmegaConf
198
+ # DictConfig -> dict, resolving interpolations). Done before the fork
199
+ # deep-merge below so its `isinstance(config, dict)` check works, and so
200
+ # everything downstream (storage, serialization) sees clean native data.
201
+ config = to_native_config(config)
202
+
197
203
  # Deep-merge inherited parent config with user config (client-side).
198
204
  # The server only does a shallow merge, so we fetch the parent config,
199
205
  # deep-merge locally, and disable server-side inheritance.
@@ -240,7 +246,7 @@ def init(
240
246
  return op
241
247
  except Exception as e:
242
248
  _sentry.capture_exception(e)
243
- logger.critical('%s: failed, %s', tag, e) # add early logger
249
+ logger.critical('%s: failed, %s', tag, e, exc_info=True) # add early logger
244
250
  raise e
245
251
 
246
252
 
@@ -32,7 +32,14 @@ from .store import DataStore
32
32
  from .sync import SyncProcessManager
33
33
  from .sync.store import HEALTH_METRIC_KEYS
34
34
  from .sys import System
35
- from .util import deep_merge, get_char, get_val, print_url, to_json
35
+ from .util import (
36
+ deep_merge,
37
+ get_char,
38
+ get_val,
39
+ print_url,
40
+ to_json,
41
+ to_native_config,
42
+ )
36
43
 
37
44
  logger = logging.getLogger(f'{__name__.split(".")[0]}')
38
45
  tag = 'Operation'
@@ -888,6 +895,7 @@ class Op:
888
895
  run.update_config({'epochs': 100})
889
896
  run.update_config({'lr': 0.01, 'model': 'resnet50'})
890
897
  """
898
+ config = to_native_config(config)
891
899
  if self.config is None:
892
900
  self.config = {}
893
901
  self.config = deep_merge(self.config, config)
@@ -17,6 +17,11 @@ import numpy as np
17
17
 
18
18
  from .sets import get_console
19
19
 
20
+ try:
21
+ from omegaconf import OmegaConf
22
+ except ImportError: # optional dependency; degrade gracefully when absent
23
+ OmegaConf = None # type: ignore[misc, assignment]
24
+
20
25
  logger = logging.getLogger(f'{__name__.split(".")[0]}')
21
26
  tag = 'Util'
22
27
 
@@ -419,6 +424,79 @@ def clean_dict(d):
419
424
  return c
420
425
 
421
426
 
427
+ def _is_omegaconf(obj: Any) -> bool:
428
+ """True if ``obj`` is an OmegaConf node (DictConfig/ListConfig).
429
+
430
+ Returns False when omegaconf isn't installed. Any failure (broken install)
431
+ is also treated as "not omegaconf" so a degraded dependency can never crash
432
+ a caller that doesn't even use OmegaConf.
433
+ """
434
+ if OmegaConf is None:
435
+ return False
436
+ try:
437
+ return OmegaConf.is_config(obj)
438
+ except Exception:
439
+ return False
440
+
441
+
442
+ def _omegaconf_to_container(obj: Any) -> Any:
443
+ """Convert an OmegaConf node to a native container, degrading on failure.
444
+
445
+ Prefer fully-resolved values, but ``resolve=True`` raises on unresolvable
446
+ or cyclic interpolations (e.g. an unset ``${oc.env:VAR}``). Rather than let
447
+ that crash init or disable logging, fall back to the unresolved structure
448
+ (interpolations kept as literal ``${...}`` strings), and finally to a plain
449
+ string if even that fails. Callers must already have checked
450
+ :func:`_is_omegaconf`.
451
+ """
452
+ if OmegaConf is None: # pragma: no cover - guarded by _is_omegaconf
453
+ return str(obj)
454
+ try:
455
+ return OmegaConf.to_container(obj, resolve=True)
456
+ except Exception:
457
+ try:
458
+ return OmegaConf.to_container(obj, resolve=False)
459
+ except Exception:
460
+ return str(obj)
461
+
462
+
463
+ def to_native_config(obj: Any) -> Any:
464
+ """Recursively convert a run config into JSON-native Python types.
465
+
466
+ Handles OmegaConf ``DictConfig`` / ``ListConfig`` at any nesting depth,
467
+ including the common ``dict(cfg)`` shape where the top-level is a plain
468
+ ``dict`` but nested values are still OmegaConf nodes. OmegaConf nodes are
469
+ deep-converted, resolving interpolations (``${...}``) where possible and
470
+ degrading gracefully when they can't be resolved (see
471
+ :func:`_omegaconf_to_container`). Plain data (dicts, lists, scalars,
472
+ ``None``) passes through unchanged. Non-string dict keys are coerced to
473
+ ``str`` (note: distinct keys with the same string form collapse, matching
474
+ JSON's own key semantics).
475
+ """
476
+ if _is_omegaconf(obj):
477
+ # _omegaconf_to_container already does a deep conversion to native
478
+ # types, so return its result rather than re-walking the whole tree.
479
+ return _omegaconf_to_container(obj)
480
+ if isinstance(obj, dict):
481
+ return {str(k): to_native_config(v) for k, v in obj.items()}
482
+ if isinstance(obj, (list, tuple)):
483
+ return [to_native_config(v) for v in obj]
484
+ return obj
485
+
486
+
487
+ def config_json_default(o: Any) -> Any:
488
+ """``json.dumps(default=...)`` hook for config payloads.
489
+
490
+ Last line of defense so a single non-serializable config value can never
491
+ raise and silently disable logging. OmegaConf nodes are deep-converted
492
+ (resolving interpolations where possible, see
493
+ :func:`_omegaconf_to_container`); anything else degrades to its string form.
494
+ """
495
+ if _is_omegaconf(o):
496
+ return _omegaconf_to_container(o)
497
+ return str(o)
498
+
499
+
422
500
  def dict_to_json(data: Dict[str, Any]) -> Dict[str, Any]:
423
501
  for key in list(data): # avoid RuntimeError if dict size changes
424
502
  val = data[key]
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pluto-ml"
3
- version = "0.0.22"
3
+ version = "0.0.23"
4
4
  description = "Pluto ML - Machine Learning Operations Framework"
5
5
  packages = [
6
6
  {include = "pluto"},
@@ -48,6 +48,7 @@ matplotlib = "^3.10"
48
48
  torchvision = "*"
49
49
  imageio = "^2.37.0"
50
50
  moviepy = "^1.0.3"
51
+ omegaconf = "^2.3"
51
52
 
52
53
 
53
54
  [tool.poetry.extras]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes