sagemaker-hyperpod 3.0.2__tar.gz → 3.1.0__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 (75) hide show
  1. {sagemaker_hyperpod-3.0.2/src/sagemaker_hyperpod.egg-info → sagemaker_hyperpod-3.1.0}/PKG-INFO +1 -1
  2. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/pyproject.toml +1 -1
  3. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/setup.py +1 -1
  4. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/training.py +8 -9
  5. sagemaker_hyperpod-3.1.0/src/sagemaker/hyperpod/cli/common_utils.py +71 -0
  6. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/inference_utils.py +7 -11
  7. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/training_utils.py +8 -31
  8. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/config/metadata.py +4 -0
  9. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/utils.py +168 -3
  10. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_endpoint_base.py +8 -4
  11. sagemaker_hyperpod-3.1.0/src/sagemaker/hyperpod/inference/jumpstart_public_hub_visualization_utils.py +301 -0
  12. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/hyperpod_pytorch_job.py +12 -9
  13. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0/src/sagemaker_hyperpod.egg-info}/PKG-INFO +1 -1
  14. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/SOURCES.txt +2 -0
  15. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/LICENSE +0 -0
  16. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/NOTICE +0 -0
  17. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/README.md +0 -0
  18. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/setup.cfg +0 -0
  19. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/__init__.py +0 -0
  20. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/__init__.py +0 -0
  21. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/__init__.py +0 -0
  22. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/clients/__init__.py +0 -0
  23. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/clients/kubernetes_client.py +0 -0
  24. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/__init__.py +0 -0
  25. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/cluster.py +0 -0
  26. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/inference.py +0 -0
  27. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/__init__.py +0 -0
  28. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/command_constants.py +0 -0
  29. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/exception_constants.py +0 -0
  30. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/hyperpod_instance_types.py +0 -0
  31. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/kueue_constants.py +0 -0
  32. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/pytorch_constants.py +0 -0
  33. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/hyp_cli.py +0 -0
  34. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/__init__.py +0 -0
  35. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/cancel_training_job.py +0 -0
  36. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/discover_namespaces.py +0 -0
  37. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/exec_command.py +0 -0
  38. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_logs.py +0 -0
  39. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_namespaces.py +0 -0
  40. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_training_job.py +0 -0
  41. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/list_pods.py +0 -0
  42. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/list_training_jobs.py +0 -0
  43. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/self_subject_access_review.py +0 -0
  44. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/templates/__init__.py +0 -0
  45. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/templates/k8s_pytorch_job_template.py +0 -0
  46. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/utils.py +0 -0
  47. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/__init__.py +0 -0
  48. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/cluster_validator.py +0 -0
  49. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/job_validator.py +0 -0
  50. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/validator.py +0 -0
  51. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/__init__.py +0 -0
  52. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/config/__init__.py +0 -0
  53. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/__init__.py +0 -0
  54. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/constants.py +0 -0
  55. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/telemetry_logging.py +0 -0
  56. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/user_agent.py +0 -0
  57. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/__init__.py +0 -0
  58. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/__init__.py +0 -0
  59. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/constants.py +0 -0
  60. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/hp_endpoint_config.py +0 -0
  61. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/hp_jumpstart_endpoint_config.py +0 -0
  62. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_endpoint.py +0 -0
  63. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py +0 -0
  64. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/MonitoringConfig.py +0 -0
  65. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/__init__.py +0 -0
  66. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/constants.py +0 -0
  67. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/utils.py +0 -0
  68. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/__init__.py +0 -0
  69. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/config/__init__.py +0 -0
  70. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/config/hyperpod_pytorch_job_unified_config.py +0 -0
  71. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/dependency_links.txt +0 -0
  72. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/entry_points.txt +0 -0
  73. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/requires.txt +0 -0
  74. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/top_level.txt +0 -0
  75. {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/zip-safe +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sagemaker-hyperpod
3
- Version: 3.0.2
3
+ Version: 3.1.0
4
4
  Summary: Amazon SageMaker HyperPod SDK and CLI
5
5
  Home-page: https://github.com/aws/sagemaker-hyperpod-cli
6
6
  Author: Amazon Web Services
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  dynamic = ["dependencies"]
7
7
  name = "sagemaker-hyperpod"
8
- version = "3.0.2"
8
+ version = "3.1.0"
9
9
  description = "Amazon SageMaker HyperPod SDK and CLI"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.8"
@@ -47,7 +47,7 @@ for root, dirs, files in os.walk(
47
47
  setup(
48
48
  data_files=sagemaker_hyperpod_recipes,
49
49
  name="sagemaker-hyperpod",
50
- version="3.0.2",
50
+ version="3.1.0",
51
51
  description="Amazon SageMaker HyperPod SDK and CLI",
52
52
  long_description=open("README.md").read(),
53
53
  long_description_content_type="text/markdown",
@@ -1,16 +1,7 @@
1
1
  import click
2
- import logging
3
- import os
4
- import yaml
5
- import shutil
6
- import subprocess
7
- from pathlib import Path
8
2
  from sagemaker.hyperpod.training.hyperpod_pytorch_job import HyperPodPytorchJob
9
3
  from sagemaker.hyperpod.common.config import Metadata
10
- import tempfile
11
- from typing import List, Dict, Any, Optional, Callable, get_args, get_origin, Literal
12
4
  from sagemaker.hyperpod.cli.training_utils import generate_click_command
13
- from importlib.metadata import entry_points
14
5
  from hyperpod_pytorch_job_template.registry import SCHEMA_REGISTRY
15
6
  from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
16
7
  _hyperpod_telemetry_emitter,
@@ -33,11 +24,17 @@ def pytorch_create(version, debug, config):
33
24
  job_name = config.get("name")
34
25
  namespace = config.get("namespace")
35
26
  spec = config.get("spec")
27
+ metadata_labels = config.get("labels")
28
+ annotations = config.get("annotations")
36
29
 
37
30
  # Prepare metadata
38
31
  metadata_kwargs = {"name": job_name}
39
32
  if namespace:
40
33
  metadata_kwargs["namespace"] = namespace
34
+ if metadata_labels:
35
+ metadata_kwargs["labels"] = metadata_labels
36
+ if annotations:
37
+ metadata_kwargs["annotations"] = annotations
41
38
 
42
39
  # Prepare job kwargs
43
40
  job_kwargs = {
@@ -163,6 +160,8 @@ def pytorch_describe(job_name: str, namespace: str):
163
160
  click.echo("=" * 80)
164
161
  click.echo(f"Name: {job.metadata.name}")
165
162
  click.echo(f"Namespace: {job.metadata.namespace}")
163
+ click.echo(f"Labels: {job.metadata.labels}")
164
+ click.echo(f"Annotations: {job.metadata.annotations}")
166
165
 
167
166
  # Print Spec details
168
167
  click.echo("\nSpec:")
@@ -0,0 +1,71 @@
1
+ import sys
2
+ from typing import Mapping, Type
3
+ import click
4
+ import pkgutil
5
+ import json
6
+
7
+ JUMPSTART_SCHEMA = "hyperpod_jumpstart_inference_template"
8
+ CUSTOM_SCHEMA = "hyperpod_custom_inference_template"
9
+ JUMPSTART_COMMAND = "hyp-jumpstart-endpoint"
10
+ CUSTOM_COMMAND = "hyp-custom-endpoint"
11
+ PYTORCH_SCHEMA="hyperpod_pytorch_job_template"
12
+ PYTORCH_COMMAND="hyp-pytorch-job"
13
+
14
+
15
+ def extract_version_from_args(registry: Mapping[str, Type], schema_pkg: str, default: str) -> str:
16
+ if "--version" not in sys.argv:
17
+ return default
18
+
19
+ idx = sys.argv.index("--version")
20
+ if idx + 1 >= len(sys.argv):
21
+ return default
22
+
23
+ requested_version = sys.argv[idx + 1]
24
+ invoked_command = next(
25
+ (arg for arg in sys.argv if arg.startswith('hyp-')),
26
+ None
27
+ )
28
+
29
+ # Check if schema validation is needed
30
+ needs_validation = (
31
+ (schema_pkg == JUMPSTART_SCHEMA and invoked_command == JUMPSTART_COMMAND) or
32
+ (schema_pkg == CUSTOM_SCHEMA and invoked_command == CUSTOM_COMMAND) or
33
+ (schema_pkg == PYTORCH_SCHEMA and invoked_command == PYTORCH_COMMAND)
34
+ )
35
+
36
+ if registry is not None and requested_version not in registry:
37
+ if needs_validation:
38
+ raise click.ClickException(f"Unsupported schema version: {requested_version}")
39
+ else:
40
+ return default
41
+
42
+ return requested_version
43
+
44
+
45
+ def get_latest_version(registry: Mapping[str, Type]) -> str:
46
+ """
47
+ Get the latest version from the schema registry.
48
+ """
49
+ if not registry:
50
+ raise ValueError("Schema registry is empty")
51
+
52
+ # Sort versions and return the last (highest) one
53
+ sorted_versions = sorted(registry.keys(), key=lambda v: [int(x) for x in v.split('.')])
54
+ return sorted_versions[-1]
55
+
56
+
57
+ def load_schema_for_version(
58
+ version: str,
59
+ base_package: str,
60
+ ) -> dict:
61
+ """
62
+ Load schema.json from the top-level <base_package>.vX_Y_Z package.
63
+ """
64
+ ver_pkg = f"{base_package}.v{version.replace('.', '_')}"
65
+ raw = pkgutil.get_data(ver_pkg, "schema.json")
66
+ if raw is None:
67
+ raise click.ClickException(
68
+ f"Could not load schema.json for version {version} "
69
+ f"(looked in package {ver_pkg})"
70
+ )
71
+ return json.loads(raw)
@@ -2,25 +2,21 @@ import json
2
2
  import pkgutil
3
3
  import click
4
4
  from typing import Callable, Optional, Mapping, Type
5
-
6
-
7
- def load_schema_for_version(version: str, schema_pkg: str) -> dict:
8
- ver_pkg = f"{schema_pkg}.v{version.replace('.', '_')}"
9
- raw = pkgutil.get_data(ver_pkg, "schema.json")
10
- if raw is None:
11
- raise click.ClickException(f"Could not load schema.json for version {version}")
12
- return json.loads(raw)
5
+ import sys
6
+ from sagemaker.hyperpod.cli.common_utils import extract_version_from_args, get_latest_version, load_schema_for_version
13
7
 
14
8
 
15
9
  def generate_click_command(
16
10
  *,
17
- version_key: Optional[str] = None,
18
11
  schema_pkg: str = "hyperpod_jumpstart_inference_template",
19
12
  registry: Mapping[str, Type] = None,
20
13
  ) -> Callable:
21
14
  if registry is None:
22
15
  raise ValueError("You must pass a registry mapping version→Model")
23
16
 
17
+ default_version = get_latest_version(registry)
18
+ version = extract_version_from_args(registry, schema_pkg, default_version)
19
+
24
20
  def decorator(func: Callable) -> Callable:
25
21
  # Parser for the single JSON‐dict env var flag
26
22
  def _parse_json_flag(ctx, param, value):
@@ -34,7 +30,7 @@ def generate_click_command(
34
30
  # 1) the wrapper click actually invokes
35
31
  def wrapped_func(*args, **kwargs):
36
32
  namespace = kwargs.pop("namespace", None)
37
- version = version_key or kwargs.pop("version", "1.0")
33
+ pop_version = kwargs.pop("version", "1.0")
38
34
 
39
35
  Model = registry.get(version)
40
36
  if Model is None:
@@ -81,7 +77,7 @@ def generate_click_command(
81
77
  )(wrapped_func)
82
78
 
83
79
  # 3) auto-inject all schema.json fields
84
- schema = load_schema_for_version(version_key or "1.0", schema_pkg)
80
+ schema = load_schema_for_version(version, schema_pkg)
85
81
  props = schema.get("properties", {})
86
82
  reqs = set(schema.get("required", []))
87
83
 
@@ -3,29 +3,13 @@ import pkgutil
3
3
  import click
4
4
  from typing import Callable, Optional, Mapping, Type, Dict, Any
5
5
  from pydantic import ValidationError
6
-
7
-
8
- def load_schema_for_version(
9
- version: str,
10
- base_package: str,
11
- ) -> dict:
12
- """
13
- Load schema.json from the top-level <base_package>.vX_Y_Z package.
14
- """
15
- ver_pkg = f"{base_package}.v{version.replace('.', '_')}"
16
- raw = pkgutil.get_data(ver_pkg, "schema.json")
17
- if raw is None:
18
- raise click.ClickException(
19
- f"Could not load schema.json for version {version} "
20
- f"(looked in package {ver_pkg})"
21
- )
22
- return json.loads(raw)
6
+ import sys
7
+ from sagemaker.hyperpod.cli.common_utils import extract_version_from_args, get_latest_version, load_schema_for_version
23
8
 
24
9
 
25
10
  def generate_click_command(
26
11
  *,
27
- version_key: Optional[str] = None,
28
- schema_pkg: str,
12
+ schema_pkg: str = "hyperpod_pytorch_job_template",
29
13
  registry: Mapping[str, Type] = None,
30
14
  ) -> Callable:
31
15
  """
@@ -33,13 +17,15 @@ def generate_click_command(
33
17
  1) Injects click.options from the JSON Schema under `schema_pkg`
34
18
  2) At runtime, pops `version`, builds the flat model from `registry`, calls .to_domain()
35
19
  3) Finally invokes your handler as `func(version, domain_config)`
36
- - `version_key`: if given, hard-codes the version (no --version flag injected)
37
20
  - `schema_pkg`: the importable package root to read schema.json from
38
21
  - `registry`: a dict mapping version → flat‐model class, e.g. hyperpod_pytorch_job_template.registry.SCHEMA_REGISTRY
39
22
  """
40
23
  if registry is None:
41
24
  raise ValueError("You must pass a registry mapping version→Model")
42
25
 
26
+ default_version = get_latest_version(registry)
27
+ version = extract_version_from_args(registry, schema_pkg, default_version)
28
+
43
29
  def decorator(func: Callable) -> Callable:
44
30
  # Parser for the single JSON‐dict env var flag
45
31
  def _parse_json_flag(ctx, param, value):
@@ -81,7 +67,7 @@ def generate_click_command(
81
67
  # 1) the wrapper click will call
82
68
  def wrapped_func(*args, **kwargs):
83
69
  # extract version
84
- version = version_key or kwargs.pop("version", "1.0")
70
+ pop_version = kwargs.pop("version", default_version)
85
71
  debug = kwargs.pop("debug", False)
86
72
 
87
73
  # look up the model class
@@ -165,7 +151,7 @@ def generate_click_command(
165
151
  ]
166
152
  )
167
153
 
168
- schema = load_schema_for_version(version_key or "1.0", schema_pkg)
154
+ schema = load_schema_for_version(version, schema_pkg)
169
155
  props = schema.get("properties", {})
170
156
  reqs = set(schema.get("required", []))
171
157
 
@@ -195,15 +181,6 @@ def generate_click_command(
195
181
  help=spec.get("description", ""),
196
182
  )(wrapped_func)
197
183
 
198
- # 3) if no hard-coded version_key, inject the top-level --version flag
199
- if version_key is None:
200
- wrapped_func = click.option(
201
- "--version",
202
- default="1.0",
203
- show_default=True,
204
- help="Schema version to use",
205
- )(wrapped_func)
206
-
207
184
  return wrapped_func
208
185
 
209
186
  return decorator
@@ -16,3 +16,7 @@ class Metadata(BaseModel):
16
16
  default=None,
17
17
  description="Labels are key value pairs that are attached to objects, such as Pod. Labels are intended to be used to specify identifying attributes of objects. The system ignores labels that are not in the service's selector. Labels can only be added to objects during creation.",
18
18
  )
19
+ annotations: Optional[Dict[str, str]] = Field(
20
+ default=None,
21
+ description="Annotations are key-value pairs that can be used to attach arbitrary non-identifying metadata to objects.",
22
+ )
@@ -1,21 +1,21 @@
1
- from kubernetes import client
1
+ from kubernetes import client, __version__ as kubernetes_client_version
2
2
  from pydantic import ValidationError
3
3
  from kubernetes.client.exceptions import ApiException
4
4
  from kubernetes import config
5
5
  import re
6
6
  import boto3
7
7
  import json
8
- from typing import List
8
+ from typing import List, Tuple, Optional
9
9
  import logging
10
10
  import os
11
11
  import subprocess
12
12
  import yaml
13
- from typing import Optional
14
13
  from kubernetes.config import (
15
14
  KUBE_CONFIG_DEFAULT_LOCATION,
16
15
  )
17
16
 
18
17
  EKS_ARN_PATTERN = r"arn:aws:eks:([\w-]+):\d+:cluster/([\w-]+)"
18
+ CLIENT_VERSION_PATTERN = r'^\d+\.\d+\.\d+$'
19
19
 
20
20
  KUBE_CONFIG_PATH = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
21
21
 
@@ -297,3 +297,168 @@ def get_current_region():
297
297
  return get_region_from_eks_arn(eks_arn)
298
298
  except:
299
299
  return boto3.session.Session().region_name
300
+
301
+
302
+ def parse_client_kubernetes_version(version_str: str) -> Tuple[int, int]:
303
+ """Parse major and minor version from client library version string.
304
+
305
+ Handles both old versioning scheme (v12 and before) and new homogenized scheme.
306
+ Old scheme: v12.0.0 corresponds to Kubernetes v1.16
307
+ New scheme: v17.0.0 corresponds to Kubernetes v1.17
308
+
309
+ Args:
310
+ version_str (str): Client library version string (e.g., '12.0.0', '17.0.0', 'v12.0.0')
311
+
312
+ Returns:
313
+ Tuple[int, int]: Major and minor version numbers as (1, minor)
314
+ """
315
+ if not version_str:
316
+ logger = logging.getLogger(__name__)
317
+ logger.debug(f"Empty version string provided, Using default version 0.0")
318
+ return 0, 0
319
+
320
+ # Remove suffix (like '+snapshot') if present
321
+ version_str = version_str.split('+')[0]
322
+
323
+ # Remove 'v' prefix if present
324
+ if version_str.startswith('v'):
325
+ version_str = version_str[1:]
326
+
327
+ # Client library version format (x.y.z)
328
+ if re.match(CLIENT_VERSION_PATTERN, version_str):
329
+ major = int(version_str.split('.')[0])
330
+
331
+ # Old client versioning scheme (v12 and before)
332
+ if major <= 12:
333
+ # Currently maps to Kubernetes v1.x
334
+ # This mapping assumes Kubernetes major version is 1
335
+ # If Kubernetes moves to v2.x in the future, this mapping would need to be updated
336
+ return 1, major + 4
337
+
338
+ # New homogenized scheme (v17 and above)
339
+ # Currently maps to Kubernetes v1.x
340
+ # This mapping assumes Kubernetes major version is 1
341
+ # If Kubernetes moves to v2.x in the future, this mapping would need to be updated
342
+ return 1, major
343
+
344
+ # If we get here, parsing failed
345
+ logger = logging.getLogger(__name__)
346
+ logger.warning(f"Failed to parse client version from string: '{version_str}'. Using default version 0.0.")
347
+ return 0, 0
348
+
349
+
350
+
351
+ def is_kubernetes_version_compatible(client_version: Tuple[int, int], server_version: Tuple[int, int]) -> bool:
352
+ """
353
+ Check if Kubernetes client and server versions are compatible.
354
+
355
+ Args:
356
+ client_version (Tuple[int, int]): Client major and minor version
357
+ server_version (Tuple[int, int]): Server major and minor version
358
+
359
+ Returns:
360
+ bool: True if versions are compatible, False otherwise
361
+ """
362
+ # Check for default versions (0.0) which indicate parsing failures
363
+ if client_version == (0, 0) or server_version == (0, 0):
364
+ logger = logging.getLogger(__name__)
365
+ logger.warning(
366
+ f"Version compatibility check using default version(s): client={client_version}, server={server_version}. "
367
+ f"\nThis may indicate a version parsing issue. Please check your Kubernetes configuration."
368
+ )
369
+ return True
370
+
371
+ if client_version[0] != server_version[0]:
372
+ return False
373
+
374
+ """
375
+ Client version should not be more than 3 minor versions behind the server and not more than
376
+ 1 minor version ahead of the server
377
+ """
378
+ client_minor = client_version[1]
379
+ server_minor = server_version[1]
380
+
381
+ if server_minor - client_minor > 3:
382
+ return False
383
+
384
+ if client_minor - server_minor > 1:
385
+ return False
386
+
387
+ return True
388
+
389
+
390
+ def verify_kubernetes_version_compatibility(logger) -> bool:
391
+ """
392
+ Verify compatibility between Kubernetes client and server versions.
393
+
394
+ This function checks if the current Kubernetes client version is compatible with
395
+ the server version. It handles both minimum compatibility versions specified by
396
+ the server and the standard Kubernetes support policy (within 3 minor versions behind
397
+ and not more than 1 minor version ahead).
398
+
399
+ Ref link: https://github.com/kubernetes-client/python#compatibility
400
+
401
+ Args:
402
+ logger: Logger instance for outputting messages.
403
+
404
+ Returns:
405
+ bool: True if versions are compatible, False otherwise
406
+ """
407
+
408
+ try:
409
+ version_api = client.VersionApi()
410
+ server_version_info = version_api.get_code()
411
+
412
+ server_version_str = f"{server_version_info.major}.{server_version_info.minor}"
413
+ client_version = parse_client_kubernetes_version(kubernetes_client_version)
414
+ client_version_str = f"{client_version[0]}.{client_version[1]}"
415
+
416
+ # Debug output of server version info
417
+ logger.debug(f"Server version info: {server_version_info}")
418
+ logger.debug(f"Client version: {kubernetes_client_version}, parsed as {client_version_str}")
419
+
420
+ # Check if server provides minimum compatibility versions (these are optional strings)
421
+ has_min_compatibility = False
422
+ is_compatible = True
423
+
424
+ try:
425
+ if hasattr(server_version_info, 'min_compatibility_major') and server_version_info.min_compatibility_major is not None and \
426
+ hasattr(server_version_info, 'min_compatibility_minor') and server_version_info.min_compatibility_minor is not None:
427
+ min_major = int(server_version_info.min_compatibility_major)
428
+ min_minor = int(server_version_info.min_compatibility_minor)
429
+ has_min_compatibility = True
430
+
431
+ # Check if client version is below minimum compatibility
432
+ if client_version[0] < min_major or (client_version[0] == min_major and client_version[1] < min_minor):
433
+ logger.warning(
434
+ f"Kubernetes version incompatibility detected! Your client version {client_version_str} "
435
+ f"(package: {kubernetes_client_version}) is below the minimum compatible version {min_major}.{min_minor} "
436
+ f"required by server {server_version_str}. The server explicitly requires a minimum client version."
437
+ )
438
+ logger.warning(
439
+ f"To resolve this issue, please update your kubernetes Python client to meet the minimum requirement."
440
+ )
441
+ is_compatible = False
442
+ except (ValueError, TypeError, AttributeError) as e:
443
+ logger.debug(f"Could not parse minimum compatibility version: {e}")
444
+ has_min_compatibility = False
445
+
446
+ if not has_min_compatibility:
447
+ # Fall back to standard compatibility check if min versions not provided
448
+ server_version_parsed = (int(server_version_info.major), int(server_version_info.minor))
449
+ if not is_kubernetes_version_compatible(client_version, server_version_parsed):
450
+ logger.warning(
451
+ f"Kubernetes version incompatibility detected! Your client version {client_version_str} "
452
+ f"(package: {kubernetes_client_version}) is not compatible with server version {server_version_str}. "
453
+ f"According to Kubernetes support policy, client should be within 3 minor versions behind "
454
+ f"and not more than 1 minor version ahead of the server."
455
+ )
456
+ logger.warning(
457
+ f"To resolve this issue, please update your kubernetes Python client to a compatible version."
458
+ )
459
+ is_compatible = False
460
+
461
+ return is_compatible
462
+ except Exception as e:
463
+ logger.warning(f"Failed to verify Kubernetes version compatibility: {e}")
464
+ return True # Be lenient if we can't check compatibility
@@ -14,6 +14,7 @@ from sagemaker.hyperpod.common.utils import (
14
14
  handle_exception,
15
15
  setup_logging,
16
16
  get_default_namespace,
17
+ verify_kubernetes_version_compatibility,
17
18
  )
18
19
  from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
19
20
  _hyperpod_telemetry_emitter,
@@ -24,15 +25,18 @@ from sagemaker.hyperpod.common.telemetry.constants import Feature
24
25
  class HPEndpointBase:
25
26
  is_kubeconfig_loaded = False
26
27
 
28
+ @classmethod
29
+ def get_logger(cls):
30
+ return logging.getLogger(__name__)
31
+
27
32
  @classmethod
28
33
  def verify_kube_config(cls):
29
34
  if not cls.is_kubeconfig_loaded:
30
35
  config.load_kube_config()
31
36
  cls.is_kubeconfig_loaded = True
32
-
33
- @classmethod
34
- def get_logger(cls):
35
- return logging.getLogger(__name__)
37
+
38
+ # Verify Kubernetes version compatibility
39
+ verify_kubernetes_version_compatibility(cls.get_logger())
36
40
 
37
41
  @classmethod
38
42
  def call_create_api(
@@ -0,0 +1,301 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ """Notebook visualization utilities for Models in JumpStart Public Hub."""
15
+ from __future__ import absolute_import
16
+
17
+ import time
18
+ import boto3
19
+ import itables
20
+ import pandas
21
+ import logging
22
+ import json
23
+ from botocore.config import Config
24
+ from ipywidgets import Button, Output
25
+ from IPython.display import display
26
+
27
+
28
+ class ModelDataLoader:
29
+ """Handles progressive loading of model data from SageMaker Hub."""
30
+
31
+ TIMEOUT_SECONDS = 30
32
+ MAX_RESULTS_PER_CALL = 100
33
+
34
+ def __init__(self, region: str, hub_name: str = "SageMakerPublicHub"):
35
+ config = Config(region_name=region, retries={"max_attempts": 10, "mode": "adaptive"})
36
+ self.client = boto3.client("sagemaker", config=config)
37
+ self.hub_name = hub_name
38
+ self.all_data = []
39
+ self.next_token = None
40
+ self.table_output = None
41
+ self.load_button = None
42
+
43
+ def load_initial_data(self, limit: int = 100):
44
+ """Load first batch of models and display progressive table."""
45
+ batch_data = self._get_batch(limit)
46
+ self.all_data = batch_data["data"]
47
+ self.next_token = batch_data["next_token"]
48
+ self._display_progressive_table()
49
+
50
+ def _get_batch(self, limit: int = 100):
51
+ """Get a batch of model data with pagination and timeout."""
52
+ model_summaries = []
53
+ current_token = self.next_token
54
+ start_time = time.time()
55
+
56
+ try:
57
+ while (len(model_summaries) < limit and
58
+ (time.time() - start_time) < self.TIMEOUT_SECONDS):
59
+
60
+ params = {
61
+ "HubName": self.hub_name,
62
+ "HubContentType": "Model",
63
+ "MaxResults": min(self.MAX_RESULTS_PER_CALL, limit - len(model_summaries))
64
+ }
65
+ if current_token:
66
+ params["NextToken"] = current_token
67
+
68
+ response = self.client.list_hub_contents(**params)
69
+ batch_summaries = [
70
+ self._get_model_summary(summary)
71
+ for summary in response["HubContentSummaries"]
72
+ ]
73
+ model_summaries.extend(batch_summaries)
74
+
75
+ current_token = response.get("NextToken")
76
+ if not current_token:
77
+ break
78
+
79
+ if (time.time() - start_time) >= self.TIMEOUT_SECONDS:
80
+ logging.debug(f"Retrieved {len(model_summaries)} models in {self.TIMEOUT_SECONDS} seconds. There may be more models to list, press Load More Models button to continue loading.")
81
+
82
+ return {"data": model_summaries, "next_token": current_token}
83
+
84
+ except Exception as e:
85
+ print(f"Error fetching models: {e}")
86
+ return {"data": model_summaries, "next_token": current_token}
87
+
88
+ def _display_progressive_table(self):
89
+ """Display table with optional load more button."""
90
+ if not self.all_data:
91
+ print("No models to display")
92
+ return
93
+
94
+ self._create_and_display_table()
95
+ self._create_load_button()
96
+
97
+ def _create_load_button(self):
98
+ """Create and display load more button if needed."""
99
+ if self.next_token:
100
+ self.load_button = Button(description="Load More Models")
101
+ self.load_button.on_click(lambda b: self._load_more())
102
+ display(self.load_button)
103
+
104
+ def _create_and_display_table(self):
105
+ """Create and display the data table."""
106
+ self.table_output = Output()
107
+ display(self.table_output)
108
+
109
+ try:
110
+ with self.table_output:
111
+ self.table_output.clear_output(wait=True)
112
+ interactive_view(self.all_data)
113
+ except Exception as e:
114
+ print(f"Error displaying table: {e}")
115
+
116
+ def _load_more(self):
117
+ """Load next batch and update consolidated table."""
118
+ if not self.next_token:
119
+ print("No more models to load")
120
+ return
121
+
122
+ self._set_loading_state()
123
+ self._fetch_and_update_data()
124
+ self._update_button_state()
125
+
126
+ def _set_loading_state(self):
127
+ """Set button to loading state."""
128
+ if self.load_button:
129
+ self.load_button.disabled = True
130
+ self.load_button.description = "Loading..."
131
+
132
+ def _fetch_and_update_data(self):
133
+ """Fetch new data and update table."""
134
+ batch_data = self._get_batch()
135
+ self.next_token = batch_data["next_token"]
136
+ self.all_data.extend(batch_data["data"])
137
+
138
+ with self.table_output:
139
+ self.table_output.clear_output(wait=True)
140
+ interactive_view(self.all_data)
141
+
142
+ def _update_button_state(self):
143
+ """Update button state based on data availability."""
144
+ if not self.load_button:
145
+ return
146
+
147
+ if self.next_token:
148
+ self.load_button.disabled = False
149
+ self.load_button.description = "Load More Models"
150
+ else:
151
+ self.load_button.description = "All Models Loaded"
152
+
153
+ def _get_model_summary(self, full_summary):
154
+ """Extract relevant model information."""
155
+ keywords = full_summary["HubContentSearchKeywords"]
156
+ model_type = self._determine_model_type(keywords, full_summary["HubContentName"])
157
+
158
+ return {
159
+ "Model Id": full_summary["HubContentName"],
160
+ "Model Display Name": full_summary["HubContentDisplayName"],
161
+ "Model Type": model_type,
162
+ "Model Description": full_summary["HubContentDescription"],
163
+ "Search Keywords": keywords,
164
+ "Deployment Configs": self._create_config_link(full_summary["HubContentName"]),
165
+ }
166
+
167
+ def _determine_model_type(self, keywords, model_id):
168
+ """Determine model type from keywords or hub document."""
169
+ if "@model-type:proprietary" in keywords:
170
+ return "Proprietary"
171
+ elif "@model-type:open_weights" in keywords:
172
+ return "Open"
173
+ else:
174
+ # Check if gated via hub content document
175
+ hub_doc = self._get_hub_document(model_id)
176
+ return "Gated" if '"GatedBucket": true' in hub_doc else "Open"
177
+
178
+ def _get_hub_document(self, model_id):
179
+ """Get hub content document for model."""
180
+ return self.client.describe_hub_content(
181
+ HubName=self.hub_name,
182
+ HubContentType="Model",
183
+ HubContentName=model_id
184
+ )["HubContentDocument"]
185
+
186
+ def _get_supported_instance_types(self, model_id):
187
+ """Extract supported instance types from hub document."""
188
+ try:
189
+ hub_doc = self._get_hub_document(model_id)
190
+ doc_data = json.loads(hub_doc)
191
+
192
+ supported_types = doc_data.get("SupportedInferenceInstanceTypes", [])
193
+ default_type = doc_data.get("DefaultInferenceInstanceType")
194
+
195
+ if default_type and default_type in supported_types:
196
+ supported_types = [default_type] + [t for t in supported_types if t != default_type]
197
+
198
+ return {"types": supported_types, "default": default_type, "error": None}
199
+ except Exception as e:
200
+ return {"types": [], "default": None, "error": str(e)}
201
+
202
+ def _create_config_link(self, model_id):
203
+ """Create deployment config display using collapsible details for all environments."""
204
+ return f'<details><summary style="color: #007bff; cursor: pointer;">View SDK Config</summary><pre style="font-size: 10px; background: #f5f5f5; padding: 5px; margin: 5px 0;">{self._generate_deployment_config(model_id)}</pre></details>'
205
+
206
+ def _generate_deployment_config(self, model_id):
207
+ """Generate deployment configuration code for a model."""
208
+ instance_data = self._get_supported_instance_types(model_id)
209
+ supported_types = instance_data["types"]
210
+ default_type = instance_data["default"]
211
+ error = instance_data["error"]
212
+
213
+ if error:
214
+ instance_type = '<ENTER-INSTANCE-TYPE>'
215
+ types_comment = ""
216
+ else:
217
+ instance_type = default_type if default_type else '\<ENTER-INSTANCE-TYPE\>'
218
+ types_comment = self._format_instance_types_comment(supported_types)
219
+
220
+ config_code = f'''# Deployment configuration for {model_id}
221
+ from sagemaker.hyperpod.inference.config.hp_jumpstart_endpoint_config import (
222
+ Model, Server, SageMakerEndpoint
223
+ )
224
+ from sagemaker.hyperpod.inference.hp_jumpstart_endpoint import HPJumpStartEndpoint
225
+
226
+ {types_comment}
227
+
228
+ # Create configs
229
+ model = Model(
230
+ model_id='{model_id}',
231
+ )
232
+ server = Server(
233
+ instance_type='{instance_type}',
234
+ )
235
+
236
+ # Default endpoint name using model_id, modify as desired
237
+ endpoint_name = SageMakerEndpoint(name='{model_id}')
238
+
239
+ # Create endpoint spec
240
+ js_endpoint = HPJumpStartEndpoint(
241
+ model=model,
242
+ server=server,
243
+ sage_maker_endpoint=endpoint_name,
244
+ )
245
+
246
+ # Deploy the endpoint
247
+ js_endpoint.create()'''
248
+ return config_code
249
+
250
+ def _format_instance_types_comment(self, supported_types):
251
+ """Format instance types comment with line breaks for better readability."""
252
+ if not supported_types:
253
+ return "# No supported instance types found"
254
+
255
+ if len(supported_types) <= 5:
256
+ return f"# Supported instance types: {', '.join(supported_types)}"
257
+
258
+ # For more than 5 instance types, format with newlines every 5 types
259
+ comment_lines = ["# Supported instance types:"]
260
+ for i in range(0, len(supported_types), 5):
261
+ batch = supported_types[i:i+5]
262
+ comment_lines.append(f"# {', '.join(batch)}")
263
+
264
+ return '\n'.join(comment_lines)
265
+
266
+
267
+ def get_all_public_hub_model_data(region: str):
268
+ """Load and display SageMaker public hub models with progressive loading."""
269
+ loader = ModelDataLoader(region)
270
+ loader.load_initial_data()
271
+
272
+
273
+ def interactive_view(tabular_data: list):
274
+ """Display interactive table in Jupyter notebook."""
275
+ if not tabular_data:
276
+ return
277
+
278
+ _configure_itables()
279
+ df = pandas.DataFrame(tabular_data)
280
+ styled_df = _style_dataframe(df)
281
+ layout = _get_table_layout(len(tabular_data))
282
+
283
+ itables.show(styled_df, layout=layout, allow_html=True)
284
+
285
+
286
+ def _configure_itables():
287
+ """Configure itables for notebook display."""
288
+ itables.init_notebook_mode(all_interactive=True)
289
+ itables.options.allow_html = True
290
+
291
+
292
+ def _style_dataframe(df):
293
+ """Apply styling to dataframe."""
294
+ return df.style.set_properties(**{"text-align": "left"}).set_table_styles(
295
+ [{"selector": "th", "props": [("text-align", "left")]}]
296
+ )
297
+
298
+
299
+ def _get_table_layout(data_length):
300
+ """Get appropriate table layout based on data size."""
301
+ return {} if data_length > 10 else {"topStart": None, "topEnd": "search"}
@@ -9,6 +9,7 @@ from sagemaker.hyperpod.common.utils import (
9
9
  handle_exception,
10
10
  get_default_namespace,
11
11
  setup_logging,
12
+ verify_kubernetes_version_compatibility
12
13
  )
13
14
  from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
14
15
  _hyperpod_telemetry_emitter,
@@ -17,6 +18,7 @@ from sagemaker.hyperpod.common.telemetry.constants import Feature
17
18
  import yaml
18
19
  import logging
19
20
 
21
+
20
22
  TRAINING_GROUP = "sagemaker.amazonaws.com"
21
23
  API_VERSION = "v1"
22
24
  PLURAL = "hyperpodpytorchjobs"
@@ -36,15 +38,18 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
36
38
  default=None, description="The status of the HyperPodPytorchJob"
37
39
  )
38
40
 
41
+ @classmethod
42
+ def get_logger(cls):
43
+ return logging.getLogger(__name__)
44
+
39
45
  @classmethod
40
46
  def verify_kube_config(cls):
41
47
  if not cls.is_kubeconfig_loaded:
42
48
  config.load_kube_config()
43
49
  cls.is_kubeconfig_loaded = True
44
-
45
- @classmethod
46
- def get_logger(cls):
47
- return logging.getLogger(__name__)
50
+
51
+ # Verify Kubernetes version compatibility
52
+ verify_kubernetes_version_compatibility(cls.get_logger())
48
53
 
49
54
  @_hyperpod_telemetry_emitter(Feature.HYPERPOD, "create_pytorchjob")
50
55
  def create(self, debug=False):
@@ -79,7 +84,7 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
79
84
  plural=PLURAL,
80
85
  body=config,
81
86
  )
82
- logger.info("Successfully submitted HyperPodPytorchJob!")
87
+ logger.info(f"Successfully submitted HyperPodPytorchJob '{self.metadata.name}'!")
83
88
  except Exception as e:
84
89
  logger.error(f"Failed to create HyperPodPytorchJob {self.metadata.name}!")
85
90
  handle_exception(e, self.metadata.name, self.metadata.namespace)
@@ -126,7 +131,7 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
126
131
  plural=PLURAL,
127
132
  name=self.metadata.name,
128
133
  )
129
- logger.info(f"Successful deleted HyperPodPytorchJob!")
134
+ logger.info(f"Successful deleted HyperPodPytorchJob '{self.metadata.name}'!")
130
135
  except Exception as e:
131
136
  logger.error(f"Failed to delete HyperPodPytorchJob {self.metadata.name}!")
132
137
  handle_exception(e, self.metadata.name, self.metadata.namespace)
@@ -230,11 +235,9 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
230
235
 
231
236
 
232
237
  def _load_hp_job(response: dict) -> HyperPodPytorchJob:
233
- name = response["metadata"]["name"]
234
- namespace = response["metadata"]["namespace"]
235
238
 
236
239
  spec = _HyperPodPytorchJob.model_validate(response["spec"], by_name=True)
237
- metadata = Metadata(name=name, namespace=namespace)
240
+ metadata = Metadata(**response["metadata"])
238
241
 
239
242
  if "status" in response:
240
243
  status = HyperPodPytorchJobStatus.model_validate(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sagemaker-hyperpod
3
- Version: 3.0.2
3
+ Version: 3.1.0
4
4
  Summary: Amazon SageMaker HyperPod SDK and CLI
5
5
  Home-page: https://github.com/aws/sagemaker-hyperpod-cli
6
6
  Author: Amazon Web Services
@@ -7,6 +7,7 @@ setup.py
7
7
  src/sagemaker/__init__.py
8
8
  src/sagemaker/hyperpod/__init__.py
9
9
  src/sagemaker/hyperpod/cli/__init__.py
10
+ src/sagemaker/hyperpod/cli/common_utils.py
10
11
  src/sagemaker/hyperpod/cli/hyp_cli.py
11
12
  src/sagemaker/hyperpod/cli/inference_utils.py
12
13
  src/sagemaker/hyperpod/cli/training_utils.py
@@ -51,6 +52,7 @@ src/sagemaker/hyperpod/inference/__init__.py
51
52
  src/sagemaker/hyperpod/inference/hp_endpoint.py
52
53
  src/sagemaker/hyperpod/inference/hp_endpoint_base.py
53
54
  src/sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py
55
+ src/sagemaker/hyperpod/inference/jumpstart_public_hub_visualization_utils.py
54
56
  src/sagemaker/hyperpod/inference/config/__init__.py
55
57
  src/sagemaker/hyperpod/inference/config/constants.py
56
58
  src/sagemaker/hyperpod/inference/config/hp_endpoint_config.py