Python HTTP Post method returns response as magicmock object instead of value - python-3.x

I am trying to check the response status code after trigerring some API with a POST method, Response status code is of Magicmock instance type, i am checking whether the status code is inbetween 400 and 500 using comparison operator which works in python 2 but raises TypeError in python 3
import mock
response = <MagicMock name='Session().post()' id='130996186'>
Below code works in python 2
if (400 <= response.status_code <= 500):
print('works')
But when executed in python 3, raises
TypeError: '<=' not supported between instances of 'int' and 'MagicMock'
class BMRAPI(object):
root_url = None
def __init__(self, user, api_key, root_url=BMR_URL,
api_uri=RESULTS_API_URI):
self.log =
logging.getLogger("BMRframework.Reporting.BMR6.BMRAPI")
self.root_url = root_url
self.url = urljoin(root_url, api_uri)
self.log.info("Connecting to BMR REST API: %s" % self.url)
self.session = requests.Session()
auth = 'ApiKey {0}:{1}'.format(user, api_key)
self.session.headers.update({
'Content-type': 'application/json',
'Accept': 'text/plain',
'Authorization': auth})
self.session.trust_env = False # bypass the proxy
self.log.debug("Authenticating as: %s" % user)
self.log.debug("Using API Key: %s" % api_key)`enter code here`
self.log.info("Connection to REST API successful")
def url_for_resource(self, resource_name):
return urljoin(self.url, resource_name) + "/"
def create(self, resource_name, data):
response = self.session.post(self.url_for_resource(resource_name),
json.dumps(data), timeout=TIMEOUT)
return self.handle_response(response)
def handle_response(self, response):
if (400 <= response.status_code <= 500):
print('mars')
Below is the UNit test case
#mock.patch("requests.Session")
def BMRAPI(Session):
api = BMRAPI('http://1.2.3.4/', 'dummy_user', '12345')
data = {'hello': 123}
api.create('testresource', data)

This isn't exactly a fix, more of a workaround.
Instead of making that <= comparison, write a separate method:
def is_4xx_or_5xx_code(status_code):
return 400 <= status_code <= 500
if is_4xx_or_5xx_code(status_code=response.status_code):
print('works')
Then mock it in your tests.
#mock.patch('path.to_code.under_test.is_4xx_or_5xx_code')
def test_your_method(mock_status_code):
mock_status_code.return_value = True
# rest of the test.

Related

count successful and unsuccessful post requests for asynchronous post call/request

I need help in implementing the logic to count number of successful post calls which are asynchronous in nature (status_code=200) as well as failed_calls (status_code != 200)
I am new to coroutines. Would appreciate if someone can suggest a better way of making a post asynchronous call which can be retried, polled for status, and that can emit metrics for successful post requests as well.
Following is my code:
asyncio.get_event_loop().run_in_executor(
None,
self.publish_actual,
event_name,
custom_payload,
event_message_params,
)
which calls publish_actual:
def publish_actual(
self,
event_name: str,
custom_payload={},
event_message_params=[],
):
"""Submits a post request using the request library
:param event_name: name of the event
:type event_name: str
:param key: key for a particular application
:param custom_payload: custom_payload, defaults to {}
:type custom_payload: dict, optional
:param event_message_params: event_message_params, defaults to []
:type event_message_params: list, optional
"""
json_data = {}
path = f"/some/path"
self.request(path, "POST", json=json_data)
which calls following request function
def request(self, api_path, method="GET", **kwargs):
try:
self._validate_configuration()
headers = {}
api_endpoint = self.service_uri.to_url(api_path)
logger.debug(api_endpoint)
if "headers" in kwargs and kwargs["headers"]:
headers.update(kwargs["headers"])
headers = {"Content-Type": "application/json"}
begin = datetime.now()
def build_success_metrics(response, *args, **kwargs):
tags = {
"name": "success_metrics",
"domain": api_endpoint,
"status_code": 200,
}
build_metrics(tags)
def check_for_errors(response, *args, **kwargs):
response.raise_for_status()
response = self.session.request(
method=method,
url=api_endpoint,
headers=headers,
timeout=self.timeout,
hooks={"response": [build_success_metrics, check_for_errors]},
**kwargs,
)
end = datetime.now()
logger.debug(
f"'{method}' request against endpoint '{api_endpoint}' took {round((end - begin).total_seconds() * 1000, 3)} ms"
)
logger.debug(f"response: {response}")
except RequestException as e:
tags = {
"name": "error_metrics",
"domain": api_endpoint,
"exception_class": e.__class__.__name__,
}
build_metrics(tags)
return f"Exception occured: {e}"
Let me know if anything else is required from my end to explain what exactly I have done and what I am trying to achieve.
There is not much await and async in your example so I've just addressed the counting part of your question in general terms in asyncio. asyncio.Queue is good for this because you can separate out the counting from the cause quite simply.
import asyncio
import aiohttp
class Count():
def __init__(self, queue: asyncio.Queue):
self.queue = queue
self.good = 0
self.bad = 0
async def count(self):
while True:
result = await self.queue.get()
if result == 'Exit':
return
if result == 200:
self.good += 1
else:
self.bad += 1
async def request(q: asyncio.Queue):
async with aiohttp.ClientSession() as session:
for _ in range(5): # just poll 30 times in this instance
await asyncio.sleep(0.1)
async with session.get(
'https://httpbin.org/status/200%2C500', ssl=False
) as response:
q.put_nowait(response.status)
q.put_nowait('Exit')
async def main():
q = asyncio.Queue()
cnt = Count(q)
tasks = [cnt.count(), request(q)]
await asyncio.gather(*[asyncio.create_task(t) for t in tasks])
print(cnt.good, cnt.bad)
if __name__ == "__main__":
asyncio.run(main())
Output is random given httpbin response. Should add to 5.
4 1

