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
base: master
Are you sure you want to change the base?
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
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,68 @@ 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.""" | ||
| if value is None or is_pipeline_variable(value): | ||
| return value | ||
| return str(value) | ||
|
|
||
|
|
||
| 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. | ||
|
|
||
| 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 +2180,43 @@ def tag_exists(tag: TagsDict, curr_tags: Optional[Tags]) -> bool: | |
| if curr_tags is None: | ||
| 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 +2231,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.
🟡 Low: In
_tag_dict, a value ofNoneis passed through unchanged by_tag_text, so a tag dict missingValue(e.g.{"Key": "x"}) now normalizes to{"Key": "x", "Value": None}. The previous{key: value}path stringified everything (str(v)), so this is a behavior change that can emit"Value": Noneinto a request. Worth confirming the service/serializer tolerates a nullValue, or defaulting the missing value to"".