Modify JSON response of Flask-Restless - python

I am trying to use Flask-Restless with Ember.js which isn't going so great. It's the GET responses that are tripping me up. For instance, when I do a GET request on /api/people for example Ember.js expects:
{
people: [
{ id: 1, name: "Yehuda Katz" }
]
}
But Flask-Restless responds with:
{
"total_pages": 1,
"objects": [
{ "id": 1, "name": "Yahuda Katz" }
],
"num_results": 1,
"page": 1
}
How do I change Flask-Restless's response to conform to what Ember.js would like? I have this feeling it might be in a postprocessor function, but I'm not sure how to implement it.

Flask extensions have pretty readable source code. You can make a GET_MANY postprocessor:
def pagination_remover(results):
return {'people': results['objects']} if 'page' in results else results
manager.create_api(
...,
postprocessors={
'GET_MANY': [pagination_remover]
}
)
I haven't tested it, but it should work.

The accepted answer was correct at the time. However the post and preprocessors work in Flask-Restless have changed. According to the documentation:
The preprocessors and postprocessors for each type of request accept
different arguments, but none of them has a return value (more
specifically, any returned value is ignored). Preprocessors and
postprocessors modify their arguments in-place.
So now in my postprocessor I just delete any keys that I do not want. For example:
def api_post_get_many(result=None, **kw):
for key in result.keys():
if key != 'objects':
del result[key]

Related

How to add a status field to a dictionary output?

I need to write a python API for users to call. The input and output specifications are below, as one example:
// input
{"keyword":"iPhone",
"category":"phone"
}
// output
{
"keyword": "iPhone",
"status": "success",
"result": [
{
"word": "iPhone",
"type": "smart_phone"
}, "extend":{}
]
}
I am not going to write http service, but just regular python function for call, and the user needs to install:
pip install my_api
The input is a dictionary, and the output is also a dictionary. How to populate the 'status' field in the output dictionary? I feel this is required for http service to indicate whether the call is successful or not. Since I am not using http service, I don't know how to generate value for the 'status' in the output. The function I write would look like:
def query_by_dict(input: Dict) -> Dict:
result = {}
...
result = self.query_helper(input)
return result
if the self.query_helper() fails or didn't produce any value, I will just return an empty dict 'result' to the client. Why is the 'status' field in the output specification?
If we know that result in the query_by_dict function is already a dictionary, we can just add another argument to that dictionary. As seen below:
def query_by_dict(input: Dict) -> Dict:
result = {}
...
result = self.query_helper(input)
result['status'] = 'success' if result else 'failure'
return result
In the above example, the important part of the code is the result['status'] where we are defining the status argument of the result dictionary. Since we know an empty dictionary result is failure, we can just check for this and set the status argument.

Podio: How do I use the Item.update-method in Python 3?