AWS API Gateway/Lambda/DynamoDB - .get_item() not finding item in table

I currently have POSTed items into the DynamoDB (the date is a string):
dynamodb
When I try accessing this via a GET, I get 404 not found (not a 502 so it appears the lambda response is OK):
get request
This is the code in my lambda function:
def lambda_handler(event, context):
logger.info(event)
httpMethod = event['httpMethod']
path = event['path']
if httpMethod == getMethod and path == answersPath:
response = buildResponse(200)
elif httpMethod == getMethod and path == dailyAnswerPath:
response = getAnswer(event['queryStringParameters']['day'])
else:
response = buildResponse(404, 'Not Found')
return response
def getAnswer(day):
try:
response = table.get_item(
Key = {
'day': day
}
)
if 'answer' in response:
return buildResponse(200, response['answer'])
else:
return buildResponse(404, {'Message':'Day %s not found' %day})
except:
logger.exception('getAnswer error day %s' %day)
def buildResponse(statusCode, body=None):
response = {
'statusCode': statusCode,
'headers': {
'Content-Type':'application/json',
'Access-Control-Allow-Origin': '*'
}
}
if body is not None:
response['body'] = json.dumps(body, cls=encodeddd)
return response

How can we terminate the locust process based on num of requests?

I have to do load test using locust.io for multiple APIs, so I want to execute 10 requests for each and every API (suppose I have 10 APIs , so each and every API should get 10 requests) and should terminate the process automatically if all requests were done.
Below is the code snippet i have tried using for loop but not yet worked.
class UserBehavior(TaskSet):
def __init__(self, parent):
super().__init__(parent)
self.camp_code = random.choice(t.camp_code)
self.pgm_id = random.choice(t.pgm_id)
#task
def alloction_details(self):
pgm_id = self.pgm_id
camp_code = self.camp_code
print("L21",camp_code)
myheaders = {'Content-Type': 'application/json', 'Accept': 'application/json'}
load = {
"Code": str(camp_code),
"Id": str(pgm_id)}
# print("L43", users)
for i in range(10):
if i==9:
self.interrupt()
else:
with self.client.post('/ContentDetails', data= json.dumps(load), headers=myheaders,catch_response=True) as response:
print(response.status_code)
if response.status_code==200:
response.success()
# res = response.json()
print("Response of allocation details ", response.text, '\n\n')
else:
response.failure('API failure')
#task
def get_content_details(self):
myheaders = {'Content-Type': 'application/json', 'Accept': 'application/json'}
pgm_id = self.pgm_id
camp_code = self.camp_code
for i in range(10):
if i == 9:
# sleep(1000)
self.interrupt()
else:
with self.client.post('/ScheduleDetails?Code='+str(camp_code), catch_response=True) as response:
print(response.status_code)
if response.status_code==200:
response.success()
# res = response.json()
print("Response : ", response.text, '\n\n')
else:
response.failure('API failure')
class WebsiteUser(HttpUser):
tasks = [UserBehavior]
host = 'localhost'
wait_time = between(1, 3)

How to assert a monkey patch was called in pytest?

