Usually request.json is used to access json objects send to a flask view. I would like to assign something to request.json
from flask import request
print("request.json = ", request.json)
print("request.form['json'] = ", request.form['json'])
request.json = jsonify(request.form['json'])
leads to
request.json = None
request.form['json'] = {
"test": "test"
}
Traceback (most recent call last):
...
File "/ajax_handlers.py", line 952, in X
request.json = jsonify(request.form['json'])
File "/opt/anaconda3/lib/python3.7/site-packages/werkzeug/local.py", line 365, in <lambda>
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
AttributeError: can't set attribute
Any idea how I can assign a json object to request.json?
Presumably for safety, you cannot replace the values on a request object; they come in from the client, and they remain as-is. When you think about it, that's good practice; if various pieces of code try to modify your request object's data in various ways, it would lead to inconsistent and hard-to-test code.
As an alternative, you can assign meaningful attributes that aren't part of the default request object. For example, perhaps you're trying to remember, for elsewhere in your stack, who the user is authenticated as:
# Don't do this!
flask.request.json['authenticated_user'] = authenticated_user
That's messing with the original request data; don't do that. It'll make debugging (not to mention security) a nightmare. A better alternative:
# Do this instead
flask.request.authenticated_user = authenticated_user
You can add to the existing request with new attributes, but you can't go replacing existing properties on the request. Nor should you try!
Related
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.
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')
I need to post data to a Django server. I'd like to use pickle. (There're no security requirements -> small intranet app.)
First, I pickle the data on the client and sending it with urllib2
def dispatch(self, func_name, *args, **kwargs):
dispatch_url = urljoin(self.base_url, "api/dispatch")
pickled_args = cPickle.dumps(args, 2)
pickled_kwargs = cPickle.dumps(kwargs, 2)
data = urllib.urlencode({'func_name' : func_name,
'args' : pickled_args,
'kwargs': pickled_kwargs})
resp = self.opener.open(dispatch_url, data)
Recieving the data at the server works, too:
def dispatch(request):
func_name = request.POST["func_name"]
pickled_args = request.POST["args"]
pickled_kwargs = request.POST["kwargs"]
But unpickling raises an error:
cPickle.loads(pickled_args)
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
TypeError: must be string, not unicode
Obviously the urllib.urlencode has created a unicode string. But how can I convert it back to be able to unpickling (laods) again?
By the way, using pickle format 0 (ascii) works. I can convert to string before unpickling, but I'd rather use format 2.
Also, recommendations about how to get binary data to a Django view are highly appreciated.
Obviously the urllib.urlencode has created a unicode string.
urllib.urlencode doesn't return Unicode string.
It might be a feature of the web framework you use that request.POST contains Unicode strings.
Don't use pickle to communicate between services it is not secure, brittle, not portable, hard to debug and it makes your components too coupled.
There have been a number of other questions on AttributeErrors here, but I've read through them and am still not sure what's causing the type mismatch in my specific case.
Thanks in advance for any thoughts on this.
My model:
class Object(db.Model):
notes = db.StringProperty(multiline=False)
other_item = db.ReferenceProperty(Other)
time = db.DateTimeProperty(auto_now_add=True)
new_files = blobstore.BlobReferenceProperty(required=True)
email = db.EmailProperty()
is_purple = db.BooleanProperty()
My BlobstoreUploadHandler:
class FormUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
try:
note = self.request.get('notes')
email_addr = self.request.get('email')
o = self.request.get('other')
upload_file = self.get_uploads()[0]
# Save the object record
new_object = Object(notes=note,
other=o,
email=email_addr,
is_purple=False,
new_files=upload_file.key())
db.put(new_object)
# Redirect to let user know everything's peachy.
self.redirect('/upload_success.html')
except:
self.redirect('/upload_failure.html')
And every time I submit the form that uploads the file, it throws the following exception:
ERROR 2010-10-30 21:31:01,045 __init__.py:391] 'unicode' object has no attribute 'has_key'
Traceback (most recent call last):
File "/home/user/Public/dir/google_appengine/google/appengine/ext/webapp/__init__.py", line 513, in __call__
handler.post(*groups)
File "/home/user/Public/dir/myapp/myapp.py", line 187, in post
new_files=upload_file.key())
File "/home/user/Public/dir/google_appengine/google/appengine/ext/db/__init__.py", line 813, in __init__
prop.__set__(self, value)
File "/home/user/Public/dir/google_appengine/google/appengine/ext/db/__init__.py", line 3216, in __set__
value = self.validate(value)
File "/home/user/Public/dir/google_appengine/google/appengine/ext/db/__init__.py", line 3246, in validate
if value is not None and not value.has_key():
AttributeError: 'unicode' object has no attribute 'has_key'
What perplexes me most is that this code is nearly straight out of the documentation, and jives with other examples of blob upload handler's I've found online in tutorials as well.
I've run --clear-datastore to ensure that any changes I've made to the DB schema aren't causing problems, and have tried casting upload_file as all sorts of things to see if it would appease Python - any ideas on what I've screwed up?
Edit: I've found a workaround, but it's suboptimal.
Altering the UploadHandler to this instead resolves the issue:
...
# Save the object record
new_object = Object()
new_object.notes = note
new_object.other = o
new_object.email = email.addr
new_object.is_purple = False
new_object.new_files = upload_file.key()
db.put(new_object)
...
I made this switch after noticing that commenting out the files line threw the same issues for the other line, and so on. This isn't an optimal solution, though, as I can't enforce validation this way (in the model, if I set anything as required, I can't declare an empty entity like above without throwing an exception).
Any thoughts on why I can't declare the entity and populate it at the same time?
You're passing in o as the value of other_item (in your sample code, you call it other, but I presume that's a typo). o is a string fetched from the request, though, and the model definition specifies that it's a ReferenceProperty, so it should either be an instance of the Other class, or a db.Key object.
If o is supposed to be a stringified key, pass in db.Key(o) instead, to deserialize it.
Object is a really terrible name for a datastore class (or any class, really), by the way - the Python base object is called object, and that's only one capitalized letter away - very easy to mistake.
has_key error is due to the ReferenceProperty other_items. You are most likely passing in '' for other_items when appengine's api expects a dict. In order to get around this, you need to convert other_items to hash.
[caveat lector: I know zilch about "google_app_engine"]
The message indicates that it is expecting a dict (the only known object that has a has_key attribute) or a work-alike object, not the unicode object that you supplied. Perhaps you should be passing upload_file, not upload_file.key() ...
I am writing a google app engine app and I have this key value error upon requests coming in
from the backtrace I just access and cause the key error
self.request.headers
entire code snippet is here, I just forward the headers unmodified
response = fetch( "%s%s?%s" % (
self.getApiServer() ,
self.request.path.replace("/twitter/", ""),
self.request.query_string
),
self.request.body,
method,
self.request.headers,
)
and get method handling the request calling proxy()
# handle http get
def get(self, *args):
parameters = self.convertParameters(self.request.query_string)
# self.prepareHeader("GET", parameters)
self.request.query_string = "&".join("%s=%s" % (quote(key) , quote(value)) for key, value in parameters.items())
self.proxy(GET, *args)
def convertParameters(self, source):
parameters = {}
for pairs in source.split("&"):
item = pairs.split("=")
if len(item) == 2:
parameters[item[0]] = unquote(item[1])
return parameters
the error back trace:
'CONTENT_TYPE'
Traceback (most recent call last):
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/__init__.py", line 513, in __call__
handler.post(*groups)
File "/base/data/home/apps/waytosing/1.342850593213842824/com/blogspot/zizon/twitter/RestApiProxy.py", line 67, in post
self.proxy(POST, *args)
File "/base/data/home/apps/waytosing/1.342850593213842824/com/blogspot/zizon/twitter/RestApiProxy.py", line 47, in proxy
self.request.headers,
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/urlfetch.py", line 240, in fetch
allow_truncated, follow_redirects)
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/urlfetch.py", line 280, in make_fetch_call
for key, value in headers.iteritems():
File "/base/python_runtime/python_dist/lib/python2.5/UserDict.py", line 106, in iteritems
yield (k, self[k])
File "/base/python_runtime/python_lib/versions/1/webob/datastruct.py", line 40, in __getitem__
return self.environ[self._trans_name(item)]
KeyError: 'CONTENT_TYPE'
Any idea why it happens or is this a known bug?
This looks weird. The docs mention that response "Headers objects do not raise an error when you try to get or delete a key that isn't in the wrapped header list. Getting a nonexistent header just returns None". It's not clear from the request documentation if request.headers are also objects of this class, but even they were regular dictionaries, iteritems seems to be misbehaving. So this might be a bug.
It might be worth inspecting self.request.headers, before calling fetch, and see 1) its actual type, 2) its keys, and 3) if trying to get self.request.headers['CONTENT_TYPE'] raises an error then.
But, if you simply want to solve your problem and move forward, you can try to bypass it like:
if 'CONTENT_TYPE' not in self.request.headers:
self.request.headers['CONTENT_TYPE'] = None
(I'm suggesting setting it to None, because that's what a response Header object should return on non-existing keys)
Here's my observation about this problem:
When the content-type is application/x-www-form-urlencoded and POST data is empty (e.g. jquery.ajax GET, twitter's favorite and retweet API...), the content-type is dropped by Google appengine.
You can add:
self.request.headers.update({'content-type':'application/x-www-form-urlencoded'})
before urlfetch.
Edit: indeed, looking at the error more carefully, it doesn't seem to be related to convertParameters, as the OP points out in the comments. I'm retiring this answer.
I'm not entirely sure what you mean by "just forward the headers unmodified", but have you taken a look at self.request.query_string before and after you call convertParameters? More to the point, you're leaving out any (valid) GET parameters of the form "key=" (that is, keys with empty values).
Maybe your original query_string had a value like "CONTENT_TYPE=", and your convertParameters is stripping it out.
Known issue http://code.google.com/p/googleappengine/issues/detail?id=3427 and potential workarounds here http://code.google.com/p/googleappengine/issues/detail?id=2040