Repository navigation
feat: hub search and metadata, trainer job handles, typed deploy options, endpoint diagnostics, Bedrock and S3 helpers #6533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
450c866
6e9df41
c2db676
d352fe5
8c8dd9a
b04f4c9
b6959d8
93c0a67
097192a
fb7c616
a4c2786
5a134b9
2c09cf5
cd59491
41aae12
d906911
6e820f6
0727c47
c6294e1
4c9bedb
4b89520
a20a1e6
326e8bd
76c02cd
fa28a67
7ffd898
cb1a6ea
8f9ea7f
f529db2
625a4f0
a081545
4042f61
5ea7b09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1688,12 +1688,74 @@ def create_paginator_config(max_items: int = None, page_size: int = None) -> Dic | |
| } | ||
|
|
||
|
|
||
| def format_tags(tags: Tags) -> List[TagsDict]: | ||
| """Process tags to turn them into the expected format for Sagemaker.""" | ||
| if isinstance(tags, dict): | ||
| return [{"Key": str(k), "Value": str(v)} for k, v in tags.items()] | ||
| def _tag_text(value: Any) -> Union[str, PipelineVariable]: | ||
| """Return a tag key or value as the service string, keeping a pipeline variable intact. | ||
|
|
||
| The service model marks ``Tag.Value`` as a necessary member (0 to 256 characters), so a | ||
| missing value becomes the empty string instead of ``None``. | ||
| """ | ||
| if value is None: | ||
| return "" | ||
| if is_pipeline_variable(value): | ||
| return value | ||
| return str(value) | ||
|
|
||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low: In
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed: a missing value becomes |
||
| def _tag_dict(key: Any, value: Any) -> TagsDict: | ||
| return {"Key": _tag_text(key), "Value": _tag_text(value)} | ||
|
|
||
|
|
||
| def _normalize_tag(tag: Any) -> TagsDict: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to |
||
| """Return one tag as a boto-style ``{"Key": ..., "Value": ...}`` dict of strings. | ||
|
|
||
| Accepts the boto form, the ``{"key": ..., "value": ...}`` form that the | ||
| ``sagemaker.core.resources`` classes take, a ``(key, value)`` pair, and a ``Tag`` | ||
| shape object (anything with ``key`` and ``value`` attributes). The key and the value | ||
| come out as ``str`` whatever the input representation, except a ``PipelineVariable``, | ||
| which the request serializer resolves later. Any other input raises ``TypeError`` so a | ||
| malformed tag fails at the call site instead of as a ``KeyError`` deep in a deploy | ||
| path. | ||
| """ | ||
| if isinstance(tag, dict): | ||
| if "Key" in tag: | ||
| return _tag_dict(tag["Key"], tag.get("Value")) | ||
| if "key" in tag: | ||
| return _tag_dict(tag["key"], tag.get("value")) | ||
| raise TypeError( | ||
| "A tag dict must have a 'Key' (or 'key') entry, " | ||
| f"got {sorted(tag)}. Pass a {{key: value}} mapping to set many tags at once." | ||
| ) | ||
| if isinstance(tag, (tuple, list)) and len(tag) == 2: | ||
| return _tag_dict(tag[0], tag[1]) | ||
| if hasattr(tag, "key") and hasattr(tag, "value"): | ||
| return _tag_dict(tag.key, tag.value) | ||
| raise TypeError( | ||
| "A tag must be a {'Key': ..., 'Value': ...} dict, a {'key': ..., 'value': ...} dict, " | ||
| f"a (key, value) pair, or a Tag object, got {type(tag).__name__}." | ||
| ) | ||
|
|
||
|
|
||
| return tags | ||
| def format_tags(tags: Optional[Tags]) -> Optional[List[TagsDict]]: | ||
| """Process tags to turn them into the expected format for Sagemaker. | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low: |
||
| Every accepted representation comes out as a list of boto-style | ||
| ``{"Key": ..., "Value": ...}`` dicts with string keys and values: | ||
|
|
||
| * a ``{key: value}`` mapping, one tag per entry; | ||
| * a single tag dict or ``Tag`` object; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every representation now comes out with |
||
| * a list of tag dicts in either casing, ``(key, value)`` pairs, ``Tag`` objects, or a mix. | ||
|
|
||
| ``None`` is returned unchanged. | ||
| """ | ||
| if tags is None: | ||
| return tags | ||
| if isinstance(tags, dict): | ||
| if "Key" in tags or "key" in tags: | ||
| return [_normalize_tag(tags)] | ||
| return [_tag_dict(key, value) for key, value in tags.items()] | ||
| if isinstance(tags, (list, tuple)): | ||
| return [_normalize_tag(tag) for tag in tags] | ||
| return [_normalize_tag(tags)] | ||
|
|
||
|
|
||
| def _get_resolved_path(path): | ||
|
|
@@ -2124,38 +2186,43 @@ def tag_exists(tag: TagsDict, curr_tags: Optional[Tags]) -> bool: | |
| if curr_tags is None: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low: |
||
| return False | ||
|
|
||
| for curr_tag in curr_tags: | ||
| if tag["Key"] == curr_tag["Key"]: | ||
| key = _normalize_tag(tag)["Key"] | ||
| for curr_tag in format_tags(curr_tags): | ||
| if key == curr_tag["Key"]: | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| def _validate_new_tags(new_tags: Optional[Tags], curr_tags: Optional[Tags]) -> Optional[Tags]: | ||
| """Validates new tags against existing tags. | ||
| def _validate_new_tags( | ||
| new_tags: Optional[Tags], curr_tags: Optional[Tags] | ||
| ) -> Optional[List[TagsDict]]: | ||
| """Merge new tags into the current tags, without a duplicate key. | ||
|
|
||
| The two inputs take any representation ``format_tags`` accepts. The result is the | ||
| merged list in boto form, so a caller that stores it (``ModelBuilder.add_tags``) | ||
| holds one shape whatever shape it received. ``None`` comes back only when the two | ||
| inputs are ``None``. | ||
|
|
||
| Args: | ||
| new_tags (Optional[Tags]): The new tags. | ||
| curr_tags (Optional[Tags]): The current tags. | ||
|
|
||
| Returns: | ||
| Optional[Tags]: The updated tags. | ||
| Optional[List[TagsDict]]: The merged tags in boto form. | ||
| """ | ||
| if curr_tags is None: | ||
| return new_tags | ||
| if new_tags is None: | ||
| return format_tags(curr_tags) if curr_tags is not None else None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Low:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed: |
||
|
|
||
| if curr_tags and isinstance(curr_tags, dict): | ||
| curr_tags = [curr_tags] | ||
| if curr_tags is None: | ||
| return format_tags(new_tags) | ||
|
|
||
| if isinstance(new_tags, dict): | ||
| if not tag_exists(new_tags, curr_tags): | ||
| curr_tags.append(new_tags) | ||
| elif isinstance(new_tags, list): | ||
| for new_tag in new_tags: | ||
| if not tag_exists(new_tag, curr_tags): | ||
| curr_tags.append(new_tag) | ||
| merged = format_tags(curr_tags) | ||
| for new_tag in format_tags(new_tags): | ||
| if not tag_exists(new_tag, merged): | ||
| merged.append(new_tag) | ||
|
|
||
| return curr_tags | ||
| return merged | ||
|
|
||
|
|
||
| def remove_tag_with_key(key: str, tags: Optional[Tags]) -> Optional[Tags]: | ||
|
|
@@ -2170,11 +2237,9 @@ def remove_tag_with_key(key: str, tags: Optional[Tags]) -> Optional[Tags]: | |
| """ | ||
| if tags is None: | ||
| return tags | ||
| if isinstance(tags, dict): | ||
| tags = [tags] | ||
|
|
||
| updated_tags = [] | ||
| for tag in tags: | ||
| for tag in format_tags(tags): | ||
| if tag["Key"] != key: | ||
| updated_tags.append(tag) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verse uses a different repo aws:master-data-preparation right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Verse installs the preview SDK. The
SageMakerAIAgentSkillsbuild declaresSageMakerPythonSDKPreview = 3.x_master_data_preparationas a build tool, andinstall_preview_sdk.shinstalls the five wheels the preview distribution pipeline writes from that staging branch. The staging branch and publicmasterhave diverged.master-data-preparationis 66 commits ahead of and 44 behind stagingmaster, which is identical to publicmaster(4042a24f). The data-preparation sync PR #2345 names the plan to port the branch to a release branch off publicmaster. This PR targets publicmaster, so Verse gets it when the next sync carries publicmasterintomaster-data-preparation(the last one, #2345 on 2026-10-26, brought stagingmasterup to 3.27.0). The 7 files of this PR that the staging branch also touches (common_utils.py,resources.py,telemetry_logging.py,base_trainer.py,model_builder.py, and two tests) are in that sync path. I can open the port tomaster-data-preparationafter this merges, if that is the route you want.