Iterating through json request with multiple objects - django - python

I have a django project that I am working on. I have a api request that is returning a json response. With the json reponse that I am getting, there is a list of funding sources and within each funding source is an item that has the name with the object. I want to go though each json item and grab the names from the list of objewcts. how can I do this with the following response. I will also attach the request code that I sent...
response:
{
'funding-sources':[
{
'_links':{
'self':{
'href':'https://api-sandbox.dwolla.com/funding-sources/..546a9720a01',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'funding-source'
},
'customer':{
'href':'https://api-sandbox.dwolla.com/customers/..524dde5a',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'customer'
},
'initiate-micro-deposits':{
'href':'https://api-sandbox.dwolla.com/funding-sources/..6a9720a01/micro-deposits',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'micro-deposits'
}
},
'id':'56b3ab2c-da7b-4edc-83a8-5546a9720a01',
'status':'unverified',
'type':'bank',
'bankAccountType':'checking',
'name':'Cheese',
'created':'2017-11-02T05:37:39.000Z',
'removed':False,
'channels':[
'ach'
],
'bankName':'SANDBOX TEST BANK',
'fingerprint':'..ff1d7dcd22'
},
{
'_links':{
'self':{
'href':'https://api-sandbox.dwolla.com/funding-sources/..a51126e4',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'funding-source'
},
'customer':{
'href':'https://api-sandbox.dwolla.com/customers/..6b56524dde5a',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'customer'
},
'with-available-balance':{
'href':'https://api-sandbox.dwolla.com/funding-sources/..d3a51126e4',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'funding-source'
},
'balance':{
'href':'https://api-sandbox.dwolla.com/funding-sources/..6e4/balance',
'type':'application/vnd.dwolla.v1.hal+json',
'resource-type':'balance'
}
},
'id':'522d3f3c-5ea2-4751-a485-4ad3a51126e4',
'status':'verified',
'type':'balance',
'name':'Balance',
'created':'2017-11-02T01:51:27.000Z',
'removed':False,
'channels':[
]
}
]
}
here is the code that I have right now for the request... the funding sources is in the -embedded objects as you will see in the code below:
sources = app_token.get('%s/funding-sources' % account_url)
accounts = sources.body['_embedded']

Related

Getting user in a app.view() handle method

I've have a modal for submitting ticket to trello, everything is working great but i need to output in slack a message to the user that his ticket is published.
In the slack api documentation (chapter 'Listening for view submissions') i can read :
# Handle a view_submission request
#app.view("view_1")
def handle_submission(ack, body, client, view, logger):
user = body["user"]["id"]
When i try the same I've got a KeyError: 'user'. Without that line the code work great but I need the user to know that his ticket is published on Trello and provide him the link (and other informations)
EDIT : I've printed the body dict content and nothing about the user :
{
"id":"XXXXXXXXX",
"team_id":"XXXXXXXXX",
"type":"modal",
"private_metadata":"",
"callback_id":"ticket_submission",
"state":{
"values":{
"block_type":{
"type":{
"type":"static_select",
"selected_option":{
"text":{
"type":"plain_text",
"text":"Feature",
"emoji":true
},
"value":"feature"
}
}
},
"block_priority":{
"priority":{
"type":"static_select",
"selected_option":{
"text":{
"type":"plain_text",
"text":"Critical",
"emoji":true
},
"value":"critical"
}
}
},
"block_description":{
"description":{
"type":"plain_text_input",
"value":"This is the description"
}
},
}
},
"hash":"1674058454.YvY8jNKh",
"title":{
"type":"plain_text",
"text":"Create Ticket",
"emoji":true
},
"clear_on_close":false,
"notify_on_close":false,
"close":{
"type":"plain_text",
"text":"Cancel",
"emoji":true
},
"submit":{
"type":"plain_text",
"text":"Submit",
"emoji":true
},
"previous_view_id":"None",
"root_view_id":"XXXXXXXXX",
"app_id":"XXXXXXXXX",
"external_id":"",
"app_installed_team_id":"XXXXXXXXX",
"bot_id":"XXXXXXXXX"
}

Is there a way to make a response from Python requests a list within a Python dictionary?

