') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Transformer.transform() raises pydantic ValidationError ("tags: extra_forbidden") after submitting the job when tags are set · Issue #6090 · aws/sagemaker-python-sdk · GitHub
Skip to content

Transformer.transform() raises pydantic ValidationError ("tags: extra_forbidden") after submitting the job when tags are set #6090

Description

@Alex-Mesmer

PySDK Version

  • PySDK V2 (2.x)
  • PySDK V3 (3.x)

Describe the bug
Transformer.transform() raises a pydantic.ValidationError for TransformJob whenever the Transformer was created with non-empty tags. The error is raised after the transform job has already been submitted to SageMaker: transform() builds and submits the CreateTransformJob request successfully, then reconstructs a local sagemaker.core.resources.TransformJob from the request dict but that resource model declares no tags field and sets extra="forbid", so the round-trip fails when the request contains tags.

Another major issue is that there are no tags attached to the created transformation job.

Consequences: the transform job is actually created on SageMaker, but the SDK call raises and the caller never gets the job handle. The failure is independent of the tag value/shape, any non-empty tags triggers it, because the tags field itself is rejected on the resource model.

A related issue, the shape of the tags between ModelTrainer and Transformer are not the same.

To reproduce
This is exactly the construction transform() performs:

fromsagemaker.core.resourcesimportTransformJobTransformJob(transform_job_name="x", tags=[{"key": "team", "value": "ml"}])

Real-world trigger:

fromsagemaker.core.transformerimportTransformert=Transformer(model_name="my-model", instance_count=1, instance_type="ml.m5.large",
output_path="s3://bucket/out/", tags=[{"Key": "team", "Value": "ml"}])
t.transform(data="s3://bucket/in/", content_type="text/csv") # job submits, then raises

Actual behavior:

pydantic_core._pydantic_core.ValidationError: 1validationerrorforTransformJobtagsExtrainputsarenotpermitted [type=extra_forbidden, input_value=[{'key': 'team', 'value': 'ml'}], input_type=list]
Forfurtherinformationvisithttps://errors.pydantic.dev/2.12/v/extra_forbidden

Expected behavior
Transformer.transform() succeeds with tags set and returns the job handle; tags are applied to the transform job.

Root Cause
In sagemaker-core/src/sagemaker/core/transformer.py, Transformer.transform():

  • L717: transform_request["Tags"] = tags
  • L405: create_transform_job(**request) - job is submitted
  • L411–L412:
transformed=transform_util(serialized_request, "CreateTransformJobRequest")
self.latest_transform_job=TransformJob(**transformed) # sagemaker.core.resources.TransformJob

transformed includes a tags key, but sagemaker.core.resources.TransformJob has no tags/Tags field and model_config["extra"] == "forbid" → extra_forbidden.

Suggested fix
Any of: (1) add a tags field to sagemaker.core.resources.TransformJob; (2) drop tags from transformed before TransformJob(**transformed) at L412; or (3) build the post-submit local object via TransformJob.get(...) instead of TransformJob(**transformed).

Screenshots or logs
If applicable, add screenshots or logs to help explain your problem.

System information
A description of your system. Please provide:

  • SageMaker Python SDK version: sagemaker==3.15.1, sagemaker-core==2.16.0
  • Framework name (eg. PyTorch) or algorithm (eg. KMeans):
  • Framework version:
  • Python version:
  • CPU or GPU:
  • Custom Docker image (Y/N):

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions