Mocking requests/responses, mock object has no attribute 'url' - python

I'm new to the mocking library and so far it's been giving me trouble. I'm trying to test a Url parsing method that takes a response from an initialUrl which is then parsed in the method. I set autospec=true so I think it should have access to all methods in the requests library (including response.url) I'm trying to mock both get and response though I'm not sure if that's needed?
My getUrl method that takes a response and returns its parsed contents:
def getUrl(response):
if response.history:
destination = urllib.parse.urlsplit(response.url)
baseUrlTuple = destination._replace(path="", query="")
return urllib.parse.urldefrag(urllib.parse.urlunsplit(baseUrlTuple)).url
raise RuntimeError("No redirect")
Test method:
def testGetUrl(self):
initialUrl = 'http://www.initial-url.com'
expectedUrl = 'http://www.some-new-url.com'
mock_response = Mock(spec=requests, autospec=True)
mock_response.status_code = 200
mock_get = Mock(return_value=mock_response)
#mock_get.return_value.history = True
resp = mock_get(self.initialUrl)
mock_response.history = True
resultUrl = getBaseUrl(resp)
self.assertEqual(resultUrl, expectedUrl)
When I run the test, I get
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'url'

First I would fix the code in your question so that it actually runs.
You have several options, the easiest being just adding url to the list of attributes you're mocking:
mock_response.url = <your URL>
But it's also important to understand that you're trying to use the requests library as a specification for the mock, when you should be using requests.Response() if you want the url attribute to be automatically generated. You still have to assign whatever url you want to use to it though, or you'll be comparing a Mock object to an int in your function.
Take a look at the documentation involving spec if you want to learn more:
https://docs.python.org/3/library/unittest.mock-examples.html

Related

"dict" being interpreted as a "list" and I can't tell why

I have a strange problem and I can't seem to find a solution. I'm creating a Python app that sends a get request to an endpoint, fetches some data in JSON format and processes it to insert it into a database later. I have to classes, one is like APIClient, and the other is just a namespace to hold some methods to transform the data, let's call it APITransform.
There is a problematic method in APITransform, so here's the code.
#api.py module"
class APITransform:
...
#staticmethod
def flatten(data:dict, method:str):
if method == "some flattening method from a config file":
return list(data.values())
....
class APIClient:
....
def get_data():
....
response = requests.get(URL, headers, params)
json_resp = response.json()
json_resp = APITransform.flatten(
json_resp, "some flattening method from a config file")
#main.py module
from api import APIClient
api_client = APIClient()
api_client.get_data()
The error traces to APITransform.flatten() with the message:
return list(data.values())
AttributeError: 'list' object has no attribute 'values'
EDIT: The strange thing is that If I print the type of json_resp object before passing it to APITransform.flatten() in get_data(), I get two outputs in two lines: <class dict> and <class list>. It's like get_data() is being called twice, but I searched it in the entire project, I can only find it in two places, the definition in APIClient and call in main.py. I'm out of debugging ideas.
Anyone with an idea? Thanks
the code can raise such an error if the result of the json which is returned from a server, is a list, for example, if the response (from the server) is something like "[1,2,3]" or any other json list, the json_resp variable would be a list, that of course has no values() function. make sure the server returns the data in proper format or use an if statement to check before passing to the flatten function.

How to convert a dict object in requests.models.Response object in Python?

