I've just started working with Celery on my latest project with work; I'm having a bit of trouble with executing tasks asynchronously.
All the code is taken from Miguel Grinbergs 'Using Celery with Flask'
When sending the mail without executing a task, it sends perfectly fine. Though when I attempt to send the mail delayed, it fails out giving me an error as follows.
smtplib.SMTPSenderRefused: (530, b'5.5.1 Authentication Required. Learn more at\n5.5.1 https://support.google.com/mail/answer/14257 h19sm960819igq.6 - gsmtp', 'email-removed#gmail.com')
Here's the code I'm using, in my Flask app.
import os
import time
from flask import Flask, request, render_template, session, flash, redirect, url_for, jsonify
from flask.ext.mail import Mail, Message
from celery import Celery
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-duper-secret'
# Celery Configuration
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
# Configuration for Flask-Mail
app.config['MAIL_SERVER'] = "smtp.gmail.com"
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USERNAME'] = 'email-removed#gmail.com'
app.config['MAIL_PASSWORD'] = 'password-removed'
app.config['MAIL_DEFAULT_SENDER'] = 'email-removed#gmail.com'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
mail = Mail(app)
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html', email=session.get('email', ''))
email = request.form['email']
session['email'] = email
msg = Message("Hello from Flask", recipients=[email])
msg.body = "This is a test message from the flask application!"
if request.form['submit'] == "Send":
send_async_email(msg)
flash('Sending email to {0}'.format(email))
else:
send_async_email.apply_async(args=[msg], countdown=20)
flash('An email to {0} will be sent in a minute.'.format(email))
return redirect(url_for('index'))
#celery.task()
def send_async_email(msg):
with app.app_context():
mail.send(msg)
if __name__ == "__main__":
app.run(debug=True)
I'd appreciate some insight, and perhaps an explanation on how to make this work, and why it isn't working.
I've also looked upon other threads here, and turned on insecure-application access for my google accounts, along with nulling the captcha as suggested in the errors returned URL.
Related
I have a small flask todo app, and trying to deploy it in heroku, but getting errors and I am unable to solve it, source code is here, this is working perfectly,
error screenshot can be seen here => http://prntscr.com/kyfwmy
Here is my app.py:
from flask import Flask, render_template, request, jsonify, url_for, redirect
from flask_cors import CORS
from flask_pymongo import PyMongo, pymongo
import sys, time
from bson.json_util import dumps, ObjectId
app = Flask(__name__)
app.config['MONGO_DBNAME']='todo'
app.config['MONGO_URI']='mongodb://todo_task:todo_task*123*#ds111082.mlab.com:11082/todo'
mongo = PyMongo(app)
cors = CORS(app, resources={r'/ajax/*': {"origins": '*'}})
#app.route('/')
def index():
_tasks = mongo.db.tasks.find().sort('created_at', pymongo.DESCENDING)
return render_template('index.html', tasks=_tasks)
#app.route('/add_task', methods=['POST'])
def add_task():
if request.method == 'POST':
tasks = mongo.db.tasks
data = {
'task': request.form['task'],
'status': 'view',
'created_at': time.strftime('%d-%m-%Y %H:%M:%S'),
'updated_at': time.strftime('%d-%m-%Y %H:%M:%S')
}
tasks.insert(data)
return redirect(url_for('index'))
#app.route('/destroy_task')
def task_destroy():
if request.method == 'GET':
id = request.args.get('id')
tasks = mongo.db.tasks
result = tasks.find_one({'_id': ObjectId(id)})
tasks.remove(result)
return redirect(url_for('index'))
#app.route('/ajax/task_update', methods=['POST'])
def task_update():
id = request.form['id']
tasks = mongo.db.tasks
result = tasks.find_one({'_id': ObjectId(id)})
if result['status'] == 'completed':
result['status'] = 'view'
res = {"status": 'view'}
else:
result['status'] = 'completed'
res = {"status": 'completed'}
result['updated_at'] = time.strftime('%d-%m-%Y %H:%M:%S')
tasks.save(result)
return jsonify({'status': res})
#app.route('/actives')
def actives():
tasks = mongo.db.tasks
_tasks = tasks.find({'status': 'view'}).sort('created_at', pymongo.DESCENDING)
return render_template('index.html', tasks=_tasks)
#app.route('/completes')
def completes():
tasks = mongo.db.tasks
_tasks = tasks.find({'status': 'completed'}).sort('created_at', pymongo.DESCENDING)
return render_template('index.html', tasks=_tasks)
#app.route('/clear_completes')
def clear_completes():
tasks = mongo.db.tasks
tasks.remove({'status': 'completed'})
return redirect(url_for('index'))
app.run(debug=True)
https://github.com/IrfanMumtaz/python-todo-app
You're not telling Flask what port to use, so it's trying to use port 5000 (its default):
app.run(debug=True)
Heroku tells you what port to use via the PORT environment variable. You need to use that variable's value when you run your application.
Something like this should work:
import os
# ...
app.run(port=os.getenv('PORT', 5000))
You will probably also want to disable debug mode:
Attention:
Even though the interactive debugger does not work in forking environments (which makes it nearly impossible to use on production servers), it still allows the execution of arbitrary code. This makes it a major security risk and therefore it must never be used on production machines.
Im writing a dummy website for class and I'm having trouble connecting my Heroku Database to my app that's local for now until I push to Heroku.
I'm not sure what the proper way to do this, and I've searched many videos/forums and I can't seem to get a straight answer from them. Ill post some of my code below. In dbconnect.py where the insert the heroku database credentials, like the URI, host, etc?
#app.py
from flask import Flask, render_template, redirect, url_for, request, session, flash
from functools import wraps
app = Flask(__name__)
app.secret_key = "Gundam"
# login required decorator
def login_required(f):
#wraps(f)
def wrap(*args, **kwargs):
if 'logged_in' in session:
return f(*args, **kwargs)
else:
flash('You need to login first.')
return redirect(url_for('login_page'))
return wrap
#app.route('/')
def homepage():
return render_template("main.html")
#app.route('/dashboard/')
#login_required
def dashboard():
return render_template("dashboard.html")
#app.errorhandler(404)
def page_not_found(e):
return render_template("404.html")
#app.route('/login/', methods=["GET", "POST"])
def login_page():
error = ''
try:
if request.method == "POST":
attempted_username = request.form['username']
attempted_password = request.form['password']
if attempted_username == "admin" and attempted_password == "password":
session['logged_in'] = True
flash('You were just logged in!')
return redirect(url_for('dashboard'))
else:
error = "Invalid Username or Password."
return render_template("login.html", error=error)
except Exception as e:
return render_template("login.html", error=error)
#app.route('/logout/')
def logout():
session.pop("logged_in", None)
return redirect(url_for('homepage'))
if __name__ == '__main__':
app.run(debug = True)
dbconnect.py
import os
import psycopg2
import urlparse
urlparse.uses_netloc.append("postgres")
url = urlparse.urlparse(os.environ[""])
conn = psycopg2.connect(
database=url.path[1:],
user=url.username,
password=url.password,
host=url.hostname,
port=url.port
)
You have to install the postgres database addon in heroku first. Run the heroku toolbelt in your computer and enter the command heroku addons:create heroku-postgresql:hobby-dev. Hobby-dev is the free version.
Once Heroku Postgres has been added a DATABASE_URL setting will be available in the app configuration and will contain the URL used to access the newly provisioned Heroku Postgres service. Use the value as your database uri. The app configuration can be accessed from your dashboard. Under settings, click Reveal config vars. You can also use the toolbelt command. See heroku config -h.
So now you can do:
url = urlparse.urlparse(os.environ["DATABASE_URL"])
For more details see https://devcenter.heroku.com/articles/heroku-postgresql
Just use the following code snippet on your python app. That should do the trick.
import os
import psycopg2
DATABASE_URL = os.environ['DATABASE_URL']
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
I am trying to use Sendgrid's Python API to handle transactional emails in my Flask app.
When I fire up the localhost server, I am getting a 401 error when I complete the invite form and attempt to connect with the sendgrid API and I can't understand why.
I import my sendgrid API key from a sendgrid.env file.
The app's init:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import sendgrid
import os
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://unible:test#localhost/unible'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SECRET_KEY'] = '\xea5\x1b.S3ME\x86\x8bP/\x86T\xca\x8f\xf4S\xe2h\x9c\xe5\xc6h'
app.config['WTF_CSRF_ENABLED'] = True
app.config['DAYS_TIL_INVITES_EXPIRE'] = 28
db = SQLAlchemy(app)
sendgrid = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY'))
import unible.routes
The routing file:
Then import the sendgrid object into my routes file to create and send mail.
from unible import app, sendgrid
from flask import render_template, request, redirect, url_for, abort, flash
from unible.models.invite import Invite
from .forms.InviteForm import InviteForm
from sendgrid.helpers.mail import *
import urllib
#app.route('/account/invites/new', methods=['GET','POST'])
def new_invites():
form = InviteForm()
if request.method == 'GET':
return render_template('invite_form.html', form=form)
if request.method == 'POST':
invite_from = 1 # for testing only, will be replaced by session user id
if form.validate_on_submit():
invite_for = form.email.data.lower()
invite = Invite(invite_from, invite_for)
from_email = Email("test#example.com")
subject = "Hello World from the SendGrid Python Library!"
to_email = Email("test#example.com")
content = Content("text/plain", "Hello, Email!")
msg = Mail(from_email, subject, to_email, content)
try:
response = sendgrid.client.mail.send.post(request_body=msg.get())
print(response.status_code)
print(response.body)
print(response.headers)
except urllib.error.HTTPError as e:
print(e.read())
flash('An invite email was sent to %s' % invite_for)
return redirect(url_for('new_invites'))
else:
return redirect(url_for('new_invites'))
The error I get back is this:
b'{"errors":[{"message":"The provided authorization grant is invalid, expired, or revoked","field":null,"help":null}]}'
However when I run the same sample code from here outside of a server it runs fine and sends?
I'm following this tutorial: http://blog.miguelgrinberg.com/post/using-celery-with-flask .
I've imported all the packages and running the app I've got no errors. But when I press send, I'm not getting any mail in my mail account. I've added a print statement in the send_async_email. It seems the celery task is not executing. My code:
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
app.config['SECRET_KEY'] = 'super_secret_key'
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'my_mail#gmail.com'
app.config['MAIL_PASSWORD'] = 'password'
app.config['MAIL_DEFAULT_SENDER'] = 'sender#gmail.com'
mail = Mail(app)
#celery.task
def send_async_email(msg):
print "msg sent"
with app.app_context():
mail.send(msg)
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html', email=session.get('email', ''))
email = request.form['email']
session['email'] = email
# send the email
msg = Message('Hello from Flask',
sender=app.config['MAIL_USERNAME'],
recipients=['ikram.tanjib#gmail.com'])
msg.body = 'This is a test email sent from a background Celery task.'
if request.form['submit'] == 'Send':
# send right away
send_async_email.apply_async(args=[msg])
flash('Sending email to {0}'.format(email))
else:
# send in one minute
send_async_email.apply_async(args=[msg], countdown=60)
flash('An email will be sent to {0} in one minute'.format(email))
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
Edit: Solved my own problem
I didn't start the celery worker venv/bin/celery worker -A app.celery --loglevel=info.
In project folder activated virtualenv and ran the celery worker venv/bin/celery worker -A app.celery --loglevel=info. And now it's working.
I am trying to follow this tutorial. When I try to submit the contact form, which should trigger the email, I get an internal server error. The error log says:
RuntimeError: The curent application was not configured with Flask-Mail
The instructions say to use from flask.ext.mail to import but I've seen that it might be from flask_mail now. I've also tried changing the mail port from 465 to 587. Neither of these changes have been fixed the problem. My most up to date code is:
from flask import Flask, render_template, request, flash
from forms import ContactForm
from flask_mail import Mail, Message
mail = Mail()
app = Flask(__name__)
app.secret_key = 'development key'
app.config["MAIL_SERVER"] = "smtp.gmail.com"
app.config["MAIL_PORT"] = 587
app.config["MAIL_USE_SSL"] = True
app.config["MAIL_USERNAME"] = 'contact_email#gmail.com' ## CHANGE THIS
app.config["MAIL_PASSWORD"] = 'password'
mail.init_app(app)
app = Flask(__name__)
app.secret_key = 'Oh Wow This Is A Super Secret Development Key'
#app.route('/')
def home():
return render_template('home.html')
#app.route('/about')
def about():
return render_template('about.html')
#app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if request.method == 'POST':
if form.validate() == False:
flash('All fields are required.')
return render_template('contact.html', form=form)
else:
msg = Message(form.subject.data, sender='contact_email#gmail.com', recipients=['recipient#gmail.com'])
msg.body = """
From: %s <%s>
%s
""" % (form.name.data, form.email.data, form.message.data)
mail.send(msg)
return render_template('contact.html', success=True)
elif request.method == 'GET':
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
You created a second app (presumably by accident) after configuring the initial app. Right now the "first" app is configured and has the extension registered, but the "second" app is used to register the routes and call .run().
Remove the line after mail.init_app(app), the second app = Flask(__name__) that creates another app.