I have three tables
import enum
from sqlalchemy import Column, Enum, Float, String
from sqlalchemy.orm import relationship
#enum.unique
class TypeEnum(str, enum.Enum):
person = "person"
location = "location"
class Person(Base):
name = Column(String)
id = Column(Integer, primary_key=True)
class Location(Base):
id = Column(Integer, primary_key=True)
city = Column(String)
lat = Column(Float)
lon = Column(Float)
class ExternalIdentifier(Base):
name = Column(String)
id = Column(Integer, primary_key=True)
value = Column(String)
type = Column(Enum(TypeEnum))
# internal_id = relationship(...) # Problem here
I would like to have a mapping between ExternalIdentifier and Person/Location:
If ExternalIdentifier.type is person, then ExternalIdentifier.internal_id should refer to an object from table Person
Analogously if the type is location
Unfortunately, I have only been able to make it work by either:
creating fields ExternalIdentifier.person_id/location_id instead of ExternalIdentifier.internal_id/type
or creating tables person/location_to_external_identifier
Related
I am trying to build a database with sqlalchemy.
I have two tables : flow and krbr.
from __future__ import print_function
import numpy as np
import pandas as pd
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
import sqlalchemy_utils
from sqlalchemy_utils.types.ip_address import IPAddressType
Base = declarative_base()
## Define the tables schema
class Flow(Base):
__tablename__ = 'flow'
Id = Column(Integer, primary_key=True)
First = Column(Integer, index=True)
Protocol = Column(String(10))
Src = Column(IPAddressType, index=True)
SrcPort = Column(Integer)
Dst = Column(IPAddressType, index=True)
DstPort = Column(Integer)
GroupId = Column(Integer)
Port = Column(String(10))
VPort = Column(Integer)
IpTos = Column(String)
VlanId = Column(String)
VlanPri = Column(String)
Application = Column(String(100))
Packets = Column(Integer)
Messages = Column(Integer)
Bytes = Column(Integer)
Last = Column(Integer)
#LearnedIPs alertable
#LearnedIPs learned-ip
# u'LearnedIPs new-ips', u'LearnedIPs subnet-name',
# u'LearnedIPs timestamp-sec', u'LearnedIPs total-ips', u'SrcSubnet',
# u'DstSubnet'],
# 'MPLS Exp'
class Krbr(Base):
__tablename__ = 'krbr'
Id = Column(Integer, primary_key=True)
Src = Column(IPAddressType, index=True)
SrcPort = Column(Integer)
Dst = Column(IPAddressType, index=True)
DstPort = Column(Integer)
TimeNs = Column(Integer)
To some of the rows in flow is associated one or more rows of krbr.
A row in krbr is associated with a row in flow if:
1) they have the same values of Src, Dst, SrcPort, DstPort
2) They are close in time. i.e. np.abs(Flow.first - Krbr.TimeNs/1000000000) < threshold
I am wondering what is the right approach to create a link between the two tables. i.e. given a row of one table I want to be able to get the rows of the other table.
I do not know much of sqlalchemy. I guess that I should define a foreign key but I do not know how to enforce such a complex relationship.
Here is a sample of 'Handling Multiple Join Paths':
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")
shipping_address = relationship("Address")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
More info about 'Handling Multiple Join Paths' in you can find at
SQLAlchemy 0.9 Documentation
I am trying to implement very simple example table from an old course now in SQLAlchemy...
I have got this far but when I run the code...
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, MetaData
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
Base = declarative_base()
engine = create_engine('mysql://x # amazonaws.com:3306/db', echo=True)
class Guest(Base):
__tablename__ = "guests"
guest_no = Column(String(4), primary_key=True)
g_name = Column(String(20))
g_address = Column(String(30))
booking = relationship("Booking", back_populates="guests")
class Hotel(Base):
__tablename__ = "hotels"
hotel_no = Column(String(4), primary_key=True)
h_name = Column(String(20))
h_address = Column(String(30))
room = relationship("Room", back_populates="hotels")
booking = relationship("Booking", back_populates="hotels")
class Room(Base):
__tablename__ = "rooms"
hotel_no = Column(String(4), ForeignKey('hotels.hotel_no'), primary_key=True)
room_no = Column(String(4), primary_key=True)
r_type = Column(String(1))
r_price = Column(Integer)
hotel = relationship("Hotel", back_populates="rooms")
booking = relationship("Booking", back_populates="rooms")
class Booking(Base):
__tablename__ = "bookings"
hotel_no = Column(String(4), ForeignKey('hotels.hotel_no'), primary_key=True)
guest_no = Column(String(4), ForeignKey('guests.guest_no'), primary_key=True)
date_form = Column(Date, primary_key=True)
date_to = Column(Date)
room_no = Column(String(4), ForeignKey('rooms.room_no'), primary_key=True)
hotel = relationship("Hotel", back_populates="bookings")
guest = relationship("Guest", back_populates="bookings")
room = relationship("Room", back_populates="bookings")
Base.metadata.create_all(engine)
it gives me an error about the room_no foreign key...
2017-09-11 16:16:03 2b8010c29700 Error in foreign key constraint of table db/bookings:
FOREIGN KEY(room_no) REFERENCES rooms (room_no)
):
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
I looked around a bit and I made sure they were both the same type (they were) and were both primary keys (they previously were not) but the error persists.
Does anyone have insight into what is causing this?
Because rooms has a composite primary key: (hotel_no, room_no) you'll need to specify both columns in your foreign key relationship on the booking table:
__table_args__ = (
ForeignKeyConstraint(
['hotel_no', 'room_no'],
['rooms.hotel_no', 'rooms.room_no']
),
)
I have a problem with SQL Alchemy, while trying to think about an SQL schema I encountered the following problem.
My schema is based on 2 classes, Flight and Trip.
A Trip includes 2 fields: flights_to and flights_from.
Any of the fields is basically a list of flights, it could be made of one flight, or many flights (Connection flights).
class Trip(Base):
__tablename__ = "Trip"
__table_args__ = {'sqlite_autoincrement': True}
id = Column(Integer, primary_key = True)
flights_to = relationship("Flight", backref="Trip")
flights_from = relationship("Flight", backref="Trip")
class Flight(Base):
__tablename__ = "Flight"
__table_args__ = {'sqlite_autoincrement': True}
id = Column(Integer, primary_key = True)
arrival_airport = Column(String(20))
departure_airport = Column(String(20))
flight_number = Column(Integer)
trip_id = Column(Integer, ForeignKey('Trip.id'))
The problem happens when I create 2 fields in the same type:
sqlalchemy.exc.ArgumentError: Error creating backref 'Trip' on relationship 'Trip.flights_from': property of that name exists on mapper 'Mapper|Flight|Flight'
I have thought about using 2 inheriting classes of types FlightTo and FlightFrom and saving them at two different tables, but what if I want to use a FlightFrom as a FlightTo? will the flight be duplicated in 2 tables?
I would appreciate your help.
backref is used to define a new property on the other class you are using relationship with. So you can't have two property which have the same name
You should rename your backref for the flights_from to any other name than Trip.
It will work then.
For Example:
class Person(Model):
id = Column(Integer, primary_key=True)
name = Column(String)
address = relationship("Address",backref="address")
class Address(Model):
id = Column(Integer, primary_key=True)
house_no = Column(Integer)
person_id = Column(Integer, ForeignKey('person.id'))
So you can access the person name with house_no 100 by:
query_address = Address.query.filter_by(house_no=100).first()
person = query_address.address
This returns you the person object.
Thus if you have multiple such names , it will give you an error
My many-to-many model looks like this, with an association table:
class Puppy(Base):
__tablename__ = "puppy"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(80))
class Adopter(Base):
__tablename__ = "adopter"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String)
puppies = relationship('Puppy', secondary='puppy_adopters', backref="puppy")
puppy_adopters = Table('puppy_adopters', Base.metadata,
Column('puppy_id', Integer, ForeignKey('puppy.id')),
Column('adopter_id', Integer, ForeignKey('adopter.id')))
If I have created an Adopter named Bill, I can easily add and retrieve his puppies with Python:
bill.puppies.append(fido)
bill.puppies.append(rex)
for puppy in bill.puppies:
print puppy.name # Fido, Rex
The puppy_adopters association table is populated with Bill's id and Fido's id when I do this. But how do I see that Bill is related to Fido, using Python? I get a Python object for Fido using fido = session.query(Puppy).filter_by(name="Fido"), but there is no fido.adopters list that contains Bill. How can I see all the people that have adopted Fido?
I tried this line in the puppy table:
adopter_id = Column(Integer, ForeignKey('adopter.id'))
but it did not get populated when I added an adopter, and it couldn't hold multiple adopters anyway.
The SQLAlchemy docs provide what you need under their Many to Many section. What you are looking for is back_populates.
class Puppy(Base):
__tablename__ = "puppy"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(80))
adopters = relationship('Adopter',
secondary="puppy_adopters",
back_populates="puppies")
class Adopter(Base):
__tablename__ = "adopter"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String)
puppies = relationship('Puppy',
secondary="puppy_adopters",
back_populates="adopters")
puppy_adopters = Table('puppy_adopters', Base.metadata,
Column('puppy_id', Integer, ForeignKey('puppy.id')),
Column('adopter_id', Integer, ForeignKey('adopter.id')))
Rex = Puppy(name='Rex')
Fido = Puppy(name='Fido')
Bob = Adopter(name='Bob')
Steve = Adopter(name='Steve')
Steve.puppies.append(Fido)
Bob.puppies.append(Fido)
Bob.puppies.append(Rex)
print [adopter.name for adopter in Fido.adopters] # ['Steve', 'Bob']
print [puppy.name for puppy in Bob.puppies] # ['Fido', 'Rex']
I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')