How can I update a MongoDB document with python? - python

what I'm trying to do is to update a MongoDB document with python and discord.py, but the code i've put doesn't work.
elif string2 == "add":
if string3 == "administrator":
cur = coll.find({"_id" : string1.id})
for doc in cur:
if doc["perm"] == "administrator":
await self.bot.say("Permission {} already found on db for user {}".format(string3, string1.name))
else:
db.users.update_one({"_id" : string1.id, "name" : string1.name, "perm" : "administrator"}, upsert=False)
await self.bot.say("Permissions updated on db for user {}".format(string1.name))
The following is the error.
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: update_one() missing 1 required positional argument: 'update'
Document from users collection:
_id: "191598787410526208"
name: "Stevyb0t"
perm: "administrator"

Essentially what other people have commented but with some formatting to make it more legible:
db.users.update_one(
{"_id": string1.id},
{"$set":
{"name": string1.name,
"perm": "administrator"
}})
I also removed upsert=False since that's the default value anyway so you don't need to specify it - though of course being explicit can be helpful
Edit: read all the comments, suggestion was already made to make accepted comment an answer so here it is. My answer is no different from that comment

If your id is a string, you may need to convert it to an ObjectId. Also, you can print the result of the update, and check success:
from bson import ObjectId
result = db.users.update_one(
{"_id": ObjectId(string1.id)},
{"$set":
{"name": string1.name,
"perm": "administrator"}
})
logger.debug('update result: {}'.format(result.raw_result))
if result.matched_count > 0:
# Success code goes here
pass
else:
# Failure code goes here
pass

Related

Error when trying to access nested objects from API response

