MongoEngine returns nothing - python-3.x

I'm creating a basic flask web application and want to get some data from my mongo database. In my views.py I have that:
from app import app
from app.models import User
#app.route('/')
def index():
return "Hello world"
#app.route("/users")
def about():
users = User.objects()
return users.to_json()
models.py:
from flask_mongoengine import MongoEngine
from app import db
class User(db.Document):
snp = db.StringField(required=False)
g = db.StringField(required=False)
card = db.StringField(required=False)
phone = db.StringField(required=False)
passport = db.StringField(required=False)
group = db.StringField(required=False)
meta = {'collection': 'User'}
def to_json(self):
return {
"name": self.snp
}
When I enter http://127.0.0.1:5000/users I get []. Find returns nothing but in my mongo database I have collection User with two document:
{"_id":{"$oid":"6089d5d13dc15d0a2c5f607e"},"snp":"testov1 test1 testovich1","g":"f","card":"12345","phone":"8888888881","passport":"8888 48484848","group":"users"}
and
{"_id":{"$oid":"6089d5fd3dc15d0a2c5f607f"},"snp":"testov2 test2 testovich2","g":"m","card":"1676767","phone":"88566768881","passport":"8888 234343434","group":"users"}
Connection to database is correct.
I'm new to flask and mongoEngine.

If you print users, you will see a list of 'User' objects, Actually When you try to get all users with User.objects() , the result is of type <class 'flask_mongoengine.BaseQuerySet'>, so calling users.to_json() is wrong since it is not an instance of class User .
But if you get only one user with something like User.objects().first(), the result would be of type "User", and then you can call user.to_json().

Related

Sync to Async Django ORM queryset foreign key property

Seemingly simple situation:
Django model has foreign key:
class Invite(models.Model):
inviter = models.ForeignKey(User, on_delete=models.CASCADE)
...
In async context, I do:
# get invite with sync_to_async decorator, then
print(invite.inviter)
Get async's favorite error:
You cannot call this from an async context - use a thread or sync_to_async
print(sync_to_async(invite.inviter)) # -> throws the same error
Sure, I can do:
#sync_to_async
def get_inviter(self, invite):
return invite.inviter
But, this is senile, if I have to do this for every queryset property call.
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
Yes, resolve the extra fields using select_related:
# Good: pick the foreign_key fields using select_related
user = await Invite.objects.select_related('user').aget(key=key).user
Your other string non-foreign such as strings and ints attributes should already
exist on the model.
Won't work, (although they feel like they should)
# Error django.core.exceptions.SynchronousOnlyOperation ... use sync_to_async
user = await Model.objects.aget(key=key).user
# Error (The field is actually missing from the `_state` fields cache.
user = await sync_to_async(Invite.objects.get)(key=key).user
Other examples for research
A standard aget, followed by a foreign key inspection yields a SynchronousOnlyOperation error.
I have a string key, and a ForeignKey user to the standard user model.
class Invite(models.Model):
user = fields.user_fk()
key = fields.str_uuid()
An example with alternatives that mostly don't work:
Invite = get_model('invites.Invite')
User = get_user_model()
def _get_invite(key):
return Invite.objects.get(key=key)
async def invite_get(self, key):
# (a) works, the related field is populated on response.
user = await Invite.objects.select_related('user').aget(key=key).user
async def intermediate_examples(self, key):
# works, but is clunky.
user_id = await Invite.objects.aget(key=key).user_id
# The `user_id` (any `_id` key) exists for a FK
user = await User.objects.aget(id=user_id)
async def failure_examples(self, key):
# (b) does not work.
user = await sync_to_async(Invite.objects.get)(key=key).user
invite = await sync_to_async(Invite.objects.get)(key=key)
# (c) these are not valid, although the error may say so.
user = await invite.user
user = await sync_to_async(invite.user)
# same as the example (b)
get_invite = sync_to_async(_get_invite, thread_sensitive=True)
invite = get_invite(key)
user = invite.user # Error
# (d) Does not populate the additional model
user = await Invite.objects.aget(key=key).user # Error
print(sync_to_async(invite.inviter)) # -> throws the same error
That's because it's equivalent to:
i = invite.inviter # -> throws the error here
af = sync_to_async(i)
print(af)
The correct usage is:
f = lambda: invite.inviter
af = sync_to_async(f)
i = await af()
print(i)
# As a one-liner
print(await sync_to_async(lambda: invite.inviter)())
Is there a sane way to handle this?
Perhaps, there is a way to do this for all calls like that at once?
(Disclaimer: Not tested in production.)
With nest_asyncio, you could do this:
def do(f):
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(f)())
print(do(lambda: invite.inviter))
Or take it even further:
class SynchronousOnlyAttributeHandler:
def __getattribute__(self, item):
from django.core.exceptions import SynchronousOnlyOperation
try:
return super().__getattribute__(item)
except SynchronousOnlyOperation:
from asgiref.sync import sync_to_async
import asyncio
import nest_asyncio
nest_asyncio.apply()
return asyncio.run(sync_to_async(lambda: self.__getattribute__(item))())
class Invite(models.Model, AsyncUnsafeAttributeHandler):
inviter = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
...
# Do this even in async context
print(invite.inviter)
Does something like this work? Instead of invite.inviter you do await async_resolve_attributes(invite, "inviter")
#sync_to_async
def async_resolve_attributes(instance, *attributes):
current_instance = instance
for attribute in attributes:
current_instance = getattr(current_instance, attribute)
resolved_attribute = current_instance
return resolved_attribute

