Python Mock Not Getting Attributes - python

The functionality I am trying to test is as follows. I am trying to mock the client in this function, which comes from my auth module. I am trying to make the client's get function return a Mock object, which contains a property text which refers to our mock_response (seen further down).
def get_person_by_email(email):
client = auth.client()
# print(client) = <Mock name='mock_client()' id='4324810640'>
response = client.get(url="http://..." + email)
# print(response) = <Mock name='mock_client().get()' id='4575780304'>
# print(response.text) = <Mock name='mock_client().get().text' id='4348534608'>
return jsonify(loads(utils.strip_security_string(response.text)))
The function which is throwing TypeError: 'Mock' object has no attribute '__getitem__' is:
def strip_security_string(json_string):
return "\n".join(json_string.split("\n")[1:])
Which simply removes the first line from the response.
And finally, the code which is trying to test the above functionality:
def test_get_person_by_email():
with app.test_client() as client:
with app.app_context():
mock_response = """security-string
{"key":"value"}"""
mock_client = Mock(name='mock_client')
mock_client.get.return_value = Mock(text=mock_response)
with patch.object(auth, 'client', mock_client):
response = client.get("http://.../email/email#domain.com")

I'm not an mock expert but you could try to change mock_client().get.return_value with mock_client.get.return_value. That's because the code use auth.client() instead of auth.client
If you don't want to access to the mock_client() in the Mock creation stage you can do
mock_client_obj = Mock(name='mock_client_obj')
mock_client_obj.get.return_value = Mock(text=mock_response)
mock_client = Mock(name='mock_client',return_value=mock_client_obj)
Or a more simple
mock_client = Mock(name='mock_client')
mock_client.return_value.get.return_value.text = mock_response

On closer look, mock_response is not set to a valid JSON value. Perhaps you mean something like
mock_reponse= """[ "security-string", { "key": "value" } ]"""
See http://json.org for the grammar that describes correct values.

Related

how can I call a function and pass params in a Python class?