I am trying to test a function which contain an API call. So in the function I have this line of code :
api_request = dict(requests.get(url_of_the_API).json())
So I tried to use patch like this :
#patch('requests.get')
def test_products_list_creator(self, mock_get):
mock_get.return_value = json.dumps({"products":
{
"name": "apple",
"categories": "fruit,red"
}
})
But at the line of my API call, python throw me this error :
AttributeError: 'str' object has no attribute 'json'
I tried to print type(requests.get(url_of_the_API}.json")) to know what it was and I got this : <class 'requests.models.Response'>
There is a lot of questions about convert a Response in dict but didn't found any about converting a dict to a Response.
So how to make my patch callable by the method json() ?
Firstly we need to figure what is required by requests.models.Response's method .json in order to work, this can be done by scrying source code available at github - requests.models source. After reading requests.models.Response.json body we might conclude that if encoding is set, it does simply loads .text. text method has #property decorator, meaning it is computed when accessing .text, this in turn depend on .content which also is computed. After scrying .content it should be clear that if we set ._content value then it will be returned by .content, therefore to make fake Response which do support .json we need to create one and set .encoding and ._content of it, consider following example:
from requests.models import Response
resp = Response()
resp.encoding = 'ascii'
resp._content = b'{"x":100}'
print(resp.json())
output
{'x': 100}
Note that _content value needs to be bytes so if you have dict d then to get value to be put there do json.dumps(d).encode('ascii')

How can I see instance attributes of the object returned by app.request in web.py?

from bin.app import app
resp = app.request('/')
print resp.__dict__.keys()
print vars(resp)
print resp.__class__.__dict__.keys()
I know app.request returns an object with the data attributes 'headers', 'status' and 'data'. All I want to do is print them out, but none of the print statements above show that these exist.
Is there some way of finding these attributes (without using the help docs)?
The storage object resp has a __getattr__ method--which I found using dir(resp)--that you can use to list the attributes.

Django unit test client response has empty context

I have a unit test that's failing in an assertion that passes in another test in the same test case class.
Here's the passing test:
def test_home(self):
c = Client()
resp = c.get('/')
self.assertEqual(resp.status_code, 200)
self.assertTrue('a_formset' in resp.context)
Here's the failing test:
def test_number_initial_number_of_forms(self):
c = Client()
resp = c.get('/')
self.assertEqual(resp.context['a_formset'].total_form_count(), 1)
In the second test, I get the error TypeError: 'NoneType' object has no attribute '__getitem__'.
If I execute the second test as
def test_number_initial_number_of_forms(self):
c = Client()
resp = c.get('/')
self.assertTrue('a_formset' in resp.context)
self.assertEqual(resp.context['a_formset'].total_form_count(), 1)
I get the error TypeError: argument of type 'NoneType' is not iterable. I've confirmed via print statements in the second test that the response.content contains the page I expect to get, that the status code is correct, and that the template is correct. But the response's context is consistently None in the second test.
I'm running my Django unit tests through the standard "python manage.py test ..." interface, so I don't believe I'm running into the "context is empty from the shell" issue.
What's going on with this?
Edit:
If I add print type(resp.context['a_formset']) to each test, for the working test I get <class 'django.forms.formsets.AFormFormSet'>. For the non-working test, I get TypeError: 'NoneType' object has no attribute '__getitem__' again.
It's because you ran into some error, exited the shell and restarted it.
But you forgot to start environment...
from django.test.utils import setup_test_environment
>>> setup_test_environment()
That was my problem. Hope it works...
Today I run into the same issue. The second test gets same page has nothing in response.context
I made a research and found that
1) test client uses signals to populate context,
2) my view method is not called for the second test
I turned on a debugger and found that the guilty one is 'Cache middleware'. Knowing that I found this ticket and this SO question (the latter has a solution).
So, in short: the second request is served from cache, not from a view, thus a view is not executed and test-client doesn't get the signal and have no ability to populate context.
I can not disable cache middleware for my project, so I added next hack-lines into my settings:
if 'test' in sys.argv:
CACHE_MIDDLEWARE_SECONDS = 0
Hope this helps someone
You can also clear cache manually by calling cache.clear() inside a test method:
from django.core.cache import cache
import pytest
class TestPostView:
#pytest.mark.django_db(transaction=True)
def test_index_post(self, client, post):
cache.clear()
response = client.get('/')

Why do I get "'str' object has no attribute 'read'" when trying to use `json.load` on a string? [duplicate]