How to save document using classmethod with mongoengine

I try to use class method in mongoengine to define a method that hash the password and write data to mongodb, then call the method outside the class, here is my code
model.py
from mongoengine import Document, connect, fields
connect("mongoUserDB", host="127.0.0.1", port=27017)
class Users(Document):
userID = fields.StringField(unique=True)
password = fields.StringField()
#classmethod
def signup(cls,**kwargs):
print(kwargs.get("password",cls.password))
cls.password = pbkdf2_sha256.encrypt(kwargs.get("password",cls.password))
print(cls.password)
Users(**kwargs).save()
api.py
from flask import render_template
from blueprint_poc.auth import bp
from models import Users
#bp.route("/sign-up", methods=['GET', 'POST'])
def sign_up():
Users().signup(userID='aaa',password='b')
It did write data to the db, but only with userID info,the password field is missing.
I am not sure what went wrong, any advice for this.

Best practice to call function inside route methode

I'm new to flask and in order to refactor an existing route method on a Flask API, i'm looking for the best practice to reduce it and call method inside the route method.
Acutally the route is designed like that :
#qman.route('/add_report/', methods=['POST'])
def create_report():
"""
Check if data send throught http POST request, is correct based on the report
schema and not already recorded in the table report of the DB.
:param: data from POST request
:return: Ok, valide and imported -> 201, Correct but AlreadyKnown -> 208,
InvalideScheme -> 422
"""
jsonData = request.get_json()
reportSchema = ReportSchema()
try:
data = reportSchema.load(jsonData)
except ValidationError as validation_err:
return(validation_err.messages), 422
nameReportCheck = data["report_name"]
report = Report.query.filter_by(report_name=nameReportCheck).first()
if report is None:
# Create new report
report = Report(
report_name=nameReportCheck,
hostname=data["hostname"],
status=data["status"],
date=data["date"],
nb_analysis_erreur=data["nb_analysis_erreur"]
)
db.session.add(report)
db.session.commit()
NewResult = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "Created new report" , "report" : NewResult}, 201
else :
reportAlreadyKnown = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "This report is already in the DB", "report" : reportAlreadyKnown}, 208
In the facts i would like to call a function named valid_schema(_schema, _jsondata) to check if the data send throught POST request match with my schema of model Report().
This function return a Response() object with serialized data and a 200 code if it's serialization is possible or an error that i cath inside try/except with 400 error code.
def valid_schema(_schema, _jsondata):
schema = _schema()
try:
data = schema.load(_jsondata)
except ValidationError as validation_err:
response = Response(validation_err.messages, 422)
return response
response = Response(data, 200, mimetype="application/json")
return response
Then the route method call an other function named create_report(report_data) if valid_schema(_schema, _jsondata) return report_data and 200 code in response object.
With his args, this method check if the records is not already in the DB and if is not, he create a Report() object from report_data arg and insert this one as a new record into the DB.
In fact I guess I can easily call this method inside the route function but it seem weird and there is probably an other way that I can't find, maybe decorator ?
One possibility for refactoring is the use of webargs, Flask-Marshmallow and marshmallow-sqlalchemy.
With Flask-Marshmallow you can check the input by specifying fields and validators. Webargs offers you the option of validating the defined scheme in a decorator and passing it on to the route as an argument. Using marshmallow-sqlalchemy in combination, this is immediately converted into a database model.
The following example is based on your information and gives you a brief overview of the usage. By defining your own error handler, the error messages can also be sent as JSON. Use in blueprints, views or the like is possible.
from flask import Flask
from flask import jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow.validate import Length, OneOf
from webargs.flaskparser import use_args
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Report(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True)
hostname = db.Column(db.String)
status = db.Column(db.String)
date = db.Column(db.DateTime)
nb_analysis_error = db.Column(db.String)
class ReportSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Report
load_instance = True
sqla_session = db.session
name = ma.Str(required=True, validate=Length(min=3))
hostname = ma.Str(required=True)
date = ma.DateTime(required=True)
status = ma.Str(required=True, validate=OneOf(['online', 'offline']))
nb_analysis_error = ma.Str(missing='Unknown Error')
#app.route('/add_report', methods=['POST'])
#use_args(ReportSchema(), location='json')
def add_report(report):
report_schema = ReportSchema()
_report = Report.query.filter_by(name=report.name).first()
if _report:
report_data = report_schema.dump(_report)
return jsonify(message='Already Reported', report=report_data), 208
else:
db.session.add(report)
db.session.commit()
report_data = report_schema.dump(report)
return jsonify(message='Created', report=report_data), 201
with app.app_context():
db.drop_all()
db.create_all()

