I'm using SQLAlchemy
import hashlib
import sqlalchemy as sa
from sqlalchemy import orm
from allsun.model import meta
t_user = sa.Table("users",meta.metadata,autoload=True)
class Duplicat(Exception):
pass
class LoginExistsException(Exception):
pass
class EmailExistsException(Exception):
pass
class User(object):
"""
def __setattr__(self, key, value):
if key=='password' :
value=unicode(hashlib.sha512(value).hexdigset())
object.__setattr__(self,key,value)
"""
def loginExists(self):
try:
meta.Session.query(User).filter(User.login==self.login).one()
except orm.exc.NoResultFound:
pass
else:
raise LoginExistsException()
def emailExists(self):
try:
meta.Session.query(User).filter(User.email==self.email).one()
except orm.exc.NoResultFound:
pass
else:
raise EmailExistsException()
def save(self):
meta.Session.begin()
meta.Session.save(self)
try:
meta.Session.commit()
except sa.exc.IntegrityError:
raise Duplicat()
How can I get the last inserted id when I call:
user = User()
user.login = request.params['login']
user.password = hashlib.sha512(request.params['password']).hexdigest()
user.email = request.params['email']
user.save()
You can access user.id (or whatever name you use for autoincremeted primary key field) after saving user object. SQLAlchemy automatically fills fields assigned by database.
Related
I create a flask application for my web server and one of the endpoint is to update contact information. One contact has many attributes such as username, email, first_name, last_name etc. I declare below method to handle update request on the contact:
def update_contact_with_form(contact_id, id=None, username=None, first_name=None, last_name=None, email=None, password=None, phone=None, user_status=None):
session = Session()
try:
contact = session.query(DBContact).filter(DBContact.id == contact_id).one()
if username != None:
contact.username = username
if first_name != None:
contact.first_name = first_name
...
except Exception as error:
print(error)
finally:
session.close()
return abort(400, 'failed to update')
The above code works fine but what I don't like is to check each value against None. Is there a better way to update the instance without checking each attribute?
How about this way?
def update_contact_with_form(contact_id, **kwargs):
session = Session()
try:
contact = session.query(DBContact).filter(DBContact.id == contact_id).one()
for k, v in kwargs:
if v is not None:
contact.__setattr__(k, v)
except Exception as error:
print(error)
finally:
session.close()
return abort(400, 'failed to update')
The scenario is the following:
I have a database where I store users and I want to keep the data (database) and the logic (bussiness) layers completely isolated.
I've created several specific exceptions to display the correct message in the interface (Flask) layer. Such as UserNotFound, UserEmailUsed, UserNameTaken
All the code is in a single file cruduser.py
Conection to the database:
def connect():
'''Connect to the database'''
return MySQLdb.connect(
"localhost",
"root",
"****",
"myflaskapp",
cursorclass=MySQLdb.cursors.DictCursor
)
Get users by email and/or username (Returns a User object):
def get_user_by_email(user):
'''Receive a user object, check whether email is used, if not raise
UserNotFound else creates a user object and returns'''
with connect() as cursor:
cursor.execute(
'''SELECT * FROM users WHERE email = %s''',
[user.email]
)
user = cursor.fetchone()
if user is None:
raise UserNotFound
return User(**user)
def get_user_by_username(user):
'''Receive a user object, check whether username is used, if not raise
UserNotFound else creates a user object and returns'''
with connect() as cursor:
cursor.execute(
'''SELECT * FROM users WHERE username = %s''',
[user.username]
)
user = cursor.fetchone()
if user is None:
raise UserNotFound
return User(**user)
Now to check duplicates I created the following code when a new user signs up:
def create_user(user):
'''Receive a user object, check if it is available and creates a new user
and inserts it into the database'''
available_user(user)
with connect() as cursor:
cursor.execute(
"""INSERT INTO users(email, username, password) VALUES(%s,%s,%s)""",
(user.email, user.username, user.password)
)
def available_user(user):
'''Receive a user object, check email and username, if email is used, raise
UserEmailUsed if username is used raise UserNameTaken else returns True'''
try:
get_user_by_email(user)
raise UserEmailUsed
except UserNotFound:
pass
try:
get_user_by_username(user)
raise UserNameTaken
except UserNotFound:
pass
return True
My questions are:
Is this approach pythonic?
Is there a better way to write the available_user function?
Is it appropriate to use exceptions in this way?
Note: This is a learning project so I would like to keep as pure as possible, no flask plugins at this point.
I am not too experienced with custom Exceptions but it looks like both of them are raised in any case, in contrast to your comment (if). So I'd put this condition into the code, and maybe catch UserNotFound in a common except.
try:
if get_user_by_email(user) :
raise UserEmailUsed
if get_user_by_username(user) :
raise UserNameTaken
except UserNotFound:
pass # handled elsewhere?
https://docs.python.org/3/tutorial/errors.html#handling-exceptions
After think and try several solutions I came to this:
First, DB logic and Business Logic should be separated so there is a dbconnect.py file with the following code:
def connect():
'''Connect to the database'''
return psycopg2.pool.SimpleConnectionPool(
1,
30,
host='ec2-23-23-223-2.compute-1.amazonaws.com',
user='wmowxsiematqzx',
password='d46dff42de4d8af492900f33d322caa13a0e05174b2310eec2e3549295438ab1',
dbname='dfciqalcgr98ah'
)
#contextmanager
def get_cursor():
a = connect()
con = a.getconn()
con.autocommit = True
try:
yield con.cursor()
finally:
a.putconn(con)
def execute_query_void(query):
with get_cursor() as cursor:
cursor.execute(*query)
def execute_query_one(query):
with get_cursor() as cursor:
cursor.execute(*query)
result = cursor.fetchone()
return result
def execute_query_many(query):
with get_cursor() as cursor:
cursor.execute(*query)
result = cursor.fetchall()
return result
All possible scenarios are covered there.
Now the cruduser.py
def get_user_by_email(email):
'''Receive a user object, look for a user with the given email and
returns a user object or None'''
query = ('''SELECT email, username, password_hash, rolename FROM users
INNER JOIN roles ON users.idroles=roles.idroles
WHERE email = %s''',
[email])
user = execute_query_one(query)
if user is None:
return None
return User(**user)
def get_user_by_username(user):
'''Receive a user object, look for a user with the given username and
returns a user object or None'''
query = ('''SELECT email, username, password_hash, rolename FROM users
INNER JOIN roles ON users.idroles=roles.idroles
WHERE username = %s''',
[user.username])
user = execute_query_one(query)
if user is None:
return None
return User(**user)
And the available functionality is achieved with two functions:
def available_user(user):
'''Receive a user object, check email and username, if email is used, raise
UserEmailUsed if username is used raise UserNameTaken else returns True'''
users = get_users(user)
if len(users) == 0:
return True
for user_ in users:
if user.email == user_.email:
raise UserEmailUsed
if user.username == user_.username:
raise UserNameTaken
def get_users(user):
'''Receive a article object, check whether articlename is used, if not raise
articleNotFound else creates a article object and returns'''
query = ('''SELECT email, username
FROM users
WHERE username=%s OR email=%s;''',
(user.username, user.email))
users = execute_query_many(query)
return [User(**user) for user in users]
The reason why I switch between using get_user_by_email and get_user_by_username to get_users is for performance, the latter just uses two columns while the former returns a whole object after performing a Join.
Additionally the exception where renamed to a more explicit name and where moved from DB Logic to Business Logic file
I am using python 2.7 and I have written a set of python classes in order to upload data to my database. However I am not sure I am completely understanding how to use inheritance from class to class. What I have is a User and Database class - which searches for the users/ databases from a list:
class User(object):
user_lst = ['user1', 'user2', 'user3']
def __init__(self, username):
self.username = username
def find_user(self):
if self.username not in self.user_lst:
print('User not found, script will exit')
exit()
else:
pass
class Database(object):
db_lst = ['db1', 'db2', 'db3']
def __init__(self, database):
self.database = database
def find_db(self):
if self.database not in self.user_lst:
print('Database not found, script will exit')
exit()
else:
pass
I get my values for user and database using raw_input() which returns:
un = 'user1'
db = 'db1'
To instantiate these classes as I understand it, I need to pass these values through the class, at which time I can also call the methods -
User(un).find_user()
Database(db).find_db()
I now want to use a third class to inherit these values in order to connect to the database:
class DatabaseConnect(User, Database):
def __init__(self):
User.__init__(self, username)
Database.__init__(self, database)
def connect(self, pw):
try:
connect = MySQLdb.connect(host = 'localhost', user = self.username, passwd = pw, db = self.database, local_infile = 1)
cursor = connect.cursor()
print('Connected to '+self.database)
except:
print('Did not connect to database')
exit()
I then try and connect using:
DatabaseConnect().connect('password1')
However this doesn't work. I have tried to add to my DatabaseConnect class init function:
def __init__(self, username, database):
User.__init__(self, username)
Database.__init__(self, database)
I have also played around with creating object variables from these classes such as:
user_obj = User(un)
user_obj.find_user()
db_obj = Database(db)
db_obj.find_user()
Do I need to create these object variables and then pass them through my DatabaseConnection class - if so do I even need to inherit? This is why I am confused. Say I use these object variables and use this class:
class DatabaseConnect(User, Database):
def __init__(self, username, database):
self.username = User.username
self.database = Database.database
def connect(self, pw):
try:
connect = MySQLdb.connect(host = 'localhost', user = self.username, passwd = pw, db = self.database, local_infile = 1)
cursor = connect.cursor()
print('Connected to '+self.database)
except:
print('Did not connect to database')
exit()
and then I instantiate it using:
db_connect = DatabaseConnect(user_obj, db_obj)
How is this any different from simply using the variables themselves:
db_connect = DatabaseConnect(un, db)
and why do i have to use:
self.username = User.username
instead of simply:
self.username = username
I am struggling to get my head around this concept so any head would be appreciated. Thanks
If you are inheriting you dont need to create a User and Database instance. You can just create a DatabaseConnect object:
class DatabaseConnect(User, Database):
def __init__(self, username, database):
User.__init__(self, username)
Database.__init__(self, database)
def connect(self, pw):
try:
connect = MySQLdb.connect(host = 'localhost', user = self.username, passwd = pw, db = self.database, local_infile = 1)
cursor = connect.cursor()
print('Connected to '+self.database)
except:
print('Did not connect to database')
exit()
dbConnect = new DatabaseConnect("username", "database")
dbConnect.find_db()
Database not found, script will exit
dbConnect.find_user()
User not found, script will exit
By doing:
def __init__(self, username, database):
self.username = User.username
self.database = Database.database
you are not properly initialising the User and Database instances. You need to call their __init__ function. There are several ways to do so e.g.:
def __init__(self, username, database):
User.__init__(self, username)
Database.__init__(self, database)
after doing so you can use self.username, etc
Using WTForms with Flask and SQLAlchemy. Taking data from a username and email field and making sure it's not already in the database. Here's what I have to do right now.
class IsUnique(object):
def __init__(self, db_field=None):
self.db_field = db_field
def __call__(self, form, field):
data = field.data
if self.db_field=='name':
if User.query.filter_by(name=data).first() != None:
raise ValidationError('Sorry, that username is taken.')
if self.db_field=='email':
if User.query.filter_by(email=data).first() != None:
raise ValidationError(
'Sorry, that email address has already been registered.'
)
What I would like to do is to pass the db_field argument to the class instance as a string and pass it into User.query.filter_by(db_field=data. Unfortunately all I know how to do is use an if statement, which works, but is kinda cumbersome. There's gotta be a way to do this right, but I don't know how.
You can pass keyword arguments as a dict, like this:
def __call__(self, form, field):
key = self.db_field # 'name' or 'email'
params = { key: field.data }
if User.query.filter_by(**params).first() != None:
raise ValidationError('Sorry, that {} is taken.'.format(key))
I have this class:
class User(base):
__tablename__='User'
name = Column(.......
def __init__(self, name):
self.name = name
#validates('name')
def validate_name(self, key, name):
if blah blah blah:
return name
else:
raise exception.....
create a new user and store him in database...
if __name__ == '__main__':
user = User('foo')
session.add(user)
session.commit() #validation works here
when updating the user:
if __name__ == '__main__':
user = session.query(User).filter_by(name=='foo').first()
user.name = 'bar'
session.add(user)
session.commit() #validation not working here
when storing a new user, the validation works
but when updating an existing user, validation not works
Q: how to validate a table column when updating its value using #validates?
Thanks :)
sqlalchemy.orm.validates works properly and it fires up on insert or update.
class Account(Base):
# ...rest
password = Column('password', String(50))
#validates('password')
def hash_password(self, key, val):
salt = bcrypt.gensalt()
encodedpw = val.encode()
hashedpw = bcrypt.hashpw(encodedpw, salt)
return hashedpw.decode()
Heres how I update the Account
acc db_session.get(Account, '<account_id>')
acc.password = '<new_passw>'
db_session.commit()