Read MongoEngine DynamicDocuments - python

my issue is that I am saving dict objects with MongoEngine:
class MongoRecord(DynamicDocument):
record_id = SequenceField(primary_key = True)
class SimpleMongo(object):
def __init__(self, *args, **kwargs):
"""
Very simple dict-like Mongo interface
"""
if PY_VERSION == 2:
self.iterattr = 'iteritems'
else:
self.iterattr = 'items'
self.debug = DEBUG
self.dict_type = type(dict())
self.dbname = kwargs.get('dbname', 'untitled')
self.collection_name = kwargs.get('collection', 'default')
self.ip = kwargs.get('ip', '127.0.0.1')
self.port = kwargs.get('port', 27017)
self.dbconn = connect(self.dbname, host=self.ip, port=self.port)
drop = kwargs.get('drop', False)
if drop:
self.dbconn.drop_database(self.dbname)
def put(self, data):
"""
Put dict
"""
assert type(data) == self.dict_type
record = MongoRecord()
record.switch_collection(self.collection_name)
generator = getattr(data, self.iterattr)
__res__ = [setattr(record, k, v) for k,v in generator()] # iteritems() for Python 2.x
record.save()
but when trying to access them:
def get(self):
record = MongoRecord()
record.switch_collection(self.collection_name)
return record.objects
getting
mongoengine.queryset.manager.QuerySetManager object, not an iterator.
So, what is the proper way to get my data back from Mongo being saved as DynamicDocument?

The problem isn't that MongoRecordis a DynamicDocument or that it contains a dict. You would get the same result with a regular Document. Your problem is with querying, you should change record.objects to MongoRecord.objects to get a cursor.
Regarding your usage of switch_collection()...
If MongoRecord documents will be saved to a collection with the same name, at most times, you can define this like below, and you don't have to use switch_collection() when a collection with that name is being queried.
class MongoRecord(DynamicDocument):
record_id = SequenceField(primary_key = True)
meta = {'collection': 'records'}
In case you do want to retrieve MongoRecord documents from a collection which isn't called 'records', and you want to define a function for this (which can give an UnboundLocalError), you can do it like this (source):
from mongoengine.queryset import QuerySet
def get(self):
new_group = MongoRecord.switch_collection(MongoRecord(), self.collection_name)
new_objects = QuerySet(MongoRecord, new_group._get_collection())
all = new_objects.all()
# If you would like to filter on an MongoRecord attribute:
filtered = new_objects.filter(record_id=1)
return all

Related

Says 'AnimalShelter' object has no attritbute 'create' but everything is there?

I am trying to test my AnimalShelter.py code in Jupyter Notebook but I keep getting the error that the 'create' attribute is not there. Did I miss something? I have it defined in the code but no matter what I change it still says it is not there.
AnimalShelter.py code:
import pymongo
from pymongo import MongoClient
from bson.objectid import ObjectId
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
#Initializing the MongoClient. This helps to access the MongoDB databases and collections.
self.client = MongoClient('mongodb://%s:%s#localhost:45344' % (username, password))
#where xxxx is your unique port number
self.database = self.client['AAC']
#Complete this create method to implement the C in CRUD.
def create(self, data):
if data is not None:
insert = self.database.animals.insert(data) #data should be dictionary
else:
raise Exception("Nothing to save, because data parameter is empty")
#Create method to implement the R in CRUD.
def read(self, searchData):
if searchData:
data = self.database.animals.find(searchData, {"_id": False})
else:
data = self.database.animals.find({}, {"_id": False})
return data
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
if searchData is not None:
result = self.database.animals.update_many(searchData, {"$set": updateData})
else:
return "{}"
return result.raw_result
#Create method to implement D in CRUD.
def delete(self, deleteData):
if deleteData is not None:
result = self.database.animals.delete_many(deleteData)
else:
return "{}"
return result.raw_result
The test code I am trying to use to see that CRUD is functioning properly:
from AnimalShelter import AnimalShelter
data = {}
query = {}
test_class = AnimalShelter()
#test each function in the AnimalShelter class
response = test_class.create(data)
assert response
response = test_class.read(data)
assert response
response = test_class.update(data)
assert response
response = test_class.delete(data)
assert response
I am at a loss. I am new to Jupyter Notebook with the whole testing thing and I figured this was the simplest way to test the attributes before making a different test for specific data from the python code but either way I still get that the create attribute doesn't exist!
I created a slightly altered version of your code for easy testing and got the error that no username and password were being passed to the AnimalShelter constructor.
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
print("constructor")
#Complete this create method to implement the C in CRUD.
def create(self, data):
print("create")
#Create method to implement the R in CRUD.
def read(self, searchData):
print("read")
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
print("update")
#Create method to implement D in CRUD.
def delete(self, deleteData):
print("delete")
test_class = AnimalShelter("user", "pass")
data = {}
test_class.create(data)
test_class.read(data)
test_class.update(data, {})
test_class.delete(data)
Could your error be because you didn't pass these variables when constructing the test_class object? Upon passing "user" and "pass", I got the expected output:
constructor
create
read
update
delete