Consider the following:
class MockResponse:
status_code = 200
#staticmethod
def json():
return {'key': 'value'}
# where api_session is a fixture
def test_api_session_get(monkeypatch, api_session) -> None:
def mock_get(*args, **kwargs):
return MockResponse()
monkeypatch.setattr(requests.Session, 'get', mock_get)
response = api_session.get('endpoint/') # My wrapper around requests.Session
assert response.status_code == 200
assert response.json() == {'key': 'value'}
monkeypatch.assert_called_with(
'endpoint/',
headers={
'user-agent': 'blah',
},
)
How can I assert that the get I am patching gets called with '/endpoint' and headers? When I run the test now I get the following failure message:
FAILED test/utility/test_api_session.py::test_api_session_get - AttributeError: 'MonkeyPatch' object has no attribute 'assert_called_with'
What am I doing wrong here? Thanks to all those of who reply in advance.
Going to add another response that uses monkeypatch rather than "you can't use monkeypatch"
Since python has closures, here is my poor man's way of doing such things with monkeypatch:
patch_called = False
def _fake_delete(keyname):
nonlocal patch_called
patch_called = True
assert ...
monkeypatch.setattr("mymodule._delete", _fake_delete)
res = client.delete(f"/.../{delmeid}"). # this is a flask client
assert res.status_code == 200
assert patch_called
In your case, since we are doing similar things with patching an HTTP servers method handler, you could do something like (not saying this is pretty):
param_called = None
def _fake_delete(param):
nonlocal param_called
patch_called = param
assert ...
monkeypatch.setattr("mymodule._delete", _fake_delete)
res = client.delete(f"/.../{delmeid}")
assert res.status_code == 200
assert param_called == "whatever this should be"
You need a Mock object to call assert_called_with - monkeypatch does not provide that out of the box. You can use unittest.mock.patch with side_effect instead to achieve this:
from unittest import mock
import requests
...
#mock.patch('requests.Session.get')
def test_api_session_get(mocked, api_session) -> None:
def mock_get(*args, **kwargs):
return MockResponse()
mocked.side_effect = mock_get
response = api_session.get('endpoint/')
...
mocked.assert_called_with(
'endpoint/',
headers={
'user-agent': 'blah',
},
)
Using side_effect is needed to still get a mock object (mocked in this case, of type MagickMock), instead of just setting your own object in patch, otherwise you won't be able to use the assert_called_... methods.

Python passing arguments to function Flask

I am writing a Flask application script and have encountered a function that accepts arguments and functions outside of the flask app in a standalone script but will not accept the the 'TFEVT_node_id'argument I pass to it inside of the Flask app. Here is the Flask code:
#################################################
# Flask Setup
#################################################
#app = Flask(__name__, static_url_path='')
app = Flask(__name__)
CORS(app)
#################################################
#global variables
TFEVT_node_id = ''
token = ''
#################################################
# CityIQ API calls
#################################################
def get_token():
print("Get Token")
url = 'https://auth.aa.cityiq.io/oauth/token'
querystring = {"grant_type":"client_credentials"}
response = requests.get(url, auth=HTTPBasicAuth(client,secret), params=querystring).json()
token = response['access_token']
print("Token Received")
return token
#given a specific subasset this function returns the CityIQ traffic events for a given period of time in minutes
def get_traffic(TFEVT_node_id, lookback_minutes):
url_string = '/events'
url = "https://sandiego.cityiq.io/api/v2/event/assets/"+TFEVT_node_id+url_string
ts = datetime.datetime.now().timestamp()
CityIQ_Current_TS = int(ts*1000)
CityIQ_TS_Calc_TS = datetime.datetime.now() - timedelta(minutes=lookback_minutes)
CityIQ_Starttime_TS = int((ts-(lookback_minutes*60))*1000)
querystring = {"eventType":"TFEVT","startTime":CityIQ_Starttime_TS,"endTime":CityIQ_Current_TS,"pageSize":"100"}
payload = ""
headers = {
'Authorization': "Bearer {}".format(token),
'Predix-Zone-Id': "SD-IE-TRAFFIC",
'cache-control': "no-cache",
}
response = requests.request("GET", url, headers=headers, params=querystring).json()
return(response)
#give it an CityIQ node ID and it will return the asset TFEVT child .
def get_asset_TFEVT(node_id):
url = "https://sandiego.cityiq.io/api/v2/metadata/assets/"+node_id+"/subAssets"
payload = ""
headers = {
'Authorization': "Bearer {}".format(token),
'Predix-Zone-Id': "SD-IE-ENVIRONMENTAL",
'cache-control': "no-cache",
}
response = requests.request("GET", url, data=payload, headers=headers).json()
for i in response['assets']:
if any('TFEVT' in i for i in [i][0]['eventTypes']):
global TFEVT_node_id
TFEVT_node_id = ([i][0]['assetUid'])
return(TFEVT_node_id)
#test def that should be removed in production
def test(TFEVT_node_id, lookback_minutes):
found_cars = get_traffic(TFEVT_node_id, lookback_minutes)
print(found_cars)
return(found_cars)
#################################################
# Flask Routes
#################################################
#app.route('/')
def hello_world():
global token
token = get_token()
return 'documentation comming soon!'
#app.route('/test/<string:node_id>')
def go(node_id):
global token
token = get_token()
global TFEVT_node_id
TFEVT_node_id = get_asset_TFEVT(node_id)
cars_list = []
lookback_minutes = 15
env_output = {}
ccars = test(TFEVT_node_id, lookback_minutes)
cars_list.append(ccars)
env_output.update({'Cars' : cars_list})
if __name__ == '__main__':
app.run()
Again, I am getting the desired result when these functions are run outside of a Flask app. Inside of the Flask app (running the code above) returns a TypeError: The view function did not return a valid response. I have traced that back to the 404 response in the gettraffic function where the lookback minutes variable is getting through but the TFEVT_node_id isn't.
I am new to Python.
Your go() function does not have a return. A response is expected. See: About responses.

Resources