I am attempting to use the Woo exchange trading api. They provided a snippet of code which is an impressive Python class structure. Copied below.
My question is how can I use it?
I have tried to run:
get_orders(self, 'BTCUSDT')
which throws an error "NameError: name 'self' is not defined"
and
get_orders('BTCUSDT')
which throws "TypeError: get_orders() missing 1 required positional argument: 'symbol'"
Here is the code (class structure) the kind woo guys provided:
import requests
import datetime
import time
import hmac
import hashlib
from collections import OrderedDict
#Application ID 9d4d96f6-3d3b-4430-966d-8733aa3dc3bc
#API Key
api_key = 'my_api_key'
#API Secret
api_secret = 'my_api_secret'
class Client():
def __init__(self, api_key=None, api_secret=None):
self.api_key = api_key
self.api_secret = api_secret
self.base_api = "https://api.woo.network/v1/"
def get_signature(self, params, timestamp):
query_string = '&'.join(["{}={}".format(k, v) for k, v in params.items()]) + f"|{timestamp}"
signature = hmac.new(
self.api_secret.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def get_orders(self, symbol):
url = self.base_api + "orders/"
params = {
"symbol": 'BTCUSDT'
}
params = OrderedDict(sorted(params.items()))
timestamp = str(int(time.time() * 1000))
signature = self.get_signature(params, timestamp)
headers = {
'Content-Type': "application/x-www-form-urlencoded",
'x-api-key': self.api_key,
'x-api-signature': signature,
'x-api-timestamp': timestamp,
'cache-control': 'no-cache'
}
resp = requests.get(url=url, params=params, headers=headers).json()
So, to repeat and summarize, when I write my own code to use this class, how can I call the function get_orders() and, more generally, reference the elements in the class structure? Thanks, in advance, for help.
Looks like you've truncated the code because get_orders doesn't appear to return anything.
However, you would start by constructing an instance of Client like this:
client = Client(api_key, api_secret)
...then...
client.get_orders(None)
That may look a little strange but get_orders requires one parameter but it's never used. I don't think the implementation of get_orders is quite how it was intended to be because it will always use BTCUSDT

Check query string on the request using requests_mock

How to check if a request mocked by requests_mock added some query parameters to a URL?
I have a function func thats do a HTTP POST on the url with some query string on the URL and I want to check if was called with this query string.
This is my attempt, but fails.
query is a empty string and qs is a empty dict.
I have sure that my func is appending the query string on the request.
with requests_mock.Mocker() as mock:
mock.post(url, text=xml)
func() # This function will call url + query string
history = mock.request_history[0]
assert history.method == "POST" # OK
assert history.query is None # Returns an empty string, AssertionError: assert '' is None
assert history.qs is None # Returns an empty dict, assert {} is None
My func
def credilink():
url = settings["url"]
params = settings["params"]
params["CC"] = query
response = requests.post(url, params=params)
# ...
I tried to reproduce your problem and was unable to...
Here is the code I'm running:
import requests
import requests_mock
url = "http://example.com"
settings = dict(url=url, params=dict(a=1))
query = "some-query"
xml = "some-xml"
def credilink():
url = settings["url"]
params = settings["params"]
params["CC"] = query
response = requests.post(url, params=params)
return response.text
# ...
def test():
with requests_mock.Mocker() as mock:
mock.post(url, text=xml)
data = credilink() # This function will call url + query string
history = mock.request_history[0]
assert history.method == "POST" # OK
assert history.qs == dict(a=['1'], cc=[query])
assert history.query == f"a=1&cc={query}"
assert data == xml
The assertions pass in this snippet.
Maybe it's some version problem? I used requests==2.25.1 and requests-mock==1.8.0.
In my case, the problem was in mock:// URL schema, as it's present in the requests-mock samples
session.get('mock://test.com/path')
But requests library skips query arguments for non "http" URLs. Here is the comment from the source code
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'):
self.url = url
return

Python how to return a list to other functions?

I have been trying to return the list so other functions can access it. But in all the other functions the variables become undefined. The command should be "return twitchClipLinks" right?
def api():
#API via twitch to get the top clips of Just Chatting
API_ENDPOINT = 'https://api.twitch.tv/kraken/clips/top?game=Just%20Chatting&period=day&trending=false&limit=6'
ID = 'REMOVED'
auth = 'application/vnd.twitchtv.v5+json'
head = {
'Client-ID' : ID,
'Accept' : auth
}
r = requests.get(url = API_ENDPOINT, headers = head)
twitchClipLinks = []
data = r.json()
for link in data['clips']:
store = str(link['url'])
twitchClipLinks.append(store)
return twitchClipLinks
Inside every function, you have to add something like this: twitchClipLinks = api() Or you can define twitchClipLinks as a global variable and remove the return statement from ur api() function.

How do I Mock a method that makes multiple POST and GET request all requiring different response data?

I have looked at How to mock REST API and I have read the answers but I still can't seem to get my head around how I would go about dealing with a method that executes multiple GET and POST requests. Here is some of my code below.
I have a class, UserAliasGroups(). Its __init__() method executes requests.post() to login into the external REST API. I have in my unit test this code to handling the mocking of the login and it works as expected.
#mock.patch('aliases.user_alias_groups.requests.get')
#mock.patch('aliases.user_alias_groups.requests.post')
def test_user_alias_groups_class(self, mock_post, mock_get):
init_response = {
'HID-SessionData': 'token==',
'errmsg': '',
'success': True
}
mock_response = Mock()
mock_response.json.return_value = init_response
mock_response.status_code = status.HTTP_201_CREATED
mock_post.return_value = mock_response
uag = UserAliasGroups(auth_user='TEST_USER.gen',
auth_pass='FakePass',
groups_api_url='https://example.com')
self.assertEqual(uag.headers, {'HID-SessionData': 'token=='})
I also have defined several methods like obtain_request_id(), has_group_been_deleted(), does_group_already_exists() and others. I also define a method called create_user_alias_group() that calls obtain_request_id(), has_group_been_deleted(), does_group_already_exists() and others.
I also have code in my unit test to mock a GET request to the REST API to test my has_group_been_deleted() method that looks like this:
has_group_been_deleted_response = {
'error_code': 404,
'error_message': 'A group with this ID does not exist'
}
mock_response = Mock()
mock_response.json.return_value = has_group_been_deleted_response
mock_response.status_code = status.HTTP_404_NOT_FOUND
mock_get.return_value = mock_response
Now I can get to my question. Below is the pertinent part of my code.
class UserAliasGroups:
def __init__(
self,
auth_user=settings.GENERIC_USER,
auth_pass=settings.GENERIC_PASS,
groups_api_url=settings.GROUPS_API_URL
):
""" __init__() does the login to groups. """
self.auth_user = auth_user
self.auth_pass = auth_pass
self.headers = None
self.groups_api_url = groups_api_url
# Initializes a session with the REST API service. Each login session times out after 5 minutes of inactivity.
self.login_url = f'{self.groups_api_url}/api/login'
response = requests.post(self.login_url, json={}, headers={'Content-type': 'application/json'},
auth=(auth_user, auth_pass))
if response.status_code is not 201:
try:
json = response.json()
except:
json = "Could not decode json."
raise self.UserAliasGroupsException(f"Error: User {self.auth_user}, failed to login into "
f"{self.login_url} {json}")
response_json = response.json()
self.headers = {'HID-SessionData': response_json['HID-SessionData']}
def obtain_request_id(self, request_reason):
payload = {'request_reason': request_reason}
url = f'{self.groups_api_url}/api/v1/session/requests'
response = requests.post(url=url, json=payload, headers=self.headers)
if response.status_code is not status.HTTP_200_OK:
try:
json = response.json()
except:
json = "Could not decode json."
msg = f'obtain_request_id() Error url={url} {response.status_code} {json}.'
raise self.UserAliasGroupsException(msg)
request_id = response.json().get('request_id')
return request_id
def has_group_been_deleted(self, group_name):
url = f'{self.groups_api_url}/api/v1/groups/{group_name}/attributes/RESATTR_GROUP_DELETED_ON'
response = requests.get(url=url, headers=self.headers)
return response.status_code == status.HTTP_200_OK
def does_group_already_exists(self, group_name):
url = f'{self.groups_api_url}/api/v1/groups/{group_name}'
response = requests.get(url=url, headers=self.headers)
if response.status_code is status.HTTP_200_OK:
# check if the group has been "deleted".
return not self.has_group_been_deleted(group_name=group_name)
return False
def create_user_alias_group(
self,
... long list of params omitted for brevity ...
):
if check_exists:
# Check if group already exists or not.
if self.does_group_already_exists(group_name):
msg = f'Cannot create group {group_name}. Group already exists.'
raise self.UserAliasGroupsException(msg)
... more code omitted for brevity ...
My question is how do I write my unit test to deal with multiple calls to requests.post() and request.get() all resulting in different responses in my create_user_alias_group() method?
I want to call create_user_alias_group() in my unit test so I have to figure out how to mock multiple requests.get() and requests.post() calls.
Do I have use multiple decorators like this:
#mock.patch('aliases.user_alias_groups.obtain_request_id.requests.post')
#mock.patch('aliases.user_alias_groups.does_group_already_exists.requests.get')
#mock.patch('aliases.user_alias_groups.has_group_been_deleted.requests.get')
def test_user_alias_groups_class(self, mock_post, mock_get):
...
?
Thanks for looking my long question :)
You can use mock.side_effect which takes an iterable. Then different calls will return different values:
mock = Mock()
mock.side_effect = ['a', 'b', 'c']
This way the first call to mock returns "a", then the next one "b" and so on. (In your case, you'll set mock_get.side_effect).

Missing header error with Python requests but header defined

I am trying to create an ansible module using the python requests library, and the module runs without errors but returns the response
"{\n \"code\" : \"generic_err_missing_required_header\",\n \"message\" : \"Missing header: [X-chkp-sid]\"\n}"
even though the header appears to be correctly defined.
Module code below:
from ansible.module_utils.basic import AnsibleModule
import requests
def somefunction(sid):
url = '<someurl>'
headers = {
'Content-Type': 'application/json',
'X-chkp-sid': sid,
}
data = {
'type': 'tag'
}
response = requests.post(url,headers,data,verify=False,timeout=10)
return response.content
def main():
module_args = dict(
sid = dict(type='str', required=True)
)
result = dict(
changed=False,
original_message='',
message=''
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
if module.check_mode:
return result
sid = module.params['sid']
result=somefunction(sid)
module.exit_json(somefunction_output=result)
if __name__ == '__main__':
main()
I have gotten a successful response using the ansible uri module and identical header/body parameters. Any other ideas?
Most arguments to requests.post() should be keyword-style args instead of plain positional args.
Try this instead:
response = requests.post(url,data=data,headers=headers,verify=False,timeout=10)
somefunction() does not return anything, therefore the return value is None.
Perhaps you wanted return requests.post(url,headers,data,verify=False,timeout=10)?

Categories

Resources