I am using the Python requests library to get some data from an API. However, within the output dictionary, I would like to have a list. I want to have a list within the data_providers key where the first entry for name, url and api_url to be hard coded and the second entry to be gotten from the response.
Currently I get the error TypeError: unhashable type: 'dict', and my code look like this
output_dict = {
'data_providers' : {
{
'name': 'NBN Atlas',
'url': 'https://registry.nbnatlas.org/datasets',
'api_url': 'https://registry.nbnatlas.org/ws/dataResource',
},
{
'name': owner_name,
'url': owner_url,
'api_url': owner_api_url,
},
}
}
After running the code and dumping it into a JSON file my desired response should look something like this:
[
{
"data_providers": [
{
"name": "NBN Atlas",
"url": "https://registry.nbnatlas.org/datasets",
"api_url": "https://registry.nbnatlas.org/ws/dataResource"
},
{
"name": "Marine Biological Association",
"url": "https://registry.nbnatlas.org/public/show/dp80",
"api_url": "https://registry.nbnatlas.org/ws/dataProvider/dp80"
}
]
}
]
Any way to go about this?
If I understood your question correctly, you wish to fix the unhashable type error and display valid JSON. If so, you can correct the error by changing data_providers dictionary to a list since it contains multiple nested dictionaries.
output_dict = {
'data_providers': [
{
'name': 'NBN Atlas',
'url': 'https://registry.nbnatlas.org/datasets',
'api_url': 'https://registry.nbnatlas.org/ws/dataResource'
},
{
'name': owner_name,
'url': owner_url,
'api_url': owner_api_url
}
]
}
You can now use a statement similar to print(json.dumps(output_dict)) to display the result as JSON.

How to push the data from dynamodb through stream

Below is the json file
[
{
"year": 2013,
"title": "Rush",
"actors": [
"Daniel Bruhl",
"Chris Hemsworth",
"Olivia Wilde"
]
},
{
"year": 2013,
"title": "Prisoners",
"actors": [
"Hugh Jackman",
"Jake Gyllenhaal",
"Viola Davis"
]
}
]
Below is the code to push to dynamodb. I have created testjsonbucket bucket name, moviedataten.json is the filename and saved above json.Create a dynamodb with Primary partition key as year (Number) and
Primary sort key as title (String).
import json
from decimal import Decimal
import json
import boto3
s3 = boto3.resource('s3')
obj = s3.Object('testjsonbucket', 'moviedataten.json')
body = obj.json
#def lambda_handler(event,context):
# print (body)
def load_movies(movies, dynamodb=None):
if not dynamodb:
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Movies')
for movie in movies:
year = int(movie['year'])
title = movie['title']
print("Adding movie:", year, title)
table.put_item(Item=movie)
def lambda_handler(event, context):
movie_list = json.loads(body, parse_float=Decimal)
load_movies(movie_list)
I want to push in to ElasticSearch from dynamodb.
I have created a Elastic Domain https://xx.x.x.com/testelas
I have gone through the link https://aws.amazon.com/blogs/compute/indexing-amazon-dynamodb-content-with-amazon-elasticsearch-service-using-aws-lambda/
I clicked Managestream also
My Requirement:
Any change in Dynamodb has to reflect in the Elasticsearch?
This lambda just writing the document to DynamoDb, and I will not recommend adding the code in this lambda to push the same object to Elastic search, as lambda function should perform a single task and pushing the same document to ELK should be managed as a DynamoDB stream.
What if ELK is down or not available how you will manage this in lambda?
What if you want to disable this in future? you will need to modify lambda instead of controlling this from AWS API or AWS console, all you need to just disable the stream when required no changes on above lambda side code
What if you want to move only modify or TTL item to elastic search?
So create Dyanodb Stream that pushes the document to another Lambda that is responsible to push the document to ELK, with this option you can also push old and new both items.
You can look into this article too that describe another approach data-streaming-from-dynamodb-to-elasticsearch
For above approach look into this GitHub project dynamodb-stream-elasticsearch.
const { pushStream } = require('dynamodb-stream-elasticsearch');
const { ES_ENDPOINT, INDEX, TYPE } = process.env;
function myHandler(event, context, callback) {
console.log('Received event:', JSON.stringify(event, null, 2));
pushStream({ event, endpoint: ES_ENDPOINT, index: INDEX, type: TYPE })
.then(() => {
callback(null, `Successfully processed ${event.Records.length} records.`);
})
.catch((e) => {
callback(`Error ${e}`, null);
});
}
exports.handler = myHandler;
DynamoDB has a built in feature (DynamoDB streams) that will handle the stream part of this question.
When you configure this you have the choice of the following configurations:
KEYS_ONLY — Only the key attributes of the modified item.
NEW_IMAGE — The entire item, as it appears after it was modified.
OLD_IMAGE — The entire item, as it appeared before it was modified.
NEW_AND_OLD_IMAGES — Both the new and the old images of the item.
This will produce an event that looks like the following
{
"Records":[
{
"eventID":"1",
"eventName":"INSERT",
"eventVersion":"1.0",
"eventSource":"aws:dynamodb",
"awsRegion":"us-east-1",
"dynamodb":{
"Keys":{
"Id":{
"N":"101"
}
},
"NewImage":{
"Message":{
"S":"New item!"
},
"Id":{
"N":"101"
}
},
"SequenceNumber":"111",
"SizeBytes":26,
"StreamViewType":"NEW_AND_OLD_IMAGES"
},
"eventSourceARN":"stream-ARN"
},
{
"eventID":"2",
"eventName":"MODIFY",
"eventVersion":"1.0",
"eventSource":"aws:dynamodb",
"awsRegion":"us-east-1",
"dynamodb":{
"Keys":{
"Id":{
"N":"101"
}
},
"NewImage":{
"Message":{
"S":"This item has changed"
},
"Id":{
"N":"101"
}
},
"OldImage":{
"Message":{
"S":"New item!"
},
"Id":{
"N":"101"
}
},
"SequenceNumber":"222",
"SizeBytes":59,
"StreamViewType":"NEW_AND_OLD_IMAGES"
},
"eventSourceARN":"stream-ARN"
},
{
"eventID":"3",
"eventName":"REMOVE",
"eventVersion":"1.0",
"eventSource":"aws:dynamodb",
"awsRegion":"us-east-1",
"dynamodb":{
"Keys":{
"Id":{
"N":"101"
}
},
"OldImage":{
"Message":{
"S":"This item has changed"
},
"Id":{
"N":"101"
}
},
"SequenceNumber":"333",
"SizeBytes":38,
"StreamViewType":"NEW_AND_OLD_IMAGES"
},
"eventSourceARN":"stream-ARN"
}
]
}
As you're already familiar with Lambda it makes sense to use a Lambda function to consume the records and then iterate through them to process them in the Elasticsearch format before adding them to your index.
When doing this make sure that you iterate through each record as there may be multiple depending on your configuration.
For more information on the steps required for the Lambda side of the function check out the Tutorial: Using AWS Lambda with Amazon DynamoDB streams page.