Python 3, ODM (umongo), difficulty with relationship fields

I'm having some trouble getting relationships fields to work in umongo. Each Document definition is in a separate file.
In this example i have two basic entities, account and target.
each target has a reference to an account.
// account/schema.py
from datetime import datetime
from pymongo import MongoClient
from umongo import Instance, Document, fields, validate
import os
log = Config.config_logger(__name__)
mongo_url = os.environ.get('MONGO_URL')
db = MongoClient(mongo_url).mydb
instance = Instance(db)
#instance.register
class AccountSchema(Document):
user_id = fields.StringField(required=True, unique=True)
user_name = fields.StringField(required=True)
account_type = fields.StringField(required=True)
class Meta:
collection = db.account
# Make sure that unique indexes are created
AccountSchema.ensure_indexes()
try:
sub = AccountSchema(user_id='my8htwwi', account_type='SUBSCRIPTION', user_name='myuser')
sub.commit()
freeloader = AccountSchema(user_id='ouygouyg', account_type='FREE', user_name='myotheruser')
freeloader.commit()
except Exception:
log.info('account already created')
I've added some manual data there at the bottom, and that works fine when I execute this file, or import it elsewhere.
I define second entity schema for 'target'
// target/schema.py
from datetime import datetime
from pymongo import MongoClient
from umongo import Instance, Document, fields, validate
import os
mongo_url = os.environ.get('MONGO_URL')
db = MongoClient(mongo_url).mydb
instance = Instance(db)
#instance.register
class TargetSchema(Document):
account = fields.ReferenceField('AccountSchema', required=True)
date = fields.DateTimeField(
default=lambda: datetime.utcnow(),
allow_none=False
)
somefield = fields.IntegerField(required=True)
value = fields.IntegerField(required=True)
class Meta:
collection = db.target
# Make sure that unique indexes are created
TargetSchema.ensure_indexes()
service.py
from models.account.schema import AccountSchema
from models.target.schema import TargetSchema
class Service:
self._odm = TargetSchema
....
def save_test(data):
account = AccountRepo().find({'user_id': self._user_id})
# account returns a valid object
item = self._odm(
account=account,
somefield=123123,
value=1234
)
return item.commit()
When I call save_test method, I keep keep getting:
umongo.exceptions.NotRegisteredDocumentError: Unknown document class `AccountSchema`
I get the same error if i try and pass the class object AccountSchema in instead
from models.account.schema import AccountSchema
#instance.register
class TargetSchema(Document):
account = fields.ReferenceField(AccountSchema, required=True)
my feeling is it's about the order that I instantiate/import the instances, but trying to move them around, for example into __init__.py, doesn't seem to change anything.
Or how in each schema definition:
db = MongoClient(mongo_url).target
instance = Instance(db)
All examples I've been able to find keep the referencefield class definition in the same file, so I'm not really sure how to make the different registered instances 'aware' of each other.
so the issue was not declaring and registering all classes to the same db instance.
i.e. in the __init__.py for the models folder i moved:
from umongo import Instance, Document, fields, validate
from pymongo import MongoClient
db = MongoClient(mongo_url).mydb
instance = Instance(db)
then in each schema file:
from models import db
from models import instance

Can't write to MySQL DB

I'm a Flask newbie trying to create a simple app. I'm currently stuck at user registration where I'm trying to save data in database but it's not happening. However, the logging I'm doing indicates that the operation was a success. Could someone tell me what I'm doing wrong?
Here are portions of code that'll help you understand what I'm trying to do:
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
from flask.ext.mysqldb import MySQL
# Configuration
MYSQL_HOST = 'localhost'
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'root'
MYSQL_DB = 'up2date'
DEBUG = True
SECRET_KEY =
'\xc6)\x0f\\\xc5\x86*\xd7[\x92\x89[\x95\xcfD\xfd\xc1\x18\x8e\xf1P\xf7_\r'
# Create the flask app
app = Flask(__name__)
app.config.from_object(__name__)
# Create instance for working with MySQL
mysql = MySQL(app)
# Function to connect to DB
def connect_db():
return mysql.connection.cursor()
# define functions that will make DB available automatically on each request
#app.before_request
def before_request():
g.db = connect_db()
#app.teardown_request
def teardown_request(exception):
g.db.close()
And finally, the code that performs user registration:
#app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
result = g.db.execute('INSERT INTO users (email, password) VALUES (%s, %s)', [email, password])
print(email, password)
print(result, " rows affected")
flash('Registration successful! You may log in now.')
return redirect(url_for('show_home'))
The two print statements confirm that the email address and password were captured correctly, and the result variable contains 1, indicating 1 row affected. But still there's no row in the DB. I earlier thought this had something to do with committing, but g.db.commit() throws error: AttributeError: 'Cursor' object has no attribute 'commit'
I assume you use MySQL-python.
connect_db() returns the cursor and not the connection. The cursor does not have a commit() function, as the exception says, however the connection has the commit function you need. I think you need to do this:
def connect_db():
return mysql.connection
For more info you can take a look at the code.

Resources