There is no example code for the Podio API for Python but this is the example code written for Ruby:
Podio::Item.update(210606, {
:fields => {
'title' => 'The API documentation is much more funny',
'business_value' => { :value => 20000, :currency => 'EUR' },
'due_date' => { :start => '2011-05-06 11:27:20', :end =>
5.days.from_now.to_s(:db) }
}
})
I can't for the life of me figure out how to translate this to Python 3. I've tried using dictionaries, dicts inside lists, referencing the field with both their field-id and their names etc. But it never actually updates anything.
This is my failed attempt at translating the above to Python code (with different fields since the fields in my 'Bugs (API example) app' aren't the same as in the example code):
newValues = {'fields':{'title': "This is my title",'description_of_problem':
"the not work"}}
try:
podio.Item.update(629783395, newValues['fields'])
print('updating was successful')
except:
print('updating was not successful')
With podio being:
podio = api.OAuthClient(
client_id,
client_secret,
username,
password,
)
The 'fields' part in my code doesn't make any sense really, but I couldn't figure out what else to do with that part of the Ruby code, I suspect that is the issue. The program always prints 'updating was successful' as if the Item.update method was successfully called, but as I said it doesn't actually update anything in Podio. Can anyone see what's wrong?
I'd just follow the Item update API, and pass in a dictionary that matches the request section there:
{
"revision": The revision of the item that is being updated. This is optional,
"external_id": The new external_id of the item,
"fields": The values for each field, see the create item operation for details,
"file_ids": The list of attachments,
"tags": The list of tags,
"reminder": Optional reminder on this task
{
"remind_delta": Minutes (integer) to remind before the due date
},
"recurrence": The recurrence for the task, if any,
{
"name": The name of the recurrence, "weekly", "monthly" or "yearly",
"config": The configuration for the recurrence, depends on the type
{
"days": List of weekdays ("monday", "tuesday", etc) (for "weekly"),
"repeat_on": When to repeat, "day_of_week" or "day_of_month" (for "monthly")
},
"step": The step size, 1 or more,
"until": The latest date the recurrence should take place
},
"linked_account_id": The linked account to use for meetings,
"ref" The reference of the item
{
"type": The type of reference,
"id": The id of the reference
}
}
The documentation further points to the item creation API for further examples. Note how that object has a "fields" key in the outermost mapping.
All the Ruby documentation does is build that mapping as a Ruby hash (in Python, a dict) with the entries that need updating; :field is an immutable string (called a symbol) that defines a key in that hash pointing to a nested hash. The Python implementation for the update method just converts that dictionary to a JSON post body.
A direct translation of the Ruby code to Python is:
from datetime import datetime, timedelta
podio.item.update(210606, {
'fields': {
'title': 'The API documentation is much more funny',
'business_value': {'value': 20000, 'currency': 'EUR'},
'due_date': {
'start': '2011-05-06 11:27:20',
'end': (datetime.now() + timedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')}
}
})
What you did wrong in your case is not include the 'fields' key in the outermost dictionary; you unwrapped the outermost dictionary and only posted the nested dictionary under 'fields'. Instead, include that outer dictionary:
newValues = {
'fields': {
'title': "This is my title",
'description_of_problem': "the not work"
}
}
podio.Item.update(629783395, newValues)

Can Google Calendar extended properties be lists?

I want to add a set of extendedProperties to a Google Calendar event. I want some of those properties be lists. As (in python),
event = {
..., # standard properties
"extendedProperties": {
"shared": {
"max_crew": 3,
"crew_list": [
"crew1#example.com",
"crew2#example.com",
],
}
}
...
}
This creates the max_crew property but not the crew_list property.
Any way to do this? Or do I need to use a parse-able string (max 1024 chars)?
There is a way: explained in Google Calendar's guide and reference.
And in Python first create a dictionary for your extra fields.
body = {
"extendedProperties": {
"private": {
"petsAllowed": "yes"
}
}
}
Then make a request with:
service.events().patch(calendarId='calendar_id', eventId='event_id', body=body).execute()
If it is successful, it will return the updated event.
Hybor confirms my observation that the interface does not support a list as a value. Shortsighted, imho, but so it goes.

aggregate a field in elasticsearch-dsl using python

Can someone tell me how to write Python statements that will aggregate (sum and count) stuff about my documents?
SCRIPT
from datetime import datetime
from elasticsearch_dsl import DocType, String, Date, Integer
from elasticsearch_dsl.connections import connections
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search, Q
# Define a default Elasticsearch client
client = connections.create_connection(hosts=['http://blahblahblah:9200'])
s = Search(using=client, index="attendance")
s = s.execute()
for tag in s.aggregations.per_tag.buckets:
print (tag.key)
OUTPUT
File "/Library/Python/2.7/site-packages/elasticsearch_dsl/utils.py", line 106, in __getattr__
'%r object has no attribute %r' % (self.__class__.__name__, attr_name))
AttributeError: 'Response' object has no attribute 'aggregations'
What is causing this? Is the "aggregations" keyword wrong? Is there some other package I need to import? If a document in the "attendance" index has a field called emailAddress, how would I count which documents have a value for that field?
First of all. I notice now that what I wrote here, actually has no aggregations defined. The documentation on how to use this is not very readable for me. Using what I wrote above, I'll expand. I'm changing the index name to make for a nicer example.
from datetime import datetime
from elasticsearch_dsl import DocType, String, Date, Integer
from elasticsearch_dsl.connections import connections
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search, Q
# Define a default Elasticsearch client
client = connections.create_connection(hosts=['http://blahblahblah:9200'])
s = Search(using=client, index="airbnb", doc_type="sleep_overs")
s = s.execute()
# invalid! You haven't defined an aggregation.
#for tag in s.aggregations.per_tag.buckets:
# print (tag.key)
# Lets make an aggregation
# 'by_house' is a name you choose, 'terms' is a keyword for the type of aggregator
# 'field' is also a keyword, and 'house_number' is a field in our ES index
s.aggs.bucket('by_house', 'terms', field='house_number', size=0)
Above we're creating 1 bucket per house number. Therefore, the name of the bucket will be the house number. ElasticSearch (ES) will always give a document count of documents fitting into that bucket. Size=0 means to give use all results, since ES has a default setting to return 10 results only (or whatever your dev set it up to do).
# This runs the query.
s = s.execute()
# let's see what's in our results
print s.aggregations.by_house.doc_count
print s.hits.total
print s.aggregations.by_house.buckets
for item in s.aggregations.by_house.buckets:
print item.doc_count
My mistake before was thinking an Elastic Search query had aggregations by default. You sort of define them yourself, then execute them. Then your response can be split b the aggregators you mentioned.
The CURL for the above should look like:
NOTE: I use SENSE an ElasticSearch plugin/extension/add-on for Google Chrome. In SENSE you can use // to comment things out.
POST /airbnb/sleep_overs/_search
{
// the size 0 here actually means to not return any hits, just the aggregation part of the result
"size": 0,
"aggs": {
"by_house": {
"terms": {
// the size 0 here means to return all results, not just the the default 10 results
"field": "house_number",
"size": 0
}
}
}
}
Work-around. Someone on the GIT of DSL told me to forget translating, and just use this method. It's simpler, and you can just write the tough stuff in CURL. That's why I call it a work-around.
# Define a default Elasticsearch client
client = connections.create_connection(hosts=['http://blahblahblah:9200'])
s = Search(using=client, index="airbnb", doc_type="sleep_overs")
# how simple we just past CURL code here
body = {
"size": 0,
"aggs": {
"by_house": {
"terms": {
"field": "house_number",
"size": 0
}
}
}
}
s = Search.from_dict(body)
s = s.index("airbnb")
s = s.doc_type("sleepovers")
body = s.to_dict()
t = s.execute()
for item in t.aggregations.by_house.buckets:
# item.key will the house number
print item.key, item.doc_count
Hope this helps. I now design everything in CURL, then use Python statement to peel away at the results to get what I want. This helps for aggregations with multiple levels (sub-aggregations).
I do not have the rep to comment yet but wanted to make a small fix on Matthew's comment on VISQL's answer regarding from_dict. If you want to maintain the search properties, use update_from_dict rather the from_dict.
According to the Docs , from_dict creates a new search object but update_from_dict will modify in place, which is what you want if Search already has properties such as index, using, etc
So you would want to declare the query body before the search and then create the search like this:
query_body = {
"size": 0,
"aggs": {
"by_house": {
"terms": {
"field": "house_number",
"size": 0
}
}
}
}
s = Search(using=client, index="airbnb", doc_type="sleep_overs").update_from_dict(query_body)

How to mix JSON-serialized Django models with flat JSON

The problem I'm having is with mixing serialized Django models using django.core.serializers with some other piece of data and then trying to serialize the entire thing using json.dumps.
Example code:
scores = []
for indicator in indicators:
score_data = {}
score_data["indicator"] = serializers.serialize("json", [indicator,])
score_data["score"] = evaluation.indicator_percent_score(indicator.id)
score_data["score_descriptor"] = \
serializers.serialize("json",
[form.getDescriptorByPercent(score_data["score"]),],
fields=("order", "value", "title"))
scores.append(score_data)
scores = json.dumps(scores)
return HttpResponse(scores)
Which returns a list of objects like this:
{
indicator: "{"pk": 345345, "model": "forms.indicator", "fields": {"order": 1, "description": "Blah description.", "elements": [10933, 4535], "title": "Blah Title"}}",
score: 37.5,
score_descriptor: "{"pk": 66666, "model": "forms.descriptor", "fields": {"order": 1, "value": "1.00", "title": "Unsatisfactory"}}"
}
The problem I'm having can be seen in the JSON with the serialized Django models being wrapped in multiple sets of quotations. This makes the it very hard to work with on the client side as when I try to do something like
indicator.fields.order
it evaluates to nothing because the browser thinks I'm dealing with a string of some sort.
Ideally I would like valid JSON without the conflicting quotations that make it unreadable. Something akin to a list of objects like this:
{
indicator: {
pk: 12931231,
fields: {
order: 1,
title: "Blah Title",
}
},
etc.
}
Should I be doing this in a different order, using a different data structure, different serializer?
My solution involved dropping the use of django.core.serializers and instead using django.forms.model_to_dict and django.core.serializers.json.DjangoJSONEncoder.
The resulting code looked like this:
for indicator in indicators:
score_data = {}
score_data["indicator"] = model_to_dict(indicator)
score_data["score"] = evaluation.indicator_percent_score(indicator.id)
score_data["score_descriptor"] = \
model_to_dict(form.getDescriptorByPercent(score_data["score"]),
fields=("order", "value", "title"))
scores.append(score_data)
scores = json.dumps(scores, cls=DjangoJSONEncoder)
The problem seemed to arise from the fact that I was essentially serializing the Django models twice. Once with the django.core.serializers function and once with the json.dumps function.
The solution solved this by converting the model to a dictionary first, throwing it in the dictionary with the other data and then serializing it once using the json.dumps using the DjangoJSONEncoder.
Hope this helps someone out because I couldn't find my specific issue but was able to piece it together using other answer to other stackoverflow posts.

Categories