Validation in SQLAlchemy - python

How can I get the required validator in SQLAlchemy? Actually I just wanna be confident the user filled all required field in a form. I use PostgreSQL, but it doesn't make sense, since the tables created from Objects in my models.py file:
from sqlalchemy import (
Column,
Integer,
Text,
DateTime,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
scoped_session,
sessionmaker,
)
from zope.sqlalchemy import ZopeTransactionExtension
from pyramid.security import (
Allow,
Everyone,
)
Base = declarative_base()
class Article(Base):
""" The SQLAlchemy declarative model class for a Article object. """
__tablename__ = 'article'
id = Column(Integer, primary_key=True)
name = Column(Text, nullable=False, unique=True)
url = Column(Text, nullable=False, unique=True)
title = Column(Text)
preview = Column(Text)
content = Column(Text)
cat_id = Column(Integer, nullable=False)
views = Column(Integer)
popular = Column(Integer)
created = Column(DateTime)
def __unicode__(self):
return unicode(self.name)
So this nullable=False doesn't work, because the records added in any case with empty fields. I can of course set the restrictions at the database level by set name to NOT NULL for example. But there must be something about validation in SQLAlchemy isn't it? I came from yii php framework, there it's not the problem at all.

By empty fields I guess you mean an empty string rather than a NULL. A simple method is to add validation, e.g.:
class Article(Base):
...
name = Column(Text, unique=True)
...
#validates('name')
def validate_name(self, key, value):
assert value != ''
return value
To implement it at a database level you could also use a check constraint, as long as the database supports it:
class Article(Base):
...
name = Column(Text, CheckConstraint('name!=""')
...

Related

sqlalchemy relationship and classmethods and not only

I have a problem that I can't solve((( Sample code below...
from sqlalchemy import Column, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.types import Integer, String, Text
Base = declarative_base()
class Model(Base):
__abstract__ = True
#classmethod
def need_run(cls):
pass
class Profile(Model):
__tablename__ = 'profile'
id = Column('id', Integer, primary_key=True)
email = Column('email', String(length=128), unique=True, index=True)
password = Column('password', String(length=255), index=True)
messages = relationship('Message', lazy='select')
class Message(Model):
__tablename__ = 'message'
id = Column('id', Integer, primary_key=True)
profile_id = Column('profile_id', Integer, ForeignKey('profile.id', ondelete='CASCADE'))
title = Column('title', String(length=128), unique=True, index=True)
body = Column('text', Text, index=True)
Through the message attribute, i want to access the Message class to run the need_run method
Through the profile_id attribute, i want to access the Profile class to run the need_run method
Is it possible?
It should be possible but you probably want to change your relationship to include a backref:
class Profile(Model):
#...
messages = relationship('Message', lazy='select', backref="profile")
Then you can access messages from a profile like:
for msg in some_profile.messages:
msg.need_run()
And you can access a profile from a message like:
some_message.profile.need_run()

Flask SQLAlchemy: adding third column to joining table

Context: I'm making an auctioning website for which I am using Flask-SQLAlchemy. My tables will need to have a many-to-many relationship (as one artpiece can have many user bids and a user can bid on many artpieces)
My question is: it is possible to add another column to my joining table to contain the id of the user bidding, the id of artpiece that they are bidding on and also how much they bid? Also if yes, how would I include this bid in the table when I add a record to said table?
bid_table = db.Table("bid_table",
db.Column("user_id", db.Integer, db.ForeignKey("user.user_id")),
db.Column("item_id", db.Integer, db.ForeignKey("artpiece.item_id"))
)
class User(db.Model):
user_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
username = db.Column(db.Integer, unique=True, nullable=False)
email = db.Column(db.String(50), unique =True, nullable=False)
password = db.Column(db.String(60), nullable=False)
creation_date = db.Column(db.DateTime, default=str(datetime.datetime.now()))
bids = db.relationship("Artpiece", secondary=bid_table, backref=db.backref("bids", lazy="dynamic"))
class Artpiece(db.Model):
item_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
artist = db.Column(db.String(40), nullable=False)
buyer = db.Column(db.String(40), nullable=False)
end_date = db.Column(db.String(40))
highest_bid = db.Column(db.String(40))
It is possible to do this with SQL Alchemy, but it's very cumbersome in my opinion.
SQLAlchemy uses a concept called an Association Proxy to turn a normal table into an association table. This table can have whatever data fields you want on it, but you have to manually tell SQLAlchemy which columns are foreign keys to the other two tables in question.
This is a good example from the documentation.
In your case, the UserKeyword table is the association proxy table that you want to build for your user/bid scenario.
The special_key column is the arbitrary data you would store like the bid amount.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
Check out the full documentation for instructions on how to access and create this kind of model.
Having used this in a real project, it's not particularly fun and if you can avoid it, I would recommend it.
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html

issue creating sqlalchemy relationship/join with multiple foreign keys [duplicate]

I have this simple model of Author - Books and can't find a way to make firstName and lastName a composite key and use it in relation. Any ideas?
from sqlalchemy import create_engine, ForeignKey, Column, String, Integer
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine('mssql://user:pass#library')
engine.echo = True
session = sessionmaker(engine)()
class Author(Base):
__tablename__ = 'authors'
firstName = Column(String(20), primary_key=True)
lastName = Column(String(20), primary_key=True)
books = relationship('Book', backref='author')
class Book(Base):
__tablename__ = 'books'
title = Column(String(20), primary_key=True)
author_firstName = Column(String(20), ForeignKey('authors.firstName'))
author_lastName = Column(String(20), ForeignKey('authors.lastName'))
The problem is that you have defined each of the dependent columns as foreign keys separately, when that's not really what you intend, you of course want a composite foreign key. Sqlalchemy is responding to this by saying (in a not very clear way), that it cannot guess which foreign key to use (firstName or lastName).
The solution, declaring a composite foreign key, is a tad clunky in declarative, but still fairly obvious:
class Book(Base):
__tablename__ = 'books'
title = Column(String(20), primary_key=True)
author_firstName = Column(String(20))
author_lastName = Column(String(20))
__table_args__ = (ForeignKeyConstraint([author_firstName, author_lastName],
[Author.firstName, Author.lastName]),
{})
The important thing here is that the ForeignKey definitions are gone from the individual columns, and a ForeignKeyConstraint is added to a __table_args__ class variable. With this, the relationship defined on Author.books works just right.

Problems with Update and Table Inheritance with SQLAlchemy

I'm building an inheritance table schema like the following:
Spec Code
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
discriminator = Column('type', String(50))
updated = Column(DateTime, server_default=func.now(), onupdate=func.now())
name = Column(String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Engineer(Person):
__mapper_args__ = {'polymorphic_identity': 'engineer'}
start_date = Column(DateTime)
class Manager(Person):
__mapper_args__ = {'polymorphic_identity': 'manager'}
start_date = Column(DateTime)
UPDATED (WORKING) CODE
import os
import sys
from sqlalchemy import Column, create_engine, ForeignKey, Integer, String, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base
try:
os.remove('test.db')
except FileNotFoundError:
pass
engine = create_engine('sqlite:///test.db', echo=True)
Session = sessionmaker(engine)
Base = declarative_base()
class People(Base):
__tablename__ = 'people'
discriminator = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
id = Column(Integer, primary_key=True)
name = Column(String(50))
updated = Column(DateTime, server_default=func.now(), onupdate=func.now())
class Engineer(People):
__tablename__ = 'engineer'
__mapper_args__ = {'polymorphic_identity': 'engineer'}
id = Column(Integer, ForeignKey('people.id'), primary_key=True)
kind = Column(String(100), nullable=True)
Base.metadata.create_all(engine)
session = Session()
e = Engineer()
e.name = 'Mike'
session.add(e)
session.flush()
session.commit()
# works when updating the object
e.name = "Doug"
session.add(e)
session.commit()
# works using the base class for the query
count = session.query(People).filter(
People.name.is_('Doug')).update({People.name: 'James'})
# fails when using the derived class
count = session.query(Engineer).filter(
Engineer.name.is_('James')).update({Engineer.name: 'Mary'})
session.commit()
print("Count: {}".format(count))
Note: this is slightly modified example from sql docs
If I try to update the name for Engineer two things should happen.
update statement to the People table on column name
automatic trigger of update to the updated column on the People table
For now, i'd like to focus on number 1. Things like the example below (as also documented in the full code example) will result in invalid SQL
session.query(Engineer).filter(
Engineer.name.is_('James')).update({Engineer.name: 'Mary'})
I believe the above generates the following:
UPDATE engineer SET name=?, updated=CURRENT_TIMESTAMP FROM people WHERE people.name IS ?
Again, this is invalid. The statement is trying to update rows in incorrect table. name is in the base table.
I'm a little unclear about how inheritance tables should work but it seems like updates should work transparently with the derived object. Meaning, when I update Engineer.name querying against the Engineer object SQLAlchemy should know to update the People table. To complicate things a bit more, what happens if I try to update columns which exist in two tables
session.query(Engineer).filter(
Engineer.name.is_('James')).update({Engineer.name: 'Mary', Engineer.start_date: '1997-01-01'})
I suspect SQLAlchemy will not issue two update statements.

How to extend the `getter`-functionality of SQLAlchemy's association_proxy?

Edit: I would like to model a 1 to 0:1 relationship between User and Comment (a User can have zero or one Comment). Instead of accessing the object Comment I would rather directly access the comment itself. Using SQLAlchemys association_proxy works perfect for that scenario except for one thing: accessing User.comment before having a Comment associated. But in this case I would rather expect None instead of AttributeError as result.
Look at the following example:
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy import Column, Integer, Text, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __init__(self, name):
self.name = name
# proxy the 'comment' attribute from the 'comment_object' relationship
comment = association_proxy('comment_object', 'comment')
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
comment = Column('comment', Text, nullable=False, default="")
user_id = Column(ForeignKey('users.id'), nullable=False, unique=True)
# user_id has to be unique to ensure that a User can not have more than one comments
def __init__(self, comment):
self.comment = comment
user_object = orm.relationship(
"User",
uselist=False, # added after edditing the question
backref=orm.backref('comment_object', uselist=False)
)
if __name__ == "__main__":
engine = sa.create_engine('sqlite:///:memory:', echo=True)
Session = orm.sessionmaker(bind=engine)
Base.metadata.create_all(engine)
session = Session()
Now, the following code throws an AttributeError:
u = User(name="Max Mueller")
print u.comment
What would be the best way to catch that exception and provide a default value instead (like an empty string)?
You don't really need association_proxy for this. You could really get by just fine with a regular property. The AttributeError is (probably) caused because the comment_object is itself None, since there is no dependent row, and None has no comment attribute.
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __init__(self, name):
self.name = name
# proxy the 'comment' attribute from the 'comment_object' relationship
#property
def comment(self):
if self.comment_object is None:
return ""
else:
return self.comment_object.comment
#comment.setter
def comment(self, value):
if self.comment_object is None:
self.comment_object = Comment()
self.comment_object.comment = value
Try this
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy import Column, Integer, Text, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Text)
def __init__(self, name):
self.name = name
# proxy the 'comment' attribute from the 'comment_object' relationship
comment = association_proxy('comment_object', 'comment')
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
comment = Column('comment', Text, nullable=False, default="")
user_id = Column(ForeignKey('users.id'), nullable=False)
def __init__(self, comment):
self.comment = comment
user_object = orm.relationship(
"User",
backref=orm.backref('comment_object'),
uselist=False
)
if __name__ == "__main__":
engine = sa.create_engine('sqlite:///:memory:', echo=True)
Session = orm.sessionmaker(bind=engine)
Base.metadata.create_all(engine)
session = Session()
u = User(name="Max Mueller")
# comment = Comment("")
# comment.user_object = u
# session.add(u)
# session.commit()
print "SS :", u
print u.comment
You gave uselist in backref which must be in relationship.
I do not see any answer that would solve the issue and also would work with "sort_by" for example.
Maybe it is just better to use 'column_property", see Order by association proxy: invalid sql.

Categories

Resources