I'm building an app for rating beers at an event. The beers one can rate should be added to a table, as well should the event be added to another table and the beers and the event should be connected. Since at an event there is more than just one beer to be tasted and a beer can be tasted at multiple events, I want to make a m:n-relationship. I'm doing this with python3, I'm using flask and flasksqlalchemy. I'm using an sqlite-database.
The model I builded sofar looks like this:
#association table
event_beer = db.Table('event_beer',
db.Column('event_id', db.Integer, db.ForeignKey('event.id'), primary_key=True),
db.Column('beer_id', db.Integer, db.ForeignKey('beer.id'), primary_key=True))
class Event(db.Model):
__tablename__ = 'event'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
def __str__(self):
return f'{name}'
class Beer(db.Model):
__tablename__ = 'beer'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
event = db.relationship('Event', secondary=event_beer)
def __str__(self):
return f'{self.name}, {self.event}'
I omitted a few Columns which don't have any Foreign Keys or so for the sake of simplicity. The code which is executed when I want to save the recorded data is:
event = Event(name = 'event_name')
beer1 = Beer(name = 'beerone')
beer2 = Beer(name = 'beertwo')
beer1.event.append(event)
beer2.event.append(event)
db.session.commit()
The values to be saved aren't strings, but for the sake of simplicity I replaced them. The values are there though and in the database there aren't any empty rows.
I don't know whether I set up the model wrong or whether it's an issue while committing. Any help would be appreciated.
I'm deeply sorry, I just found the problem. Obviously I forgot to add the items to the session. All that missed were db.session.add(event). I was trying to figure this out for at least 6 hours now but I just found it after I posted the problem to stackoverflow.
Related
I'm trying to understand the nature and usage of .relationship and .ForeignKey in SQLalchemy.
In every example I see they seem to be tied to a variable that is not often not referenced anywhere making it a dead variable and the .relationship backref value is also often not referenced so the whole thing seems arbitrary and difficult to understand.
Here is a random example I pulled from online.
The best that I can see is that
excuses = db.relationship('Excuse', backref='student',
lazy='dynamic')
provides a one-to-many link between the Student model and the Excuse model with 'Excuse' being the 'many' and backref='student' being the 'one. However the excuses variable that is is connected to is not referenced in the foreign key or anywhere else so I don't know how it comes into play. I would be able to understand better with a visual diagram on how they interact but I haven't been able to find such a thing.
student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
Creates a variable with the student ID from the Student model but it seems to do so without the need for the .relationship statement in the Student model.
There is no reference here to the .relationship syntax in the Student model. It does provide a link from Excuse to Student but I don't understand the point of the
excuses = db.relationship('Excuse', backref='student',
lazy='dynamic')
clause as it doesn't seem to do anything.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
class Student(db.Model):
__tablename__ = "students" # table name will default to name of the model
# Create the three columns for our table
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Text)
last_name = db.Column(db.Text)
excuses = db.relationship('Excuse', backref='student',
lazy='dynamic')
# define what each instance or row in the DB will have (id is taken care of for you)
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
# this is not essential, but a valuable method to overwrite as this is what we will see when we print out an instance in a REPL.
def __repr__(self):
return f"The student's name is {self.first_name} {self.last_name}"
class Excuse(db.Model):
__tablename__ = "excuses"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
is_believable = db.Column(db.Boolean)
# remember - the name of our table is "students"
student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
def __init__(self, name, is_believable, student_id):
self.name = name
self.is_believable = is_believable
self.student_id = student_id
elie = Student('Elie', 'Schoppik')
matt = Student('Matt', 'Lane')
michael = Student('Michael', 'Hueter')
db.session.add_all([elie, matt, michael])
db.session.commit()
len(Student.query.all()) # 3
elie = Student.query.get(1)
excuse1 = Excuse('My homework ate my dog', False, 1)
db.session.add(excuse1)
db.session.commit()
elie.excuses.all() # list of excuses
elie.excuses.first().is_believable # False
Excuse.query.get(1).student # The student's name is Elie Schoppik
excuse2 = Excuse('I overslept', True, 1)
db.session.add(excuse2)
db.session.commit()
len(elie.excuses.all()) # 2
Does anyone mind explaining the dynamic between .relationship and .ForeignKey and how they interact? As well as the point of the variables they are attached to and how the backref variable is used?
I'm going to use SQLAlchemy ORM for my model of user in Python.
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user_id'))
Additionally I want to bind to each User data that shouldn't be saved in database, because it has meaning only during runtime. Such as link to some objects (asyncio.lock(), asyncio.Task()).
Ofcourse when I request data from database new object will be returned. So my question is what is best practice to relate such User(Base) objects with additional data that is existing while my app is working.
So I want something like this, but I wonder about proper pattern for that or more elegant solution. Here I need to save some key such as user_id, to relate some User extracted from DB to the instance of UserRuntimeData.
class UserRuntimeData():
def __init__(self, ...):
self.user_id = ...
self._lock = ...
self._current_task = ...
def compare(self, user: User) -> bool:
if user.user_id == self.user_id:
return True
return False
UPD. I have thought that I could also save runtime data by serializing in byte code and then just extract whole User info from DB, but I think it's kind of mess.
I have recently come to know about fastAPI and worked my way through the tutorial and other docs. Although fastAPI is pretty well documented, I couldn't find information about how to process a nested input when working with a database.
For testing, I wrote a very small family API with two models:
class Member(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True, server_default=text("nextval('members_id_seq'::regclass)"))
name = Column(String(128), nullable=False)
age = Column(Integer, nullable=True)
family_id = Column(Integer, ForeignKey('families.id', deferrable=True, initially='DEFERRED'), nullable=False, index=True)
family = relationship("Family", back_populates="members")
class Family(Base):
__tablename__ = 'families'
id = Column(Integer, primary_key=True, server_default=text("nextval('families_id_seq'::regclass)"))
family_name = Column(String(128), nullable=False)
members = relationship("Member", back_populates="family")
and I created a Postgres database with two tables and the relations described here. With schema definitions and a crud file as in the fastAPI tutorial, I can create individual families and members and view them in a nested fashion with a get request. Here is the nested schema:
class Family(FamilyBase):
id: int
members: List[Member]
class Config:
orm_mode = True
So far, so good. Now, I would like to add a post view which accepts the nested structure as input and populates the database accordingly. The documentation at https://fastapi.tiangolo.com/tutorial/body-nested-models/ shows how to do this in principle, but it misses the database (i.e. crud) part.
As the input will not have id fields and obviously doesn't need to specify family_id, I have a MemberStub schema and the NestedFamilyCreate schema as follows:
class MemberStub(BaseModel):
name: str
age: int
class NestedFamilyCreate(BaseModel):
family_name: str
members: List[MemberStub]
In my routing routine families.py I have:
#app.post('/nested-families/', response_model=schemas.Family)
def create_family(family: schemas.NestedFamilyCreate, db: Session = Depends(get_db)):
# no check for previous existence as names can be duplicates
return crud.create_nested_family(db=db, family=family)
(the response_model points to the nested view of a family with all members including all ids; see above).
What I cannot figure out is how to write the crud.create_nested_family routine. Based on the simple create as in the tutorial, this looks like:
def create_nested_family(db: Session, family: schemas.NestedFamilyCreate):
# split information in family and members
members = family.members
core_family = None # ??? This is where I get stuck
db_family = models.Family(**family.dict()) # This fails
db.add(db_family)
db.commit()
db.refresh(db_family)
return db_family
So, I can extract the members and can loop through them, but I would first need to create a new db_family record which must not contain the members. Then, with db.refresh, I would get the new family_id back, which I could add to each record of members. But how can I do this? If I understand what is required here, I would need to achieve some mapping of my nested schema onto a plain schema for FamilyCreate (which works by itself) and a plain schema for MemberCreate (which also works by itself). But how can I do this?
I found a solution after re-reading about Pydantic models and their mapping to dict.
in crud.py:
def create_nested_family(db: Session, family: schemas.NestedFamilyCreate):
# split information in family and members
family_data = family.dict()
member_data = family_data.pop('members', None) # ToDo: handle error if no members
db_family = models.Family(**family_data)
db.add(db_family)
db.commit()
db.refresh(db_family)
# get family_id
family_id = db_family.id
# add members
for m in member_data:
m['family_id'] = family_id
db_member = models.Member(**m)
db.add(db_member)
db.commit()
db.refresh(db_member)
return db_family
Hope, this may be useful to someone else.
I am trying to wrap my head around SQLAlchemy in combination with Marshmallow. I had a Flask API that contains some Assets and Trading Pairs. I want bidirectional One-to-Many relationships between these models. I have the following code:
class Asset(db.Model):
__tablename__ = 'asset'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), unique=True, nullable=False)
abbreviation = db.Column(db.String(20), unique=True, nullable=True)
trading_bases = relationship("TradingPair", back_populates="base_asset", foreign_keys="TradingPair.base_id")
trading_quotes = relationship("TradingPair", back_populates="quote_asset", foreign_keys="TradingPair.quote_id")
class TradingPair(db.Model):
__tablename__ = 'trading_pair'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), unique=True, nullable=False)
# One to many (a pair can have only one base, but 1 asset can be the base of many pairs)
base_id = db.Column(db.Integer, db.ForeignKey("asset.id"), nullable=False)
base_asset = relationship("Asset", foreign_keys=[base_id], uselist=False, back_populates="trading_bases")
# One to many (same reasoning)
quote_id = db.Column(db.Integer, db.ForeignKey("asset.id"), nullable=False)
quote_asset = relationship("Asset", foreign_keys=[quote_id], uselist=False, back_populates="trading_quotes")
With the following resource for trading pair POST:
def post(self, name):
pair = TradingPair.query.filter_by(name=name).first()
if pair:
return {"Error": "This resource already exists"}, 409
data = request.get_json()
schema = TradingPairSchema()
try:
pair = schema.load(data, session=db.session)
if not pair.name == name:
return {"Error": f"{name} does not correspond to name in data"}
db.session.add(pair)
db.session.commit()
return {"Success":f"Added: {pair.name}"}
except ValidationError as e:
return {"Error": e.messages}, 400
except:
return {"Error":"Database error"}, 500
I expect SQLAlchemy to add new Assets that are POSTed as part of a new trading pair. However, if I want to post new pairs via the API using the following JSON:
{'name': 'DASHUSDT',
'base_asset': {
'name': 'Dash',
'abbreviation': 'DASH'},
'quote_asset': {
'name': 'Tether',
'abbreviation':
'USDT'}}
This works properly and the pair gets added to the DB as expected. The problem occurs when I try to add another pair that contains Dash or Tether. The pair is added again to the DB and my uniqueness constraint on the Asset table is violated. How can I ensure that a new instance is not created but the existing asset is used?
I ended up with checking whether the assets exist and adding them to the database if they do not yet exist. The code I used in the POST function of the trading pair is:
loaded = schema.load(data, session=db.session)
if not loaded.name == name:
return {"Error": f"{name} does not correspond to name in data"}
base = Asset.query.filter_by(abbreviation=loaded.base_asset.abbreviation).first()
if not base:
base = Asset(name=loaded.base_asset.name , abbreviation=loaded.base_asset.abbreviation)
db.session.add(base)
quote = Asset.query.filter_by(abbreviation=loaded.quote_asset.abbreviation).first()
if not quote:
quote = Asset(name=loaded.quote_asset.name, abbreviation=loaded.quote_asset.abbreviation)
db.session.add(quote)
pair = TradingPair(name=name, base_asset=base, quote_asset=quote)
db.session.add(pair)
db.session.commit()
This seems to work properly when the Asset already exist, but also does not crash when a new Asset is inserted via the POST of a trading pair. I could not find any documentation in either SQLAlchemy, Flask-SQLAlchemy or Marshmallow-SQLAlchemy on how this should be handled properly but for now, this works.
I have a large model which I have defined in my models.py file and I have the form which serves it. Do I have to explicitly state each field for the form or is there a way to dynamically create this so I don't have repetition in my code?
forms.py
class CustomerForm(FlaskForm): # must be a better way
""" Declarative Form base class responsible for customers
variable names match customers/models.py and in the corresponding HTML files """
tpi_name = StringField('Enter Your Name')
tpi_ref = StringField('Enter Your Email')
company_name = StringField('Company Name (*)', validators=[InputRequired()])
company_type = StringField('Company Type')
company_reg = StringField('Company Registration Number')
and forms.py
class Post(db.Model): # Must be a better way
""" Creates the table Post and models the structure of the database table """
tpi_name = db.Column(db.String(100), nullable=False)
tpi_ref = db.Column(db.String(100), nullable=False)
company_name = db.Column(db.String(100), nullable=False)
company_type = db.Column(db.String(100), nullable=False)
company_reg = db.Column(db.Integer, nullable=False)
So there is a lot of repetition here, is there a better way?
I've not personally tried this, but came across this: http://flask.pocoo.org/snippets/60/
Sounds like that's discussing what you're trying to achieve if I understand your question correctly.
Maybe you like using flask-admin library: https://flask-admin.readthedocs.io/en/latest/
How does it work? The basic concept behind Flask-Admin, is that it lets you build
complicated interfaces by grouping individual views together in classes: Each web page
you see on the frontend, represents a method on a class that has explicitly been added
to the interface.
These view classes are especially helpful when they are tied to particular database
models, because they let you group together all of the usual Create, Read, Update,
Delete (CRUD) view logic into a single, self-contained class for each of your models.
Example:
from flask_admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
admin = Admin(app, name='microblog', template_mode='bootstrap3')
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))