I've been trying to add unit tests to my AWS scripts. I've been using botocore.stub to stub the API calls.
I needed to add pagination to various calls, and I can't seem to find a way to write the tests to include pagination.
Here's an example of the non-paginated test, I'm wondering how I can refactor this test and function to use pagination:
# -*- coding: utf-8 -*-
import unittest
import boto3
from botocore.stub import Stubber
from datetime import datetime
def describe_images(client, repository):
return client.describe_images(repositoryName=repository)
class TestCase(unittest.TestCase):
def setUp(self):
self.client = boto3.client('ecr')
def test_describe_images(self):
describe_images_response = {
'imageDetails': [
{
'registryId': 'string',
'repositoryName': 'string',
'imageDigest': 'string',
'imageTags': [
'string',
],
'imageSizeInBytes': 123,
'imagePushedAt': datetime(2015, 1, 1)
},
],
'nextToken': 'string'
}
stubber = Stubber(self.client)
expected_params = {'repositoryName': 'repo_name'}
stubber.add_response(
'describe_images',
describe_images_response,
expected_params
)
with stubber:
response = describe_images(self.client, 'repo_name')
self.assertEqual(describe_images_response, response)
if __name__ == '__main__':
unittest.main()
If I update the function to include pagination like this:
def describe_images(client, repository):
paginator = client.get_paginator('describe_images')
response_iterator = paginator.paginate(
repositoryName=repository
)
return response_iterator
we seem to be getting somewhere. The test fails as it should as equality has changed:
F
======================================================================
FAIL: test_describe_images (__main__.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "desc_imgs_paginated.py", line 47, in test_describe_images
self.assertEqual(describe_images_response, response)
AssertionError: {'imageDetails': [{'registryId': 'string'[178 chars]ing'} != <botocore.paginate.PageIterator object at 0x1058649b0>
----------------------------------------------------------------------
Ran 1 test in 0.075s
FAILED (failures=1)
When I try to iterate over the generator::
def describe_images(client, repository):
paginator = client.get_paginator('describe_images')
response_iterator = paginator.paginate(
repositoryName=repository
)
return [r for r in response_iterator]
I get the following error:
E
======================================================================
ERROR: test_describe_images (__main__.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "desc_imgs_paginated.py", line 45, in test_describe_images
response = describe_images(self.client, repo_name)
File "desc_imgs_paginated.py", line 14, in describe_images
return '.join([r for r in response_iterator])
File "desc_imgs_paginated.py", line 14, in <listcomp>
return '.join([r for r in response_iterator])
File "lib/python3.6/site-packages/botocore/paginate.py", line 255, in __iter__
response = self._make_request(current_kwargs)
File "lib/python3.6/site-packages/botocore/paginate.py", line 332, in _make_request
return self._method(**current_kwargs)
File "lib/python3.6/site-packages/botocore/client.py", line 312, in _api_call
return self._make_api_call(operation_name, kwargs)
File "lib/python3.6/site-packages/botocore/client.py", line 579, in _make_api_call
api_params, operation_model, context=request_context)
File "lib/python3.6/site-packages/botocore/client.py", line 631, in _convert_to_request_dict
params=api_params, model=operation_model, context=context)
File "lib/python3.6/site-packages/botocore/hooks.py", line 227, in emit
return self._emit(event_name, kwargs)
File "lib/python3.6/site-packages/botocore/hooks.py", line 210, in _emit
response = handler(**kwargs)
File "lib/python3.6/site-packages/botocore/stub.py", line 337, in _assert_expected_params
self._assert_expected_call_order(model, params)
File "lib/python3.6/site-packages/botocore/stub.py", line 323, in _assert_expected_call_order
pformat(params)))
botocore.exceptions.StubResponseError: Error getting response stub for operation DescribeImages: Unexpected API Call: called with parameters:
{nextToken: string, repositoryName: repo_name}
----------------------------------------------------------------------
Ran 1 test in 0.051s
FAILED (errors=1)
Am I missing the correct approach to testing this? or is this a bug in boto3/botocore?
It's been a while since this question was asked but since there isn't an answer ..
In your set up you provide a response dictionary as below
describe_images_response = {
'imageDetails': [
{
'registryId': 'string',
'repositoryName': 'string',
'imageDigest': 'string',
'imageTags': [
'string',
],
'imageSizeInBytes': 123,
'imagePushedAt': datetime(2015, 1, 1)
},
],
'nextToken': 'string'
}
The key here is that the first response will include a nextToken value. This will result in a second request from the paginator. So you have to provide an additional response for the stub, ultimately you need to end with a response the does not include a nextToken
Now looking back at you set up, there is only a single add_response call to the stubber
stubber.add_response(
'describe_images',
describe_images_response,
expected_params
)
The net result in that when the paginator makes the second request, there is not response specified in the setup.
This results in the exception, the message on which hopefully now makes more sense
botocore.exceptions.StubResponseError: Error getting response stub for operation DescribeImages: Unexpected API Call: called with parameters:
{nextToken: string, repositoryName: repo_name}
Since the second response hasn't been set up, you get an exception with the request that was unexpected, in this request you can see the specification of the nextToken parameter.
Related
I've deployed a model using AzureML's inference cluster. I recently found that some of the requests to the model's API endpoint resulted in a 404 HTTP error involving a missing swagger.json file.
So I followed this guide in order to auto-generate the swagger.json file. But now all the requests to the endpoint result in a "list index out of range" error and it's something to do with the input_schema decorator. I just can't seem to pinpoint what the problem is exactly.
Here is a minimal recreation of my scoring script:
from inference_schema.schema_decorators import input_schema, output_schema
from inference_schema.parameter_types.standard_py_parameter_type import StandardPythonParameterType
def inference(args):
# inference logic here
return model_output
def init():
global model
model = get_model()
input_sample = StandardPythonParameterType({
'input_1': 'some text',
'input_2': 'some other text',
'input_3': 'other text'
})
sample_global_parameters = StandardPythonParameterType(1.0)
output_sample = StandardPythonParameterType({
'Results': {
'text': 'some text',
'model_output': [
{
'entity_type': 'date',
'value': '05/04/2022'
}
]
}
})
#input_schema('Inputs', input_sample)
#input_schema('GlobalParameters', sample_global_parameters)
#output_schema(output_sample)
def run(Inputs, GlobalParameters):
try:
return inference(Inputs['input_1'], Inputs['input_2'], Inputs['input_3'])
except Exception as e:
error = str(e)
return error
I've checked out this and this question but it didn't seem to help.
I tried looking at the code on GitHub as well but I still can't triangulate on the exact problem.
I'm calling the API from Postman with the default headers (I'm not adding anything). The request body looks like this:
{
"Inputs": {
"input_1": "some text",
"input_2": "some other text",
"input_3": "different text"
},
"GlobalParameters": 1.0
}
This is the error message from the endpoint logs:
2022-04-05 06:33:22,536 | root | ERROR | Encountered Exception: Traceback (most recent call last):
File "/var/azureml-server/synchronous/routes.py", line 65, in run_scoring
response, time_taken_ms = invoke_user_with_timer(service_input, request_headers)
File "/var/azureml-server/synchronous/routes.py", line 110, in invoke_user_with_timer
result, time_taken_ms = capture_time_taken(user_main.run)(**params)
File "/var/azureml-server/synchronous/routes.py", line 92, in timer
result = func(*args, **kwargs)
File "/var/azureml-app/main.py", line 21, in run
return_obj = driver_module.run(**arguments)
File "/azureml-envs/azureml_e63c7c0baf9bf3d861ce5992975a467b/lib/python3.7/site-packages/inference_schema/schema_decorators.py", line 61, in decorator_input
return user_run(*args, **kwargs)
File "/azureml-envs/azureml_e63c7c0baf9bf3d861ce5992975a467b/lib/python3.7/site-packages/inference_schema/schema_decorators.py", line 55, in decorator_input
args[param_position] = _deserialize_input_argument(args[param_position], param_type, param_name)
IndexError: list index out of range
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/azureml-envs/azureml_e63c7c0baf9bf3d861ce5992975a467b/lib/python3.7/site-packages/flask/app.py", line 1832, in full_dispatch_request
rv = self.dispatch_request()
File "/azureml-envs/azureml_e63c7c0baf9bf3d861ce5992975a467b/lib/python3.7/site-packages/flask/app.py", line 1818, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/var/azureml-server/synchronous/routes.py", line 44, in score_realtime
return run_scoring(service_input, request.headers, request.environ.get('REQUEST_ID', '00000000-0000-0000-0000-000000000000'))
File "/var/azureml-server/synchronous/routes.py", line 74, in run_scoring
raise RunFunctionException(str(exc))
run_function_exception.RunFunctionException
Try on setting the "GlobalParameters" to any kind of floating number other than 1.0 or try to remove it and execute. Sometimes global parameters will cause the issue.
https://learn.microsoft.com/en-us/answers/questions/746784/azure-ml-studio-error-while-testing-real-time-endp.html
I am new to Marshmallow (3.10.0) and I am lost and need help trying to figure out what causes the following error:
AssertionError: ["Input Error - exten: ['Missing data for required field.']"]
The traceback of the error is the following:
Traceback (most recent call last):
File "/root/wazo_virtualenv_python37/lib/python3.7/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "/root/wazo-confd/integration_tests/suite/helpers/wrappers.py", line 81, in decorated
result = func(*new_args, **kwargs)
File "/root/wazo-confd/integration_tests/suite/helpers/wrappers.py", line 81, in decorated
result = func(*new_args, **kwargs)
File "/root/wazo-confd/integration_tests/suite/base/test_call_filter_surrogate_user.py", line 216, in test_get_surrogates_callfilter_exten_when_disabled
confd.extensions.features(feature['id']).put({'enabled': False}).assert_updated()
File "/root/wazo-confd/integration_tests/suite/helpers/client.py", line 272, in assert_updated
self.assert_status(204)
File "/root/wazo-confd/integration_tests/suite/helpers/client.py", line 242, in assert_status
assert_that(self.response.status_code, is_in(statuses), self.response.text)
So it seems that the test function test_get_surrogates_callfilter_exten_when_disabled is failing:
def test_get_surrogates_callfilter_exten_when_disabled(call_filter, user):
response = confd.extensions.features.get(search="bsfilter")
feature = response.items[0]
---> (line 216 in traceback): confd.extensions.features(feature['id']).put({'enabled': False}).assert_updated()
with a.call_filter_surrogate_user(call_filter, user):
response = confd.callfilters(call_filter['id']).get()
assert_that(
response.item,
has_entries(
surrogates=has_entries(
users=contains(has_entries(exten=None, uuid=user['uuid']))
)
),
)
confd.extensions.features(feature['id']).put(
{'enabled': feature['enabled']}
).assert_updated()
the feature_extension schema is defined as the following:
class ExtensionFeatureSchema(BaseSchema):
id = fields.Integer(dump_only=True)
exten = fields.String(validate=Regexp(EXTEN_REGEX), required=True)
context = fields.String(dump_only=True)
feature = fields.String(attribute='typeval', dump_only=True)
enabled = fields.Boolean()
links = ListLink(Link('extensions_features'))
and the put function:
def put(self):
form = self.schema().load(request.get_json())
variables = [self.model(**option) for option in form]
self.service.edit(self.section_name, variables)
return '', 204
I have tried many solutions that I found online; but they did not fix the issue for me:
1 + pass partial=True to the load function:
form = self.schema().load(request.get_json(), partial=True)
2 + remove required=True from the field definition; this made the above error go away but failed many other tests that I have.
I am currently out of ideas, so if anyone has an idea how to fix the issue.
in flask-restplus, I am trying to call R function with parameter from Python, where a parameter is coming from POST request JSON body. To do so, first I load R function in python and pass json data as a parameter but I ended up with following error:
TypeError: 'ListVector' object is not callable
I don't understand why, I didn't find much from rpy2 documentation about function call. Is there any way to pass parameter to R function and call that function in python? Can anyone point me out any possible way to do this? any thought?
my current attempted code with minimal api
input json data:
{
"body": {
"sex": "M",
"feat_aa": {
"value": 12,
"machine": "AC"
},
"feat_bb": {
"value": 13,
"machine": "AB"
}
}
}
toy R function:
library(jsonlite)
my_func <- function(json_data){
qry=fromJSON(json_data)
data=qry$body
## do something
}
here is the main code for minimal flask api:
from flask import Flask, jsonify, request
from flask_restplus import Api, Namespace, Resource, fields
import rpy2
import rpy2.robjects as robjects
##
app = Flask(__name__)
api = Api(app)
ns = Namespace('hello')
features_attr = api.model('hello_world', {
'value': fields.Integer(required=True),
'machine': fields.String(required=True)
})
feat_root_objs = api.model('my machine', {
'sex': fields.String(required=True),
'features': fields.List(fields.Nested(features_attr, required=True))
})
#ns.route('/hello')
class helloWorld(Resource):
#ns.expect(feat_root_objs, validate=False)
def post(self):
myfunc = robjects.r.source("my_func.R")
param = request.get_json()
res = myfunc(['param'])
# res = myfunc(param)
return jsonify(res)
if __name__ == '__main__':
api.add_namespace(ns)
app.run(debug=True)
How can I pass parameter/arguments to R function and make the function call from python? any thought to make this happen? thanks
update
I tried this attempt as well:
def post(self):
myfunc = robjects.r.source("my_func.R")
param = request.get_json()
res = myfunc(param)
return jsonify({"output": res})
but I always have the following error:
> [2020-04-29 12:47:02,104] ERROR in app: Exception on
> /Ed_features/match_ed [POST] Traceback (most recent call last): File
> "C:\Users\jyson\AppData\Local\Programs\Python\Python37\Lib\site-packages\flask\app.py",
> line 1832, in full_dispatch_request
> rv = self.dispatch_request() File "C:\Users\jyson\AppData\Local\Programs\Python\Python37\Lib\site-packages\flask\app.py",
> line 1818, in dispatch_request
> return self.view_functions[rule.endpoint](**req.view_args) File "C:\Users\jyson\AppData\Local\Programs\Python\Python37\Lib\site-packages\flask_restplus\api.py",
> line 309, in wrapper
> resp = resource(*args, **kwargs) File "C:\Users\jyson\AppData\Local\Programs\Python\Python37\Lib\site-packages\flask\views.py",
> line 88, in view
> return self.dispatch_request(*args, **kwargs) File "C:\Users\jyson\AppData\Local\Programs\Python\Python37\Lib\site-packages\flask_restplus\resource.py",
> line 44, in dispatch_request
> resp = meth(*args, **kwargs) File "C:\Users\jyson\match_api\imm_server\heylo_ed.py", line 58, in post
> res = myfunc(param) TypeError: 'ListVector' object is not callable
Did you success?
Try :
r.source("my_func.R")
my_result=r('my_func')(param)
or
r.source("my_func.R")
functionR=r('my_func')
my_result=functionR(param)
I set up a try catch in my code, but it appears that my exception was not correct because it did not seem to catch it.
I am using an exception from a module, and perhaps I didn't import it correctly? Here is my code:
import logging
import fhirclient.models.bundle as b
from fhirclient.server import FHIRUnauthorizedException
logging.disable(logging.WARNING)
def get_all_resources(resource, struct, smart):
'''Perform a search on a resource type and get all resources entries from all retunred bundles.\n
This function takes all paginated bundles into consideration.'''
if smart.ready == False:
smart.reauthorize
search = resource.where(struct)
bundle = search.perform(smart.server)
resources = [entry.resource for entry in bundle.entry or []]
next_url = _get_next_url(bundle.link)
while next_url != None:
try:
json_dict = smart.server.request_json(next_url)
except FHIRUnauthorizedException:
smart.reauthorize
continue
bundle = b.Bundle(json_dict)
resources += [entry.resource for entry in bundle.entry or []]
next_url = _get_next_url(bundle.link)
return resources
Now when i ran the code I got the following error:
Traceback (most recent call last):
File "code.py", line 79, in <module>
main()
File "code.py", line 42, in main
reports = get_all_resources(dr.DiagnosticReport, search, smart)
File "somepath/fhir_tools/resource.py", line 23, in get_all_resources
json_dict = smart.server.request_json(next_url)
File "/usr/local/lib/python3.6/dist-packages/fhirclient/server.py", line 153, in request_json
res = self._get(path, headers, nosign)
File "/usr/local/lib/python3.6/dist-packages/fhirclient/server.py", line 181, in _get
self.raise_for_status(res)
File "/usr/local/lib/python3.6/dist-packages/fhirclient/server.py", line 256, in raise_for_status
raise FHIRUnauthorizedException(response)
server.FHIRUnauthorizedException: <Response [401]>
Shouldn't my exception catch this?
I have a command as follows:
class AddChatMessages(Command):
arguments = [
('messages', AmpList([('message', Unicode()), ('type', Integer())]))]
And I have a responder for it in a controller:
def add_chat_messages(self, messages):
for i, m in enumerate(messages):
messages[i] = (m['message'], m['type'])
self.main.add_chat_messages(messages)
return {}
commands.AddChatMessages.responder(add_chat_messages)
I am writing a unit test for it. This is my code:
class AddChatMessagesTest(ProtocolTestMixin, unittest.TestCase):
command = commands.AddChatMessages
data = {'messages': [{'message': 'hi', 'type': 'None'}]}
def assert_callback(self, unused):
pass
Where ProtocolMixin is as follows:
class ProtocolTestMixin(object):
def setUp(self):
self.protocol = client.CommandProtocol()
def assert_callback(self, unused):
raise NotImplementedError("Has to be implemented!")
def test_responder(self):
responder = self.protocol.lookupFunction(
self.command.commandName)
d = responder(self.data)
d.addCallback(self.assert_callback)
return d
It works if AmpList is not involved, but when it is - I get following error:
======================================================================
ERROR: test_responder
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/internet/defer.py", line 139, in maybeDeferred
result = f(*args, **kw)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/internet/utils.py", line 203, in runWithWarningsSuppressed
reraise(exc_info[1], exc_info[2])
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/internet/utils.py", line 199, in runWithWarningsSuppressed
result = f(*a, **kw)
File "/Users/<username>/Projects/space/tests/client_test.py", line 32, in test_responder
d = responder(self.data)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 1016, in doit
kw = command.parseArguments(box, self)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 1717, in parseArguments
return _stringsToObjects(box, cls.arguments, protocol)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 2510, in _stringsToObjects
argparser.fromBox(argname, myStrings, objects, proto)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 1209, in fromBox
objects[nk] = self.fromStringProto(st, proto)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 1465, in fromStringProto
boxes = parseString(inString)
File "/Users/<username>/Projects/space/env/lib/python2.7/site-packages/twisted/protocols/amp.py", line 2485, in parseString
return cls.parse(StringIO(data))
TypeError: must be string or buffer, not list
Which makes sense, but how do I serialize a list in AddChatMessagesTest.data?
The responder expects to be called with a serialized box. It will then deserialize it, dispatch the objects to application code, take the object the application code returns, serialize it, and then return that serialized form.
For a few AMP types. most notably String, the serialized form is the same as the deserialized form, so it's easy to overlook this.
I think that you'll want to pass your data through Command.makeArguments in order to produce an object suitable to pass to a responder.
For example:
>>> from twisted.protocols.amp import Command, Integer
>>> class Foo(Command):
... arguments = [("bar", Integer())]
...
>>> Foo.makeArguments({"bar": 17}, None)
AmpBox({'bar': '17'})
>>>
If you do this with a Command that uses AmpList I think you'll find makeArguments returns an encoded string for the value of that argument and that the responder is happy to accept and parse that kind of string.