MongoDB find() returns empty array

I have a simple flask app that does CRUD operations. I have a python module where I defined all functions and they all work fine; I can create, read, update and delete. I added a retrieve method where I call find() and it does retrieve the cursor. The problem is I can't get it to populate the data, all I get is an empty array []. I looped through the cursor and I used loads and dumps from bson.json_util, but I get the same result. Here is what the class looks like:
from pymongo import MongoClient
class AnimalShelter(object):
def __init__(self, username, password):
# Access the MongoDB databases and collections.
self.client = MongoClient('localhost', ####)
self.db = self.client['project']
self.username = username
self.password = password
# Create
def create(self, data):
if data is not None:
return self.db.collection.insert(data)
else:
print("Nothing to save, because data parameter is empty")
return False
# Read
...
# Update
...
# Delete
...
# Retrieve
def retrieve(self, data, projection):
if data is not None:
return self.db.collection.find(data, projection)
else:
return False
In the test file, I have:
animals = AnimalShelter(username, password)
try:
dog = dumps(list(animals.retrieve({"animal_type": "Dog", "name": "Flow"}, {"_id": False})))
print(dog)
except:
print("Unable to find the outcome")
Any help would be appreciated!

What is the proper way to delineate modules and classes in Python?

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.
For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:
Notes.py
|
|------ Notepad (class)
|------ Note (class)
From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".
Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.
My goal here is to understand what the best practice is.
As long as the classes are rather small put them into one file.
You can still move them later, if necessary.
Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.
I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).
# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
"""A note. This class is part of the data model and is instantiated every
time there access to the database"""
def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
self.id = noteid
self.note = note
self.date = date
self.ctx = context #context holds the db connection and some globals
def get(self):
"""Get the current object from the database. This function needs the
instance to have an id"""
if id == 0:
raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
cursor = self.ctx.db.conn.cursor()
cursor.execute("select note, date from %s.notes where id=%s" %
(self.ctx.db.DB_NAME, str(self.id)))
data = cursor.fetchone()
if not data:
raise self.ctx.ApplicationError(404, ("No note with id "
+ self.id + " was found"))
self.note = data[0]
self.date = data[1]
return self
def insert(self, user):
"""This function inserts the object to the database. It can be an empty
note. User must be authenticated to add notes (authentication handled
elsewhere)"""
cursor = self.ctx.db.conn.cursor()
query = ("insert into %s.notes (note, owner) values ('%s', '%s')" %
(self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
cursor.execute(query)
return self
def put(self):
"""Modify the current note in the database"""
cursor = self.ctx.db.conn.cursor()
query = ("update %s.notes set note = '%s' where id = %s" %
(self.ctx.db.DB_NAME, str(self.note), str(self.id)))
cursor.execute(query)
return self
def delete(self):
"""Delete the current note, by id"""
if self.id == 0:
raise self.ctx.ApplicationError(404, "No note with id 0 exists")
cursor = self.ctx.db.conn.cursor()
query = ("delete from %s.notes where id = %s" %
(self.ctx.db.DB_NAME, str(self.id)))
cursor.execute(query)
def toJson(self):
"""Returns a json string of the note object's data attributes"""
return json.dumps(self.toDict())
def toDict(self):
"""Returns a dict of the note object's data attributes"""
return {
"id" : self.id,
"note" : self.note,
"date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
}
class NotesCollection():
"""This class handles the notes as a collection"""
collection = []
def get(self, user, context):
"""Populate the collection object and return it"""
cursor = context.db.conn.cursor()
cursor.execute("select id, note, date from %s.notes where owner=%s" %
(context.db.DB_NAME, str(user["id"])))
note = cursor.fetchone()
while note:
self.collection.append(Note(note[0], note[1],note[2]))
note = cursor.fetchone()
return self
def toJson(self):
"""Return a json string of the current collection"""
return json.dumps([note.toDict() for note in self.collection])
I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done
Usage example:
class NotesCollection(BaseHandler):
#tornado.web.authenticated
def get(self):
"""Retrieve all notes from the current user and return a json object"""
allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
json = allNotes.toJson()
self.write(json)
#protected
#tornado.web.authenticated
def post(self):
"""Handles all post requests to /notes"""
requestType = self.get_argument("type", "POST")
ctx = settings["context"]
if requestType == "POST":
Note.Note(note = self.get_argument("note", ""),
context = ctx).insert(self.get_current_user())
elif requestType == "DELETE":
Note.Note(id = self.get_argument("id"), context = ctx).delete()
elif requestType == "PUT":
Note.Note(id = self.get_argument("id"),
note = self.get_argument("note"),
context = ctx).put()
else:
raise ApplicationError(405, "Method not allowed")
By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

Convert mongodb return object to dictionary

I'm using the bottle framework together with mongoengine.
I have an orders model :
class OrderDetail(Option):
orderDetailsQty = FloatField()
def to_dict(self):
return mongo_to_dict_helper(self)
class Order(Document):
userName = StringField(required=True)
orderDate = DateTimeField()
orderStatus = ListField(EmbeddedDocumentField(Status))
orderDetails = ListField(EmbeddedDocumentField(OrderDetail))
orderComments = ListField(EmbeddedDocumentField(Comment))
isActive = BooleanField()
def to_dict(self):
orderObj = mongo_to_dict_helper(self)
orderDetailList = []
for orderDetail in orderObj["orderDetails"]:
orderDetailList.append(orderDetail.__dict__)
orderObj["OrderDetails"] = orderDetailList
return (self)
When mongodb is queried I get an object which is then converted in to a dict by using the following function :
def mongo_to_dict_helper(obj):
return_data = []
for field_name in obj._fields:
if field_name in ("id",):
continue
data = obj._data[field_name]
if isinstance(obj._fields[field_name], StringField):
return_data.append((field_name, str(data)))
elif isinstance(obj._fields[field_name], FloatField):
return_data.append((field_name, float(data)))
elif isinstance(obj._fields[field_name], IntField):
return_data.append((field_name, int(data)))
elif isinstance(obj._fields[field_name], ListField):
return_data.append((field_name, int(data)))
else:
# You can define your logic for returning elements
pass
return dict(return_data)
I found this function after a long search in the internet. Later found out that this function also fails while defining a member as the ListField(EmbeddedDocumentField(obj)).
I also tried writing a condition for catching the specific case of EmbeddedDocumentField :
elif isinstance(obj._fields[field_name], EmbeddedDocumentField):
return_data.append(mongo_to_dict_helper(data))
but that didn't do any good either.
Anyone have a workaround for this issue ?
What about just using to_mongo method of an object to convert it to a dict?
object.to_mongo()
Expanding on #alexvassel's and #z0r's answers, calling .to_mongo() converts the object to a SON instance. Once you have it, you can call its .to_dict() method to convert it to a dictionary.
For example... (qset is a queryset that's returned from mongoengine, after e.g. Posts.objects.all()).
sons = [ob.to_mongo() for ob in qset]
for son in sons:
print str(son.to_dict())
import json
json.loads(yourobj.to_json())
Extending on #alexvassel's answer, to_mongo() method returns SON object, which you can convert to dict by calling its to_dict() method
object.to_mongo().to_dict()
you can custom method to convert object to dict
class Order(Document):
userName = StringField(required=True)
orderDate = DateTimeField()
orderStatus = ListField(EmbeddedDocumentField(Status))
orderDetails = ListField(EmbeddedDocumentField(OrderDetail))
orderComments = ListField(EmbeddedDocumentField(Comment))
isActive = BooleanField()
def as_dict(self):
return {
"user_name": self.userName,
"order_date": self.orderDate.strftime("%Y-%m-%d %H:%M:%S"),
}
now you can use obj.as_dict() to dict
orders = Order.objects.all()
datas = [each.as_dict() for each in orders]
combining all other answers,
import json
dict = {'data':[json.loads(ob.to_json()) for ob in qset]}
There can be two scenario.
when query returns CommandCursor object
**records = list(CursorObject)**
ex - Class.objects().aggregate({...})
when query returns BaseQuerySet object
**import json**
**records = json.loads(BaseQuerySetObject.to_json())**
ex - Class.objects().filter(..)

Learning how to use the deferred library

I'm trying code nearly identical to the example from the manual to enable a download counter but I get an exception:
File "/media/Lexar/montao/wwwblob/handler.py", line 117, in FileInfo
download_count = db.IntegerProperty(required=True, count=0) TypeError: init() got an unexpected keyword argument 'count'
Here's the code I try to run:
from google.appengine.ext import deferred
from google.appengine.runtime import DeadlineExceededError
class Mapper(object):
# Subclasses should replace this with a model class (eg, model.Person).
KIND = None
# Subclasses can replace this with a list of (property, value) tuples to filter by.
FILTERS = []
def __init__(self):
self.to_put = []
self.to_delete = []
def map(self, entity):
"""Updates a single entity.
Implementers should return a tuple containing two iterables (to_update, to_delete).
"""
return ([], [])
def finish(self):
"""Called when the mapper has finished, to allow for any final work to be done."""
pass
def get_query(self):
"""Returns a query over the specified kind, with any appropriate filters applied."""
q = self.KIND.all()
for (prop, value) in self.FILTERS:
q.filter('%s =' % prop, value)
q.order('__key__')
return q
def run(self, batch_size=100):
"""Starts the mapper running."""
self._continue(None, batch_size)
def _batch_write(self):
"""Writes updates and deletes entities in a batch."""
if self.to_put:
db.put(self.to_put)
self.to_put = []
if self.to_delete:
db.delete(self.to_delete)
self.to_delete = []
def _continue(self, start_key, batch_size):
q = self.get_query()
# If we're resuming, pick up where we left off last time.
if start_key:
q.filter('__key__ >', start_key)
# Keep updating records until we run out of time.
try:
# Steps over the results, returning each entity and its index.
for (i, entity) in enumerate(q):
(map_updates, map_deletes) = self.map(entity)
self.to_put.extend(map_updates)
self.to_delete.extend(map_deletes)
# Do updates and deletes in batches.
if (i + 1) % batch_size == 0:
self._batch_write()
# Record the last entity we processed.
start_key = entity.key()
self._batch_write()
except DeadlineExceededError:
# Write any unfinished updates to the datastore.
self._batch_write()
# Queue a new task to pick up where we left off.
deferred.defer(self._continue, start_key, batch_size)
return
self.finish()
class FileInfo(db.Model):
blob = blobstore.BlobReferenceProperty(required=True)
download_count = db.IntegerProperty(required=True, count=0)
uploaded_by = db.UserProperty(required=True)
uploaded_at = db.DateTimeProperty(required=True, auto_now_add=True)
class DailyTotal(db.Model):
date = db.DateProperty(required=True, auto_now_add=True)
file_count = db.IntegerProperty(required=True)
download_count = db.IntegerProperty(required=True)
class DownloadCountMapper(Mapper):
KIND = FileInfo
def __init__(self):
self.file_count = 0
self.download_count = 0
def map(self, file):
self.file_count += 1
self.download_count += file.download_count
def finish(self):
total = DailyTotal(file_count=self.file_count,
download_count=self.download_count)
total.put()
Can you tell me what I should do?
Thank you
This line is the culprit:
download_count = db.IntegerProperty(required=True, count=0)
The IntegerProperty constructor doesn't know what to do with count. Maybe you meant this:
download_count = db.IntegerProperty(required=True, default=0)

Categories