I am finding that I keep getting errors when accessing objects that are nest in three other objects.
The following is an example of one of the entities returned when accessing the API:
entity {
id: "000069R"
trip_update {
trip {
trip_id: "064650_R..S"
route_id: "R"
start_time: "10:46:30"
start_date: "20220902"
nyct_trip_descriptor {
train_id: "1R 1046+ CTL/95S"
direction: SOUTH
}
}
stop_time_update {
stop_id: "G08S"
arrival {
time: 1662129990
}
departure {
time: 1662129990
}
nyct_stop_time_update {
scheduled_track: "D1"
actual_track: "D1"
}
}
}
This is my code:
import gtfs_realtime_pb2
import urllib.request
trainsets=[
['a','c','e'],
['b','d','f', 'm'],
['g'],
['j','z'],
['n','q','r', 'w'],
['l'],
['1','2','3','4','5','6','7']
]
set_number=0
# get the train from user and find the corresponding trainset
char = input("What train are you looking for? ")
char = char.lower()
for sets in trainsets:
if char in sets:
print('Found it!')
set_number = trainsets.index(sets)
# form string from the values in specified list
desired_trainset= ''.join(trainsets[set_number])
# Communicate with the api
feed = gtfs_realtime_pb2.FeedMessage()
response = urllib.request.Request(f'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-{desired_trainset}')
response.add_header("x-api-key", '<API KEY HERE>')
feed.ParseFromString(urllib.request.urlopen(response).read())
# access specific info from the api
for entity in feed.entity:
if entity.HasField('trip_update'):
#! ERROR: AttributeError: nyct_trip_descriptor
print(entity.trip_update.trip.nyct_trip_descriptor.direction)
#! ERROR: AttributeError: 'google._upb._message.RepeatedCompositeContainer' object has no attribute 'arrival'
print(entity.trip_update.stop_time_update.arrival.time)
The last two print statements are where I keep getting an error. I can access start_time and start_date
or stop_id from the entity just fine, but I keep getting an error when trying to access objects inside other objects.
Using:
Python: 3.10.6
Windows: 11
I've asked a couple of my friends and they don't see the error.
Thank you for helping.
EDIT:
Someone requested to print the entity before the error. This is one of the responses I get back from the data when I do the following code:
print(entity.trip_update.trip)
#response I get back
trip_id: "006600_R..N"
route_id: "R"
start_time: "01:06:00"
start_date: "20220903"
nyct_trip_descriptor {
train_id: "1R 0106 95S/WHL"
}
Someone commented that the nyct_trip_descriptor is an optional field, and after rereading the documentation i confirmed that it is optional but I'm not sure how to proceed further and whether or not that is the reason why I'm getting this error. Thank you for helping.
Edit:
I've done the following to return a default value when 'nyct_trip_descriptor' is missing:
nyct_trip_descriptor = getattr(entity.trip_update.trip, 'nyct_trip_descriptor', 'not found')
print(nyct_trip_descriptor)
This didn't work since all the entities I got from the API just stated 'not found', but when printing just the entity 'nyct_trip_descriptor' is present as seen in the first code snippet.

"The document path provided in the update expression is invalid for update" when trying to update a map value

Here is my sample code
import boto3
import os
ENV = "dev"
DB = "http://awsservice.com"
REGION = "us-east-1"
TABLE = "traffic-count"
def main():
os.environ["AWS_PROFILE"] = ENV
client = boto3.resource("dynamodb", endpoint_url=DB, region_name=REGION)
kwargs = {'Key': {'id': 'D-D0000012345-P-1'},
'UpdateExpression': 'ADD #count.#car :delta \n SET #parentKey = :parent_key, #objectKey = :object_key',
'ExpressionAttributeValues': {':delta': 1, ':parent_key': 'District-D0000012345', ':object_key': 'Street-1'},
'ExpressionAttributeNames': {'#car': 'car', '#count': 'count', '#parentKey': 'parentKey', '#objectKey': 'objectKey'}}
client.Table(TABLE).update_item(**kwargs)
if __name__ == "__main__":
main()
What I want to achieve is this:
With a single API call (in this update_item), I want to be able to
If the item does not exit. create an item with a map count and initialise it with {'car': 1} and set the fields parent_key and object_key.
or
If the item already exists, update the field to {'car': 2} (if the original count is 1)
Previously, if I did not use a map, I can successfully update with this expression,
SET #count = if_not_exist(#count, :zero) + :delta,
#parentKey = :parent_key, #objectKey = :object_key
However I am getting this error:
botocore.exceptions.ClientError: An error occurred
(ValidationException) when calling the UpdateItem operation: The
document path provided in the update expression is invalid for update
Which document path is causing the problem? How can I fix it?
For those who landed on this page with similar error:
The document path provided in the update expression is invalid for update
The reason may be:
for the item on which the operation is being performed,
this attribute (count, for example) is not yet set.
Considering the sample code from question,
The exception could be coming from all those items where count is empty or not set. So the update query doesn't know on which map the new value(s) (car, for example) needs to be set or updated.
In the question, it was possible for the OP in the beginning because, the attribute is not a map and the process is simply setting the value to count as is. It's not trying to access a key of an unknown map to set the value.
This can be handled by catching the exception. For example:
from botocore.exceptions import ClientError
...
try:
response = table.update_item(
Key={
"pk": pk
},
UpdateExpression="set count.car = :c,
ExpressionAttributeValues={
':c': "some car"
},
ReturnValues="UPDATED_NEW"
)
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationException':
response = table.update_item(
Key={
"pk": pk
},
UpdateExpression="set count = :count",
ExpressionAttributeValues={
':count': {
':c': "some car"
}
},
ReturnValues="UPDATED_NEW"
)

Flask JSON Parsing

def api_all():
return jsonify(data)
#app.route('/api/', methods=['GET'])
def api_id():
if('id' in request.args and 'key' in request.args):
id = int(request.args['id'])
key = str(request.args['key'])
else:
return "Error: Missing argument."
results = []
for user in data["users"]: # Error from here
print(user)
if(user['id'] == id and user['key'] == key):
results.append(user)
return jsonify(results)
app.run()
There is my main code.
"users": [
{
"id":"0",
"name":"Jane",
"balance":"100$",
"key":"byt3dsz69pl0hmb"
},
{
"id":"1",
"name":"John",
"balance":"100$",
"key":"z0apdio4bvn549e"
}
]
}
Here is data.json or, the data variable. I cannot get this to work, I get the following error:
TypeError: '_io.TextIOWrapper' object is not subscriptable
I am building a basic api to get account balances, this is juts for educational purposes so it does not have to be secure. Any help would be greatly appreciated I have used flask before but I can never get my head around how JSON works with python, I find it quite hard to understand.
That code snippet converts id from the request to an int, but the value of the id key in the JSON is a string. Since
>>> 1 == "1"
False
>>>
you'll to make the types match before you can compare them.

Alexa Flask Ask Yes / No response handling