Using the Firestore REST API to Update a Document Field

I've been searching for a pretty long time but I can't figure out how to update a field in a document using the Firestore REST API. I've looked on other questions but they haven't helped me since I'm getting a different error:
{'error': {'code': 400, 'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT', 'details': [{'#type': 'type.googleapis.com/google.rpc.BadRequest', 'fieldViolations': [{'field': 'oil', 'description': "Error expanding 'fields' parameter. Cannot find matching fields for path 'oil'."}]}]}}
I'm getting this error even though I know that the "oil" field exists in the document. I'm writing this in Python.
My request body (field is the field in a document and value is the value to set that field to, both strings received from user input):
{
"fields": {
field: {
"integerValue": value
}
}
}
My request (authorizationToken is from a different request, dir is also a string from user input which controls the directory):
requests.patch("https://firestore.googleapis.com/v1beta1/projects/aethia-resource-management/databases/(default)/documents/" + dir + "?updateMask.fieldPaths=" + field, data = body, headers = {"Authorization": "Bearer " + authorizationToken}).json()
Based on the the official docs (1,2, and 3), GitHub and a nice article, for the example you have provided you should use the following:
requests.patch("https://firestore.googleapis.com/v1beta1/projects{projectId}/databases/{databaseId}/documents/{document_path}?updateMask.fieldPaths=field")
Your request body should be:
{
"fields": {
"field": {
"integerValue": Value
}
}
}
Also keep in mind that if you want to update multiple fields and values you should specify each one separately.
Example:
https://firestore.googleapis.com/v1beta1/projects/{projectId}/databases/{databaseId}/documents/{document_path}?updateMask.fieldPaths=[Field1]&updateMask.fieldPaths=[Field2]
and the request body would have been:
{
"fields": {
"field": {
"integerValue": Value
},
"Field2": {
"stringValue": "Value2"
}
}
}
EDIT:
Here is a way I have tested which allows you to update some fields of a document without affecting the rest.
This sample code creates a document under collection users with 4 fields, then tries to update 3 out of 4 fields (which leaves the one not mentioned unaffected)
from google.cloud import firestore
db = firestore.Client()
#Creating a sample new Document “aturing” under collection “users”
doc_ref = db.collection(u'users').document(u'aturing')
doc_ref.set({
u'first': u'Alan',
u'middle': u'Mathison',
u'last': u'Turing',
u'born': 1912
})
#updating 3 out of 4 fields (so the last should remain unaffected)
doc_ref = db.collection(u'users').document(u'aturing')
doc_ref.update({
u'first': u'Alan',
u'middle': u'Mathison',
u'born': 2000
})
#printing the content of all docs under users
users_ref = db.collection(u'users')
docs = users_ref.stream()
for doc in docs:
print(u'{} => {}'.format(doc.id, doc.to_dict()))
EDIT: 10/12/2019
PATCH with REST API
I have reproduced your issue and it seems like you are not converting your request body to a json format properly.
You need to use json.dumps() to convert your request body to a valid json format.
A working example is the following:
import requests
import json
endpoint = "https://firestore.googleapis.com/v1/projects/[PROJECT_ID]/databases/(default)/documents/[COLLECTION]/[DOCUMENT_ID]?currentDocument.exists=true&updateMask.fieldPaths=[FIELD_1]"
body = {
"fields" : {
"[FIELD_1]" : {
"stringValue" : "random new value"
}
}
}
data = json.dumps(body)
headers = {"Authorization": "Bearer [AUTH_TOKEN]"}
print(requests.patch(endpoint, data=data, headers=headers).json())
I found the official documentation to not to be of much use since there was no example mentioned. This is the API end-point for your firestore database
PATCH https://firestore.googleapis.com/v1beta1/projects/{YOUR_PROJECT_ID}/databases/(default)/documents/{COLLECTION_NAME}/{DOCUMENT_NAME}
the following code is the body of your API request
{
"fields": {
"first_name": {
"stringValue":"Kurt"
},
"name": {
"stringValue":"Cobain"
},
"band": {
"stringValue":"Nirvana"
}
}
}
The response you should get upon successful update of the database should look like
{
"name": "projects/{YOUR_PROJECT_ID}/databases/(default)/documents/{COLLECTION_ID/{DOC_ID}",
{
"fields": {
"first_name": {
"stringValue":"Kurt"
},
"name": {
"stringValue":"Cobain"
},
"band": {
"stringValue":"Nirvana"
}
}
"createTime": "{CREATE_TIME}",
"updateTime": "{UPDATE_TIME}"
Note that performing the above action would result in a new document being created, meaning that any fields that existed previously but have NOT been mentioned in the "fields" body will be deleted. In order to preserve fields, you'll have to add
?updateMask.fieldPaths={FIELD_NAME} --> to the end of your API call (for each individual field that you want to preserve).
For example:
PATCH https://firestore.googleapis.com/v1beta1/projects/{YOUR_PROJECT_ID}/databases/(default)/documents/{COLLECTION_NAME}/{DOCUMENT_NAME}?updateMask.fieldPaths=name&updateMask.fieldPaths=band&updateMask.fieldPaths=age. --> and so on

Eve: how to use different endpoints to access the same collection with different filters

I have an Eve app publishing a simple read-only (GET) interface. It is interfacing a MongoDB collection called centroids, which has documents like:
[
{
"name":"kachina chasmata",
"location":{
"type":"Point",
"coordinates":[-116.65,-32.6]
},
"body":"ariel"
},
{
"name":"hokusai",
"location":{
"type":"Point",
"coordinates":[16.65,57.84]
},
"body":"mercury"
},
{
"name":"cañas",
"location":{
"type":"Point",
"coordinates":[89.86,-31.188]
},
"body":"mars"
},
{
"name":"anseris cavus",
"location":{
"type":"Point",
"coordinates":[95.5,-29.708]
},
"body":"mars"
}
]
Currently, (Eve) settings declare a DOMAIN as follows:
crater = {
'hateoas': False,
'item_title': 'crater centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
DOMAIN = {
'centroids': crater,
}
Which will successfully answer to requests of the form http://hostname/centroid/<body>/<name>. Inside MongoDB this represents a query like: db.centroids.find({body:<body>, name:<name>}).
What I would like to do also is to offer an endpoint for all the documents of a given body. I.e., a request to http://hostname/centroids/<body> would answer the list of all documents with body==<body>: db.centroids.find({body:<body>}).
How do I do that?
I gave a shot by including a list of rules to the DOMAIN key centroids (the name of the database collection) like below,
crater = {
...
}
body = {
'item_title': 'body craters',
'url': 'centroids/<regex("[\w]+"):body>'
}
DOMAIN = {
'centroids': [crater, body],
}
but didn't work...
AttributeError: 'list' object has no attribute 'setdefault'
Got it!
I was assuming the keys in the DOMAIN structure was directly related to the collection Eve was querying. That is true for the default settings, but it can be adjusted inside the resources datasource.
I figured that out while handling an analogous situation as that of the question: I wanted to have an endpoint hostname/bodies listing all the (unique) values for body in the centroids collection. To that, I needed to set an aggregation to it.
The following settings give me exactly that ;)
centroids = {
'item_title': 'centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'source': 'centroids',
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
bodies = {
'datasource': {
'source': 'centroids',
'aggregation': {
'pipeline': [
{"$group": {"_id": "$body"}},
]
},
}
}
DOMAIN = {
'centroids': centroids,
'bodies': bodies
}
The endpoint, for example, http://127.0.0.1:5000/centroid/mercury/hokusai give me the name, body, and coordinates of mercury/hokusai.
And the endpoint http://127.0.0.1:5000/bodies, the list of unique values for body in centroids.
Beautiful. Thumbs up to Eve!

Categories