This question already has answers here:
How can I parse (read) and use JSON?
(5 answers)
Closed 29 days ago.
In Python I'm getting an error:
Exception: (<type 'exceptions.AttributeError'>,
AttributeError("'str' object has no attribute 'read'",), <traceback object at 0x1543ab8>)
Given python code:
def getEntries (self, sub):
url = 'http://www.reddit.com/'
if (sub != ''):
url += 'r/' + sub
request = urllib2.Request (url +
'.json', None, {'User-Agent' : 'Reddit desktop client by /user/RobinJ1995/'})
response = urllib2.urlopen (request)
jsonStr = response.read()
return json.load(jsonStr)['data']['children']
What does this error mean and what did I do to cause it?
The problem is that for json.load you should pass a file like object with a read function defined. So either you use json.load(response) or json.loads(response.read()).
Ok, this is an old thread but.
I had a same issue, my problem was I used json.load instead of json.loads
This way, json has no problem with loading any kind of dictionary.
Official documentation
json.load - Deserialize fp (a .read()-supporting text file or binary file containing a JSON document) to a Python object using this conversion table.
json.loads - Deserialize s (a str, bytes or bytearray instance containing a JSON document) to a Python object using this conversion table.
You need to open the file first. This doesn't work:
json_file = json.load('test.json')
But this works:
f = open('test.json')
json_file = json.load(f)
If you get a python error like this:
AttributeError: 'str' object has no attribute 'some_method'
You probably poisoned your object accidentally by overwriting your object with a string.
How to reproduce this error in python with a few lines of code:
#!/usr/bin/env python
import json
def foobar(json):
msg = json.loads(json)
foobar('{"batman": "yes"}')
Run it, which prints:
AttributeError: 'str' object has no attribute 'loads'
But change the name of the variablename, and it works fine:
#!/usr/bin/env python
import json
def foobar(jsonstring):
msg = json.loads(jsonstring)
foobar('{"batman": "yes"}')
This error is caused when you tried to run a method within a string. String has a few methods, but not the one you are invoking. So stop trying to invoke a method which String does not define and start looking for where you poisoned your object.
AttributeError("'str' object has no attribute 'read'",)
This means exactly what it says: something tried to find a .read attribute on the object that you gave it, and you gave it an object of type str (i.e., you gave it a string).
The error occurred here:
json.load(jsonStr)['data']['children']
Well, you aren't looking for read anywhere, so it must happen in the json.load function that you called (as indicated by the full traceback). That is because json.load is trying to .read the thing that you gave it, but you gave it jsonStr, which currently names a string (which you created by calling .read on the response).
Solution: don't call .read yourself; the function will do this, and is expecting you to give it the response directly so that it can do so.
You could also have figured this out by reading the built-in Python documentation for the function (try help(json.load), or for the entire module (try help(json)), or by checking the documentation for those functions on http://docs.python.org .
Instead of json.load() use json.loads() and it would work:
ex:
import json
from json import dumps
strinjJson = '{"event_type": "affected_element_added"}'
data = json.loads(strinjJson)
print(data)
So, don't use json.load(data.read()) use json.loads(data.read()):
def findMailOfDev(fileName):
file=open(fileName,'r')
data=file.read();
data=json.loads(data)
return data['mail']
use json.loads() function , put the s after that ... just a mistake btw i just realized after i searched error
def getEntries (self, sub):
url = 'http://www.reddit.com/'
if (sub != ''):
url += 'r/' + sub
request = urllib2.Request (url +
'.json', None, {'User-Agent' : 'Reddit desktop client by /user/RobinJ1995/'})
response = urllib2.urlopen (request)
jsonStr = response.read()
return json.loads(jsonStr)['data']['children']
try this
Open the file as a text file first
json_data = open("data.json", "r")
Now load it to dict
dict_data = json.load(json_data)
If you need to convert string to json. Then use loads() method instead of load(). load() function uses to load data from a file so used loads() to convert string to json object.
j_obj = json.loads('["label" : "data"]')

Categories