I am trying to create a simple Alexa skill using Flask Ask in python.
I have an intent called "SearchIntent" with a "searchterm" slot and the python code looks something like this:
#ask.intent("SearchIntent")
def SearchIntent(searchterm):
resList = []
searchterm = searchterm.lower()
for item in somelist:
if item.find(searchterm) != -1:
resList.append(item)
return question("I Found " + str(len(resList)) + ", Do you want me to list them all?")
I want to check if the response from the user, if he says "Yes" than read all the results:
return statement('\n'.join(resList))
and if the user says no, to perform some other action
something like:
...
return question("I Found " + str(len(resList)) + ", Do you want me to list them all?")
if "return question" == "yes":
do something
else:
do something else
I don't want to create the search function again in a YesIntent, Is it possible to do something like this within the same function?
This is not possible in the suggested way using flask ask. After you call return, you leave your SearchIntent() function and have no way to check the answer or run additional code.
However, you can still make it work: after the user answers your question a new intent is sent and flask-ask calls the according function. By using session attributes, as suggested by #user3872094, you can process your searchterm in this new function. Session attributes are used to preserve user input during a session between different intent requests.
Check this minimal example:
#ask.intent("SearchIntent")
def SearchIntent(searchterm):
session.attributes['term'] = searchterm
return question("I understood {}. Is that correct?".format(searchterm))
#ask.intent('AMAZON.YesIntent')
def yes_intent():
term = session.attributes['term']
return statement("Ok. So your word really was {}.".format(term))
#ask.intent('AMAZON.NoIntent')
def no_intent():
return statement("I am sorry I got it wrong.")
Add the Amazon Yes and No intents to your intent_schema:
{
"intents": [
{
"intent": "SearchIntent",
"slots": [{
"name": "searchterm",
"type": "AMAZON.LITERAL"
}]
},{
"intent": "AMAZON.NoIntent"
}, {
"intent": "AMAZON.YesIntent"
}
]
}

Mongoengine, retriving only some of a MapField

For Example.. In Mongodb..
> db.test.findOne({}, {'mapField.FREE':1})
{
"_id" : ObjectId("4fb7b248c450190a2000006a"),
"mapField" : {
"BOXFLUX" : {
"a" : "f",
}
}
}
The 'mapField' field is made of MapField of Mongoengine.
and 'mapField' field has a log of key and data.. but I just retrieved only 'BOXFLUX'..
this query is not working in MongoEngine....
for example..
BoxfluxDocument.objects( ~~ querying ~~ ).only('mapField.BOXFLUX')
AS you can see..
only('mapField.BOXFLUX') or only only('mapField__BOXFLUX') does not work.
it retrieves all 'mapField' data, including 'BOXFLUX' one..
How can I retrieve only a field of MapField???
I see there is a ticket for this: https://github.com/hmarr/mongoengine/issues/508
Works for me heres an example test case:
def test_only_with_mapfields(self):
class BlogPost(Document):
content = StringField()
author = MapField(field=StringField())
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...',
author={'name': "Ross", "age": "20"}).save()
obj = BlogPost.objects.only('author__name',).get()
self.assertEquals(obj.author['name'], "Ross")
self.assertEquals(obj.author.get("age", None), None)
Try this:
query = BlogPost.objects({your: query})
if name:
query = query.only('author__'+name)
else:
query = query.only('author')
I found my fault! I used only twice.
For example:
BlogPost.objects.only('author').only('author__name')
I spent a whole day finding out what is wrong with Mongoengine.
So my wrong conclusion was:
BlogPost.objects()._collection.find_one(~~ filtering query ~~, {'author.'+ name:1})
But as you know it's a just raw data not a mongoengine query.
After this code, I cannot run any mongoengine methods.
In my case, I should have to query depending on some conditions.
so it will be great that 'only' method overwrites 'only' methods written before.. In my humble opinion.
I hope this feature would be integrated with next version. Right now, I have to code duplicate code:
not this code:
query = BlogPost.objects()
query( query~~).only('author')
if name:
query = query.only('author__'+name)
This code:
query = BlogPost.objects()
query( query~~).only('author')
if name:
query = BlogPost.objects().only('author__'+name)
So I think the second one looks dirtier than first one.
of course, the first code shows you all the data
using only('author') not only('author__name')

Categories