Why don't web2py json services treat lists properly? - python

The following works for json whose outermost container is an object like { ... }
#service.json
def index():
data = request.vars
#fields are now accessible via data["fieldname"] or data.fieldname
#processing must be done to turn Storage object into dict()
return data_as_dict
If you post a list however, it does not work
POST:
[
{"test": 1}
]
data will be an empty Storage object and data[0] will be None
The workaround is simple:
#service.json #so output is still returned as json
def index():
data = json.loads(request.body.read())
return data
data is now a dict in cases of object style JSON (easier to work with than a Storage object imo) and a native list when the JSON is a list.
My question is why is this not the default behaviour? Why should a JSON service not accept valid JSON?

The #service.json decorator simply registers a function so it can be accessed via a controller that returns a Service object. The decorator ensures that the service controller returns a JSON response when the decorated function is called, but it does nothing regarding the processing of JSON input.
In any case, your problem is not with the #service.json decorator but with a misunderstanding regarding request.vars. request.vars is a dictionary-like object that is populated with keys and values from the query string and/or the request body (if the request body includes form variables or a JSON object of keys and values). It is not intended to simply be a copy of any arbitrary data structure that is posted in the request body. So, if you post a JSON array in the request body, it would not make sense to copy that array to request.vars, as it is not the appropriate type of data structure. If you want to post a JSON array, the correct way to process it is to read the request body, as you have done.
Also, note that because your index function does not take any arguments and therefore does not take advantage of the #service decorator's ability to map parameters from the HTTP request into function arguments, you could simplify your code by foregoing the #service decorator and accessing the index function more directly:
def index():
data = json.loads(request.body.read())
return data
Assuming index is in the default.py controller, you could post JSON to /yourapp/default/index.json (note the .json extension), and you will get back a JSON response.

Related

Write alert policies list in Cloud Storage with cloud functions

I want to write a list of alert policies in a json file in cloud storage. I have the script below:
def my_function(request):
alert_client = monitoring_v3.AlertPolicyServiceClient()
storage_client = storage.Client()
project_name = 'projects/my_project'
bucket_name = 'test'
policies = alert_client.list_alert_policies(name=project_name)
for policy in policies:
print(policy)
destination_blob_name = 'test'
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(destination_blob_name)
blob.upload_from_string("{}".format(json.dumps(policy)))
This script is not working and returns me the following error: TypeError: Object of type AlertPolicy is not JSON serializable"
Now a couple of things:
Using the API explorer or looking at this documentation the response from the list method should be easy to handle. However I'm writing my cloud functions in Python and It seems that the response is different.
I understand that there is something around the way pagination is handle but I don't understand how to deal with it.
I can print(policies) but the log output is kind of weird with a line for each element of the json object. Why is that? What does it mean?
How can I handle this response? Is there a generic approach here or is this specific to the API?
Still I'm able to access each variable independently policy.name, policy.conditions etc... does it means that I have to rebuild the json object I want manually?
According to the googleapis documentation of the alert policy service, iterating over the list of alert policies using list_alert_policies() automatically resolves subsequent pages of the response. You should not worry about implementing pagination logic according to the documentation:
Returns: The protocol for the ListAlertPolicies response. Iterating over this object will yield results and resolve additional pages automatically. source
As for the type AlertPolicy, it does not appear to natively be able to convert to JSON. You might have to build the JSON objects by calling on the respective properties of the AlertPolicy objects that are returned, or you can also implement something similar to this ProtoEncoder class which appears to return JSON from AlertPolicy types. As for the available properties on the AlertPolicy objects, here is the source.
class ProtoEncoder(json.JSONEncoder):
"""Encode protobufs as json."""
def default(self, obj):
if type(obj) in (monitoring_v3.AlertPolicy, monitoring_v3.NotificationChannel):
text = proto.Message.to_json(obj)
return json.loads(text)
return super(ProtoEncoder, self).default(obj)

How to set function arguments passed from a json object?

My function is below:
def do(self, text, disable=['ner'], all_tokens=False, char_match=True, channels=use_channels)
Now I am using a Flask http call to make post requests to the function, and the function parameters are passed through the http call. The results of parameters is in a dict:
parameters = {'disable':['ner', 'lst'], 'all_tokens':True, 'char_match':False}
My question is, how to apply the parameters in the dict to the function 'do'?
If I'm understanding you correctly, all you need to do is unpack the parameters object in the 'do' function. E.g.
do(**parameters)
If you're talking about how to pull the parameters from the URL-
You'll need to get them one at a time IIRC, but as follows:
from flask import request
disable = request.args.get('disable')
all_tokens = request.args.get('all_tokens')
...
do(..., disable=disable, all_tokens=all_tokens)

Iterating the streaming_content attribute on django FileResponse

I'm working on an api with django rest framework, and I'm writing some unit tests to check critical operations. I'm trying to read the contents of a file after doing a get request
downloaded_file = self.client.get(textfile)
The problem is that the object returned is of type: django.http.response.FileResponse which inherits from StreamingHttpResponse.
I'm trying to iterate over the streaming_content attribute, which supposedly is an iterator, but I cannot iterate, no method next().
I inspected this object and what i get is map object. Any ideas on how to get the content from this request?
Edit:
Problem Solved
The returned object is a map, a map takes a function and a iterable:
https://docs.python.org/3.4/library/functions.html#map
What I had to do was to cast the map to a list, access the first element of the list and convert from bytes to string. Not very elegant but it works.
list(response.streaming_content)[0].decode("utf-8")
Here is how you extract the content from a streaming_content map:
content = downloaded_file.getvalue()
Looking at the code of the getvalue() method, we see that it just iterates over the answer content:
class StreamingHttpResponse(HttpResponseBase):
...
def getvalue(self):
return b''.join(self.streaming_content)

