Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sagemaker/mxnet/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,8 @@
class MXNetPredictor(RealTimePredictor):
"""A RealTimePredictor for inference against MXNet Endpoints.

This is able to serialize Python lists and numpy arrays to multidimensional tensors for MXNet inference."""
This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference."""

def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``MXNetPredictor``.
Expand Down
11 changes: 8 additions & 3 deletions src/sagemaker/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -240,7 +240,12 @@ def __call__(self, data):
if isinstance(data, list):
if not len(data) > 0:
raise ValueError("empty array can't be serialized")
return _json_serialize_python_array(data)
return _json_serialize_python_object(data)

if isinstance(data, dict):
if not len(data.keys()) > 0:
raise ValueError("empty dictionary can't be serialized")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any special reason for handling the empty dictionary case? An empty dictionary is a valid json.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid JSON: sure, but similar to an empty list, what would I attempt to be predicting without data? I cannot think of such a use case where I'd expect my payload to be empty. My prediction response wouldn't depend on my payload. But maybe it's better to be permissive here and with the list. Let me know what you think, I can change this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that you are applying the same previously applied pattern to check if it is empty or not. I'm ok with that.

return _json_serialize_python_object(data)

# files and buffers
if hasattr(data, 'read'):
Expand All@@ -254,10 +259,10 @@ def __call__(self, data):

def _json_serialize_numpy_array(data):
# numpy arrays can't be serialized but we know they have uniform type
return _json_serialize_python_array(data.tolist())
return _json_serialize_python_object(data.tolist())


def _json_serialize_python_array(data):
def _json_serialize_python_object(data):
return _json_serialize_object(data)


Expand Down
4 changes: 3 additions & 1 deletion src/sagemaker/tensorflow/model.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,8 +19,10 @@


class TensorFlowPredictor(RealTimePredictor):
"""A ``RealTimePredictor`` for inference against MXNet ``Endpoint``s."""
"""A ``RealTimePredictor`` for inference against TensorFlow ``Endpoint``s.

This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet
inference"""
def __init__(self, endpoint_name, sagemaker_session=None):
"""Initialize an ``TensorFlowPredictor``.

Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/tensorflow/predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ def __init__(self):
self.content_type = CONTENT_TYPE_OCTET_STREAM

def __call__(self, data):
# isintance does not work here because a same protobuf message can be imported from a different module.
# isinstance does not work here because a same protobuf message can be imported from a different module.
# for example sagemaker.tensorflow.tensorflow_serving.regression_pb2 and tensorflow_serving.apis.regression_pb2
predict_type = data.__class__.__name__

Expand Down
9 changes: 7 additions & 2 deletions tests/integ/test_tf.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,8 +49,13 @@ def test_tf(sagemaker_session):
with timeout_and_delete_endpoint(estimator=estimator, minutes=20):
json_predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c4.xlarge')

result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
print('predict result: {}'.format(result))
features = [6.4, 3.2, 4.5, 1.5]
dict_result = json_predictor.predict({'inputs': features})
print('predict result: {}'.format(dict_result))
list_result = json_predictor.predict(features)
print('predict result: {}'.format(list_result))

assert dict_result == list_result


def test_tf_async(sagemaker_session):
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_predictor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,12 +51,26 @@ def test_json_serializer_python_array():
assert result == '[1, 2, 3]'


def test_json_serializer_python_dictionary():
d = {"gender": "m", "age": 22, "city": "Paris"}

result = json_serializer(d)

assert json.loads(result) == d


def test_json_serializer_python_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer([])
assert "empty array" in str(error)


def test_json_serializer_python_dictionary_invalid_empty():
with pytest.raises(ValueError) as error:
json_serializer({})
assert "empty dictionary" in str(error)


def test_json_serializer_csv_buffer():
csv_file_path = os.path.join(DATA_DIR, "with_integers.csv")
with open(csv_file_path) as csv_file:
Expand Down