I have a table of games and I want to save the moves made in each game within this table. The only way I can think of doing this(without using pickles) is to have another table for each game to save the moves set. My problem is that I'm not sure how to do that within Sqlalchemy and I could not find an answer elsewhere. Am I forced to use pickle types or is there a better way I'm not seeing?
Using Python 3.8.5 and the latest version of Sqlalchemy.
import datetime
from sqlalchemy import Column, DateTime, Integer, String, Date, ForeignKey, Float, Boolean, DateTime, PickleType
from sqlalchemy.ext.declarative import declarative_base
from flask_login import UserMixin
Base = declarative_base()
class Move(Base):
__tablename__ = 'moves'
id = Column(Integer, primary_key = True)
move = Column(String)
start_time = Column(Integer)
end_time = Column(Integer)
white = Column(Boolean)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key = True)
white_id = Column(Integer)
black_id = Column(Integer)
board = Column(PickleType)
move_table_name = Column(String)
class User(Base , UserMixin):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(15), unique=True)
email = Column(String(50), unique=True)
password = Column(String(80))
This(^^^) is my database mode.
This is a question more about the basic database design. You could think how to design this kind of requirements with just database without sqlalchemy? Relationships between tables, here Game->Move, is a one-to-many relationship which should be declared in the database table also. See https://database.guide/the-3-types-of-relationships-in-database-design.
With SqlAlchemy relationships can be designed as follows:
class Move(Base):
__tablename__ = 'moves'
id = Column(Integer, primary_key = True)
game_id = Column(Integer, ForeignKey('games.id'))
move = Column(String)
start_time = Column(Integer)
end_time = Column(Integer)
white = Column(Boolean)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key = True)
white_id = Column(Integer, ForeignKey('users.id'))
black_id = Column(Integer, ForeignKey('users.id'))
board = Column(PickleType)
white = relationship("User", foreign_keys=[white_id])
black = relationship("User", foreign_keys=[black_id])
See more info: https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html
Related
i cant get a simple query example of how to get record data based on the relationship,
models.py
from sqlalchemy import ForeignKey, Column, Integer, String, Boolean
from sqlalchemy.orm import relationship, declarative_base
Base = declarative_base()
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
wp_id = Column(Integer, nullable=False)
name = Column(String)
child_categories = relationship("Keyword", back_populates="parent_category", cascade="all, delete-orphan")
def __repr__(self):
return f"<wp id: {self.wp_id}, category name: {self.name}>"
class Keyword(Base):
__tablename__ = "keyword"
id = Column(Integer, primary_key=True)
wp_id = Column(Integer, unique=True)
name = Column(String)
questions = relationship("Question", back_populates="keyword", cascade="all, delete-orphan")
is_processed = Column(Boolean, nullable=True)
is_posted = Column(Boolean, nullable=True)
category_id = Column(String, ForeignKey('category.id'), nullable=True)
parent_category = relationship("Category", back_populates="child_categories")
def __repr__(self):
return f"<id: {self.wp_id}, keyword name: {self.name}>"
class Question(Base):
__tablename__ = "question"
id = Column(Integer, primary_key=True)
question = Column(String)
answer = Column(String)
keyword_id = Column(Integer, ForeignKey('keyword.id'), nullable=False)
keyword = relationship("Keyword", back_populates='questions')
def __repr__(self):
return f"<question: {self.question}>"
and i want all the keywords that are child of category with wp_id == 23
alot of the material in internet are either old, or are solutions for flask sqlalchemy, and there seems many ways to do one thing, thats why im a bit confused,
i tried the below:
wp_category_id = 23
def get_category_keywords_details(wp_category_id):
global engine
with Session(engine) as session:
keyword_details = session.execute(select(Keyword).where(Keyword.parent_category.wp_id ==
wp_category_id)).all()
which gave error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Keyword.parent_category has an attribute 'wp_id'
SQLAlchemy script works fine with sqlite3 but when connected to MYSQL database it gives different errors like:
AttributeError: '_NoResultMetaData' object has no attribute '_indexes_for_keys'
sqlalchemy.exc.NoSuchColumnError: Could not locate column in row for column 'filters.id'
KeyError: Column('id', Integer(), table=, primary_key=True, nullable=False)
The functions works fine when running separately but when used with flask they raise above errors.
My DB models:
from sqlalchemy import Column, Integer, String, MetaData, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
import datetime
Base = declarative_base()
class ProfileData(Base):
__tablename__ = 'profile_data'
id = Column(Integer, primary_key=True)
user_id = Column("user_id", Integer)
user_profile_link = Column(String(100))
username = Column(String(100))
name = Column(String(100))
is_verified = Column(Boolean)
website = Column(String(100))
bio = Column(String(1000))
location = Column(String(100))
created_at = Column(DateTime)
followers = Column(Integer)
following = Column(Integer)
_datetime = Column(DateTime, default=datetime.datetime.utcnow)
Thanks in advance.
I would suggest to do this and this how usually do my setup
first init db object from SQLAlchemy
db = SQLAlchemy()
then declare the model using db.model
class ProfileData(db.Model):
__tablename__ = 'profile_data'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column("user_id", db.Integer)
user_profile_link = db.Column(db.String(100))
username = db.Column(db.String(100))
hope this fix your issue
I'm using SQLAlchemy with Postgres.
Code example from https://auth0.com/blog/sqlalchemy-orm-tutorial-for-python-developers/
I have 3 objects:
Actor
Stuntman
ContactDetails
In the original code example, the relationships are as follow:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
See Code:
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor")
I'm struggling to extend this model to following relationships:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
Stuntman:ContactDetails = 1:0..*
Stuntmen work hard too and deserve ContactDetails. Who can help please?!
In my opinion best way to achieve what you need is using association table. This one should work:
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
contact_details = relationship("ContactDetails", secondary=actors_to_contact_details)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
contact_details = relationship("ContactDetails", backref="stuntman", secondary=stuntmen_to_contact_details)
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
if you want, you can put unique=True in association table like this
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
Other option would be to put another nullable foreign key on Stuntman table, but I would go with solution presented above.
I would like to create an association between a Dataset object and all Category objects through the Annotation table.
A Dataset contains a collection of Annotations. Each Annotation has a single Category. I want Dataset.categories to contain the unique set of Categories made up of all the Categories of all the Annotations in that Dataset instance. I have tried doing this with a double association table (dataset_categories), but it is not working. What is the right way to do this? Here is my code so far:
Base = declarative_base()
dataset_categories = Table('dataset_categories', Base.metadata,
Column('dataset_id', Integer, ForeignKey('datasets.id')),
Column('annotation_id', Integer, ForeignKey('annotations.id')),
Column('category_id', Integer, ForeignKey('categories.id')))
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary=dataset_categories)
class Annotation(Base):
__tablename__ = 'annotations'
id = Column(Integer, primary_key=True)
category_id = Column(Integer, ForeignKey('categories.id'), nullable=False)
category = relationship("Category")
dataset_id = Column(Integer, ForeignKey('datasets.id'))
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
dataset = relationship("Dataset", secondary=dataset_categories)
dataset_id = Column(Integer, ForeignKey('datasets.id'),
back_populates='categories')
Without the requirement that the association contain only the unique categories this would be as simple as using an association_proxy. One option is to define the collection class to use as set when defining the relationship:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary="annotations", collection_class=set)
On the other hand the secondary table of a relationship does not have to be a base table, and so a simple select from annotations can be used:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category",
secondary="""select([annotations.c.dataset_id,
annotations.c.category_id]).\\
distinct().\\
alias()""",
viewonly=True)
The example given in sqlalchemy documentation is,
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address", foreign_keys=[billing_address_id])
shipping_address = relationship("Address", foreign_keys=[shipping_address_id])
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
I am trying a similar example (cannot place so much code here) it does not work if I do something similar to this:
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address", foreign_keys=[billing_address_id], back_populates('bill_addr'))
shipping_address = relationship("Address", foreign_keys=[shipping_address_id], back_populates('ship_addr'))
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
bill_addr = relationship("Customer", back_populates('billing_address'))
ship_addr = relationship("Customer", back_populates('shipping_address'))
I have two doubts:
Q1) Is the above relationship bidirectional?
Q2) How to establish a bidirectional relationship between tables with multiple join paths?
edit:
In my case I am getting the following error:
sqlalchemy.exc.AmbiguousForeignKeysError
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables
on relationship User.expenses - there are multiple foreign key paths linking the tables.
Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a
foreign key reference to the parent table.
It is working if I used 'backref' instead of 'back_populates'. I placed the 'backref' in the relationships on the side where both the foreign keys are present and removed the relationships on the other table.