I'm just join the stackoverflow community right now because I trying to complete my first Python-flask application and I got some problem to fetch and compare a single date for single user with sqlalchemy & sqlite.
Here my imports:
from flask import Flask, request, jsonify, render_template, url_for, redirect, flash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import create_engine, MetaData, func
import datetime
Here my table database model:
class BloodDonation(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String, nullable=False)
age = db.Column(db.Integer, nullable=False)
blood_groups = db.Column(db.String, nullable=False)
city = db.Column(db.String, nullable=False)
phone_no = db.Column(db.String, nullable=False)
latest_donation = db.Column(db.Date, nullable=False)
next_donation = db.Column(db.Date)
donation_counter = db.Column(db.Integer, default=1)
This is a easy CRUD application where I should store blood donations inside a database with 3 months threshold donation per user.
To do that I use the mobile number as user identifier and I use datetime module, so when an user try to do more than an donation before 90 days the application have to show the message donation forbidden.
Here my code:
#app.route('/blood_donation', methods=['GET','POST'])
def blood_donation():
if request.method == 'GET':
blood_groups = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']
return render_template('donate.html', blood_groups = blood_groups)
else:
donation_day = datetime.date.today()
next_donation_date = threshold_time(donation_day) # this func return donation date + 90 days
data_fields = ['name','age','blood_groups','city','phone_no']
data_dict = {}
data_dict['latest_donation'] = donation_day
data_dict['next_donation'] = next_donation_date
for field in data_fields:
data_dict[field] = request.form.get(field).lower()
for value in data_dict.values():
if value == "":
return "Please enter all the details."
counter = db.session.query(BloodDonation).filter(BloodDonation.phone_no == data_dict['phone_no']).count()
next_bd_date = db.session.query(BloodDonation).filter(func.DATE(BloodDonation.latest_donation) >= 0)
if counter >= 0 and next_bd_date and\
db.session.query(BloodDonation).filter(func.DATE(BloodDonation.latest_donation)\
<= data_dict['latest_donation']):
counter += 1
data_dict['donation_counter'] = counter
blood_donation = BloodDonation(**data_dict)
db.session.add(blood_donation)
db.session.commit()
return redirect(url_for('home'))
return "Donation forbidden!!!"
I think the problem is in the if statement to compare dates and mobile number that I use as donation counter and user identifier since the application continue update the database without return donation forbidden.
Here the wrong code:
if counter >= 0 and next_bd_date and \
db.session.query(BloodDonation).filter(func.DATE(BloodDonation.latest_donation) <=
data_dict['latest_donation']):
I hope somebody can help me since I am not a pro but a beginner
Thanks in advance!
It looks like you might have several spacing issues. Python doesn't like it if you don't have proper spacing. Try this. Also I wasn't sure where you wanted that final return statement so I'm not sure if I spaced it correctly.
#app.route('/blood_donation', methods=['GET','POST'])
def blood_donation():
if request.method == 'GET':
blood_groups = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']
return render_template('donate.html', blood_groups = blood_groups)
else:
donation_day = datetime.date.today()
next_donation_date = threshold_time(donation_day)
data_fields = ['name','age','blood_groups','city','phone_no']
data_dict = {}
data_dict['latest_donation'] = donation_day
data_dict['next_donation'] = next_donation_date
for field in data_fields:
data_dict[field] = request.form.get(field).lower()
for value in data_dict.values():
if value == "":
return "Please enter all the details."
counter = db.session.query(BloodDonation).filter(BloodDonation.phone_no == data_dict['phone_no']).count()
next_bd = db.session.query(BloodDonation).filter(func.DATE(BloodDonation.latest_donation) >= 0)
if counter >= 0 and next_bd and \
db.session.query(BloodDonation).filter(func.DATE(BloodDonation.latest_donation) <= data_dict['latest_donation']):
counter += 1
data_dict['donation_counter'] = counter
blood_donation = BloodDonation(**data_dict)
db.session.add(blood_donation)
db.session.commit()
return redirect(url_for('home'))
return "Donation forbidden!!!"
Related
As far as I can tell the assert calls for id and goal_id AND my code provides them both...
enter image description here
goal_routes.py
from datetime import datetime from typing import OrderedDict from
urllib.request import OpenerDirector from flask import Blueprint,
jsonify, request, make_response, abort from app import db from
app.models.goal import Goal from app.models.task import Task from
app.task_routes import validate_task
Create a Goal: goal_bp = Blueprint("goal_bp", name, url_prefix="/goals")
#goal_bp.route("", methods = ["POST"]) def create_goals():
request_body = request.get_json()
if "title" in request_body:
new_goal = Goal(
title = request_body["title"]
)
else:
return jsonify({"details":"Invalid data"}), 400
db.session.add(new_goal)
db.session.commit()
goal_response = {"goal": new_goal.to_dictionary()}
return (jsonify(goal_response), 201)
Get Goals #goal_bp.route("", methods = ["GET"]) def get_goals():
sort = request.args.get("sort")
#Sort by assending (is default?)
if sort == "asc":
goals =Goal.query.order_by(Goal.title)
#Sort by decending
elif sort == "desc":
goals =Goal.query.order_by(Goal.title.desc())
#No Sort
else:
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_dictionary())
# If No Saved Goals wil stil return 200
return (jsonify(goals_response), 200)
Get One Goal: One Saved Goal #goal_bp.route("/<goal_id>", methods=["GET"]) def get_one_goal(goal_id):
goal = validate_goal(goal_id)
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Update Goal #goal_bp.route("/<goal_id>", methods=["PUT"]) def update_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Complete #goal_bp.route("/<goal_id>/mark_complete", methods=["PATCH"]) def goal_complete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = datetime.utcnow()
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Incomplete #goal_bp.route("/<goal_id>/mark_incomplete", methods=["PATCH"]) def goal_incomplete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = None
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Delete Goal: Deleting a Goal #goal_bp.route("/<goal_id>", methods=["DELETE"]) def delete_goal(goal_id):
goal = validate_goal(goal_id)
db.session.delete(goal)
db.session.commit()
response = {"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}
return (jsonify(response), 200)
Validate there are no matching Goal: Get, Update, and Delete
def validate_goal(goal_id):
try:
goal_id = int(goal_id)
except:
abort(make_response({"message": f"Goal {goal_id} is invalid"}, 400))
goal = Goal.query.get(goal_id)
if not goal:
abort(make_response({"message": f"Goal {goal_id} not found"}, 404))
return goal
#goal_bp.route("/<goal_id>/tasks", methods=["POST"]) def
post_task_ids_to_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal_id = goal_id
task.goal = goal
db.session.commit()
return jsonify({"id":goal.goal_id, "task_ids": request_body["task_ids"]}), 200
#goal_bp.route("/<goal_id>/tasks", methods=["GET"]) def
get_tasks_for_goal(goal_id):
goal = validate_goal(goal_id)
task_list = [task.to_dictionary() for task in goal.tasks]
goal_dict = goal.to_dictionary()
goal_dict["tasks"] = task_list
return jsonify(goal_dict)
goal.py
from app import db
class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
tasks = db.relationship("Task", back_populates="goals", lazy = True)
def to_dictionary(self):
goal_dict = {
"id": self.goal_id,
"title": self.title
}
if self.tasks:
goal_dict["tasks"] = [task.task_id for task in self.tasks]
return goal_dict
I have made one POST API named as "/get_report" which takes some input from user and return data according to inputs by doing some search queries on database. If I keep hitting the same API multiple times like for 7-8 times, on 9th hit it throws error "sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00 (Background on this error at: https://sqlalche.me/e/14/3o7r)".
Here is my main.py:
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, jsonify, request
from helper import *
from models.tx import *
app = Flask(__name__)
db = SQLAlchemy()
DB_URL = 'postgresql://postgres:postgres#localhost/test_db'
engine = create_engine(DB_URL)
Session = sessionmaker(engine)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
#contextmanager
def session_manager():
s = Session()
try:
yield s
except:
s.rollback()
raise
finally:
s.close()
#app.route('/get_report', methods=['POST'])
def get_report():
try:
vendor = request.form['vendor']
circle = request.form['circle']
c_name = request.form['c_name']
c_port = request.form['c_port']
all_ssr = request.form['all_ssr']
data_list = []
table_name = get_table_by_vendor(vendor, circle)
if table_name != None:
with session_manager() as s:
if all_ssr == 'SSR':
result = s.query(table_name).distinct(table_name.label, table_name.c_node, \
table_name.c_port, table_name.z_port, table_name.z_node) \
.filter(and_((or_( and_(table_name.c_node == c_name, table_name.c_port == c_port), \
and_(table_name.z_node == c_name, table_name.z_port == c_port ))), \
(table_name.__table__.c[field_name].like('SSR%')))).all()
elif all_ssr == 'ALL':
# Get all the records
result = s.query(table_name).distinct(table_name.label, table_name.c_node, \
table_name.c_port, table_name.z_port, table_name.z_node) \
.filter(or_( and_(table_name.c_node ==c_name, table_name.c_port == c_port), \
and_(table_name.z_node == c_name, table_name.z_port == c_port ))).all()
else:
result = []
# Preparing JSON data to send
for item in result:
port = c_port if c_port != '' else (item.c_port if item.c_node == c_name else item.z_port)
data_dict = {'user': item.user, 'port': item.port, 'rate':item.rate, 'down': item.down}
data_list.append(data_dict)
response = {"status": "Success", "message": "Successfully Fetched", "data": data_list}
return jsonify(response)
if __name__ == '__main__':
app.run(host='0.0.0.0', port = 5000,debug = True)
Here is my models/tx.py:
class C_Segment(db.Model):
__tablename__ = 'c_segment'
id = db.Column(db.Integer, primary_key=True)
c_node = db.Column(db.String(350), nullable=False)
c_port = db.Column(db.String(200), nullable=False)
label = db.Column(db.String(350), nullable=False)
z_port = db.Column(db.String(200), nullable=False)
z_node = db.Column(db.String(200), nullable=False)
user = db.Column(db.String(200), nullable=False)
down = db.Column(db.String(200), nullable=False)
port = db.Column(db.String(200), nullable=False)
rate = db.Column(db.String(200), nullable=FaI
return '<id {}>'.format(self.id)
I have searched a lot and found so many related content on google but none of them worked for me. I have tried to increase pool size and overflow size also but nothing happened.
I am not able to understand where is the exact issue. I have been stuck into this from last two days and has gone through many stack overflow contents and flask sqlalchemy session documents.
You have a set of connections in the pool, e.g. 15 (5 in the pool and 10 from possible overflow). If a request processing is longer than connection checkout timeout (how long you will wait on available connection, default 30s) you will get this error (all 15 connections are busy and your request must wait for available connection - and waits but only for 30s, after that you get an error).
Have you thought about a query optimization? How many records do you have in the table? How long a request last? You can profile your SQL query using:
EXPLAIN ANALYZE <query>
You can of course increase the pool timeout by setting e.g 300s (5min):
app.config['SQLALCHEMY_POOL_TIMEOUT'] = 300
or make the pool size bigger but it would not solve your problem genuinely. Such a long response time in a request is really bad UX and limit concurrent access to you application from clients.
So I recommend you to make your query faster.
I have a db models like this:
class Payment(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
ticket_status = db.Column(db.Enum(TicketStatus, name='ticket_status', default=TicketStatus.UNUSED))
departure_time = db.Column(db.Date)
I want to change the value from all ticket_status after datetime.utcnow() passed the date value from departure_time.
I tried to code like this:
class TicketStatus(enum.Enum):
UNUSED = 'UNUSED'
USED = 'USED'
EXPIRED = 'EXPIRED'
def __repr__(self):
return str(self.value)
class Payment(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
ticket_status = db.Column(db.Enum(TicketStatus, name='ticket_status', default=TicketStatus.UNUSED))
departure_time = db.Column(db.Date)
# TODO | set ticket expirations time
def __init__(self):
if datetime.utcnow() > self.departure_time:
self.ticket_status = TicketStatus.EXPIRED.value
try:
db.session.add(self)
db.session.commit()
except Exception as e:
db.session.rollback()
I also tried like this:
def ticket_expiration(self, payment_id):
now = datetime.utcnow().strftime('%Y-%m-%d')
payment = Payment.query.filter_by(id=payment_id).first()
if payment.ticket_status.value == TicketStatus.USED.value:
pass
elif payment and str(payment.departure_time) < now:
payment.ticket_status = TicketStatus.EXPIRED.value
elif payment and str(payment.departure_time) >= now:
payment.ticket_status = TicketStatus.UNUSED.value
try:
db.session.commit()
except Exception as e:
db.session.rollback()
return str('ok')
But it seems no effect when the datetime.utcnow() passed the date value from departure_time.
So the point of my questions is, how to change the value from a row automatically after a set of times..?
Finally I figure out this by using flask_apscheduler, and here is the snippet of my code that solved this questions:
Install flask_apscheduler:
pip3 install flask_apscheduler
create new module tasks.py
from datetime import datetime
from flask_apscheduler import APScheduler
from app import db
from app.models import Payment, TicketStatus
scheduler = APScheduler()
def ticket_expiration():
utc_now = datetime.utcnow().strftime('%Y-%m-%d')
app = scheduler.app
with app.app_context():
payment = Payment.query.all()
for data in payment:
try:
if data.ticket_status.value == TicketStatus.USED.value:
pass
elif str(data.departure_time) < utc_now:
data.ticket_status = TicketStatus.EXPIRED.value
elif str(data.departure_time) >= utc_now:
data.ticket_status = TicketStatus.UNUSED.value
except Exception as e:
print(str(e))
try:
db.session.commit()
except Exception as e:
db.session.rollback()
return str('ok')
and then register the package with the flask app in the __init__.py
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
# The other packages...
# The other packages...
scheduler.init_app(app)
scheduler.start()
return app
# import from other_module...
# To avoid SQLAlchemy circular import, do the import at the bottom.
from app.tasks import scheduler
And here is for the config.py:
class Config(object):
# The others config...
# The others config...
# Flask-apscheduler
JOBS = [
{
'id': 'ticket_expiration',
'func': 'app.tasks:ticket_expiration',
'trigger': 'interval',
'hours': 1, # call the task function every 1 hours
'replace_existing': True
}
]
SCHEDULER_JOBSTORES = {
'default': SQLAlchemyJobStore(url='sqlite:///flask_context.db')
}
SCHEDULER_API_ENABLED = True
In the config above, we can call the function to update db every 1 hours, seconds or others time according to our case, for more informations to set the interval time we can see it here.
I hope this answer helps someone who facing this in the future.
You may replace your status column with just "used" column which will contain Boolean value and make a hybrid attribute for state. https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html
class Payment(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
used = db.Column(db.Boolean(), default=False)
departure_time = db.Column(db.Date)
#hybrid_property
def status(self):
if datetime.utcnow() > self.departure_time:
return "EXPIRED"
elif self.used:
return "USED"
return "UNUSED"
I have problem with pagination. If i try this with just a list of dict it works ok, problem starts when i try use model from my DB.I have about 10k users in my sql alchemy database. User is dbModel:
class UserModel(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
I want to show users from 1 to 100, from 100 to 200 etc. I'm trying to use this tutorial : https://aviaryan.com/blog/gsoc/paginated-apis-flask
Here is classmethod to return model into dict:
#classmethod
def return_all(cls):
def to_json(x):
return {
'id': x.id,
'username': x.username,
'password': x.password
}
return {'users': list(map(lambda x: to_json(x), UserModel.query.all()))}
Here is the pagination def:
def get_paginated_list(klass, url, start, limit):
start = int(start)
limit = int(limit)
# check if page exists
results = klass.return_all()
count = (len(results))
if count < start or limit < 0:
abort(404)
# make response
obj = {}
obj['start'] = start
obj['limit'] = limit
obj['count'] = count
# make URLs
# make previous url
if start == 1:
obj['previous'] = ''
else:
start_copy = max(1, start - limit)
limit_copy = start - 1
obj['previous'] = url + '?start=%d&limit=%d' % (start_copy, limit_copy)
# make next url
if start + limit > count:
obj['next'] = ''
else:
start_copy = start + limit
obj['next'] = url + '?start=%d&limit=%d' % (start_copy, limit)
# finally extract result according to bounds
obj['results'] = results[(start - 1):(start - 1 + limit)]
return obj
My api resource code is:
class AllUsers(Resource):
def get(self):
jsonify(get_paginated_list(
UserModel,
'users/page',
start=request.args.get('start', 1),
limit=request.args.get('limit', 100)
))
The problem i get is when i try get users from link http://127.0.0.1:5000/users/page?start=1&limit=100
TypeError: unhashable type: 'slice'
How to solve this thing? Or how can i show results as i want?
Probably bug is here:
obj['results'] = results[(start - 1):(start - 1 + limit)]
results is not a list, function return_all returns dict like {'users': []}
so try something like this:
obj['results'] = results['users'][(start - 1):(start - 1 + limit)]
I use sqlalchemy to perform some queries and updates, but now I have trouble with UPDATE operation , when I change the model attribute pet_time(ins.role_time = plus_days(ins.role_time,30)) and use session to commit , sqlalchemy didn't perform the update operation.I don't know what's going on ,can anybody help?
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer,primary_key=True)
nick_name = Column(String)
create_time = Column(DateTime, default=datetime.datetime.now)
role_time = Column(DateTime)
# the db connection info is correct
def getDbSession():
return Session()
def queryUserById(id):
sess = getDbSession()
instance = sess.query(User)\
.filter(User.id == id)\
.limit(1).first()
return instance
def increaseRoleTime(id,inc=1):
ins = queryUserById(id)
sess = getDbSession()
if(ins.role_time is None):
ins.role_time = datetime.datetime.now()
inc = inc * 30
# ins.role_time = func.ADDDATE(ins.role_time,inc)
ins.role_time = plus_days(ins.role_time,inc)
sess.commit()
return 1
# import timedelta from datetime to plus days
def plus_days(mydatetime,days):
return mydatetime + timedelta(days=days)
in the function increaseRoleTime,I change attribute role_time and commit,but still not working.