Pyramid invoking a sub request

I'm trying to implement a batch request method in pyramid. I see in the docs that it's done with
subrequest = Request.blank('/relative/url')
request.invoke_subrequest(subrequest)
I'm just wondering how do you pass along the headers and cookies? Is it already done for you or is it
request.invoke_subrequest(subrequest, cookies=request.cookies, headers=request.headers)
What about parameters for different methods? The docs only have a POST keyword arg.
I feel like the docs are a little vague, or I can't find the correct docs on how to do this. Thanks
I'm just wondering how do you pass along the headers and cookies?
From http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/subrequest.html#subrequest-chapter :
The pyramid.request.Request.invoke_subrequest() API accepts two
arguments: a positional argument request that must be provided, and
use_tweens keyword argument that is optional; it defaults to False.
This tells us that the constructor only wants a Request object, and optionally a value for use_tweens, so no, this
request.invoke_subrequest(subrequest, cookies=request.cookies, headers=request.headers)
will not work.
Then, from http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/subrequest.html
It's a poor idea to use the original request object as an argument to
invoke_subrequest(). You should construct a new request instead as
demonstrated in the above example, using
pyramid.request.Request.blank(). Once you've constructed a request
object, you'll need to massage it to match the view callable you'd
like to be executed during the subrequest. This can be done by
adjusting the subrequest's URL, its headers, its request method, and
other attributes. The documentation for pyramid.request.Request
exposes the methods you should call and attributes you should set on
the request you create to massage it into something that will actually
match the view you'd like to call via a subrequest.
So basically, you need to configure your request before you pass it to invoke_subrequest().
Luckily there is an entire page that documents the Request class. There we can find a whole lot options to configure it, etc.
What about parameters for different methods? The docs only have a POST keyword arg.
Also on the Request class documentation page, there is this
method
Gets and sets the REQUEST_METHOD key in the environment.
And on http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/viewconfig.html I found
request_method This value can be a string (typically "GET", "POST",
"PUT", "DELETE", or "HEAD") representing an HTTP REQUEST_METHOD
I must agree with you that the documentation is a little vague here and there, but I assume you can use it like this
request.method = 'POST'
# Or
request.method = 'GET'
# Etc.
Summary
You'll want to do it like this
subrequest = Request.blank('/relative/url')
# Configure the subrequest object
# set headers and other options you need.
request.invoke_subrequest(subrequest)
Note
I am aware this is not a 100% complete answer with some code that you can copy paste and it'll just work (especially regarding configuring the request object), but I think this answer contains some information that, at the very least, will get you on the right track and I hope it will be of some help to you.
The following code worked for me. It copies all (headers, cookies, query string, post parameters, etc.):
def subrequest(request, path):
subreq = request.copy()
subreq.path_info = path
response = request.invoke_subrequest(subreq)
return response
Somewhat late, but based on the above two answers here is how I did this. I didn't quite like the above answer to just copy everything. Looking at the documentation of the blank() method there is a kw argument and it says
All necessary keys will be added to the environ, but the values you pass in will take precedence. If you pass in base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will be filled in from that value.
Assuming that the view's request contains the right header information and cookies that you need for your subrequest, you can use the following code:
#view_config( ... )
def something(request):
...
kwargs = { "cookies": request.cookies,
"host" : request.host,
}
req = Request.blank("/relative/url", **kwargs)
resp = request.invoke_subrequest(req)
Other header information (e.g. accept, accept_encoding, etc.) are properties of pyramid.request objects, and can be added to the kwargs dictionary like shown in the code snippet above.
The object returned by invoke_subrequest() is a Response object documented here.

json.dumps vs flask.jsonify

I am not sure I understand the purpose of the flask.jsonify method. I try to make a JSON string from this:
data = {"id": str(album.id), "title": album.title}
but what I get with json.dumps differs from what I get with flask.jsonify.
json.dumps(data): [{"id": "4ea856fd6506ae0db42702dd", "title": "Business"}]
flask.jsonify(data): {"id":…, "title":…}
Obviously I need to get a result that looks more like what json.dumps returns. What am I doing wrong?
The jsonify() function in flask returns a flask.Response() object that already has the appropriate content-type header 'application/json' for use with json responses. Whereas, the json.dumps() method will just return an encoded string, which would require manually adding the MIME type header.
See more about the jsonify() function here for full reference.
Edit:
Also, I've noticed that jsonify() handles kwargs or dictionaries, while json.dumps() additionally supports lists and others.
You can do:
flask.jsonify(**data)
or
flask.jsonify(id=str(album.id), title=album.title)
This is flask.jsonify()
def jsonify(*args, **kwargs):
if __debug__:
_assert_have_json()
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
The json module used is either simplejson or json in that order. current_app is a reference to the Flask() object i.e. your application. response_class() is a reference to the Response() class.
The choice of one or another depends on what you intend to do.
From what I do understand:
jsonify would be useful when you are building an API someone would query and expect json in return. E.g: The REST github API could use this method to answer your request.
dumps, is more about formating data/python object into json and work on it inside your application. For instance, I need to pass an object to my representation layer where some javascript will display graph. You'll feed javascript with the Json generated by dumps.
consider
data={'fld':'hello'}
now
jsonify(data)
will yield {'fld':'hello'} and
json.dumps(data)
gives
"<html><body><p>{'fld':'hello'}</p></body></html>"

Categories

Resources