Say I have a SqlAlchemy model something like this:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
Session = sessionmaker()
class EmployeeType(Base):
__tablename__ = 'employee_type'
id = Column(Integer(), primary_key=True)
name = Column(String(20))
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer(), primary_key=True)
type_id = Column(Integer(), ForeignKey(EmployeeType.id))
type = relationship(EmployeeType, uselist=False)
session = Session()
session.add(EmployeeType(name='drone'))
session.add(EmployeeType(name='PHB'))
I'd like to have some kind of "relationship" from Employee directly to EmployeeType.name as a convenience, so I can skip the step of looking up an id or EmployeeType object if I have a type name:
emp = Employee()
emp.type_name = "drone"
session.add(emp)
session.commit()
assert (emp.type.id == 1)
Is such a thing possible?
EDIT: I found that association_proxy can get me partway there:
class Employee(Base):
...
type_name = association_proxy("type", "name")
the only problem being that if I assign to it:
emp = session.query(Employee).filter_by(EmployeeType.name=='PHB').first()
emp.type_name = 'drone'
it modifies the employee_type.name column, not the employee.type_id column.
I agree with Jonathan's general approach, but I feel like adding an employee object to the session and setting the employee type should be independent operations. Here's an implementation that has type_name as a property and requires adding to the session before setting it:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
Session = sessionmaker()
class EmployeeType(Base):
__tablename__ = 'employee_type'
id = Column(Integer(), primary_key=True)
name = Column(String(20))
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer(), primary_key=True)
type_id = Column(Integer(), ForeignKey(EmployeeType.id))
type = relationship(EmployeeType)
#property
def type_name(self):
if self.type is not None:
return self.type.name
return None
#type_name.setter
def type_name(self, value):
if value is None:
self.type = None
else:
session = Session.object_session(self)
if session is None:
raise Exception("Can't set Employee type by name until added to session")
self.type = session.query(EmployeeType).filter_by(name=value).one()
I would do this by creating a method that does this for me.
class EmployeeType(Base):
__tablename__ = 'employee_type'
id = Column(Integer(), primary_key=True)
name = Column(String(20))
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer(), primary_key=True)
type_id = Column(Integer(), ForeignKey(EmployeeType.id))
type = relationship(EmployeeType, uselist=False)
def __init__(self, type):
self.type = type
def add(self, type_name=None):
if type_name is not None:
emp_type = DBSession.query(EmployeeType).filter(EmployeeType.name == type_name).first()
if emp_type:
type = emp_type
else:
type = EmployeeType(name=type_name)
else:
type = None
DBSession.add(Employee(type=type))
Then you do:
Employee.add(type_name='boss')
Related
I am trying to use ORM with SQLAlchemy in Python. My current solution fails and throws an exception right in the moment the ORM is first used. I receive the following exception:
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Event->event, expression 'Tag' failed to locate a name ('Tag'). If this is a class name, consider adding this relationship() to the <class 'backend.source.database.event.Event'> class after both dependent classes have been defined.
My classes are defined like in the offical SQLAlchemy-Documentation (https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many), which is why I am kinda confused about that error.
association = Table('event_to_tag', declarative_base().metadata,
Column('event_id', Integer, ForeignKey('event.id'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True))
class Event(declarative_base()):
__tablename__ = "event"
id = Column(Integer, primary_key=True)
title = Column(String(255))
location = Column(String(255))
organizer_id = Column(Integer, ForeignKey(Organizer.id))
start = Column(DateTime)
end = Column(DateTime)
lang = Column(String(255))
costs = Column(DECIMAL)
registration = Column(TINYINT)
url = Column(String(255))
description = Column(Text)
tags = relationship("Tag", secondary=association, back_populates="events")
class Tag(declarative_base()):
__tablename__ = "tag"
id = Column(Integer, primary_key=True)
name = Column(String(255))
events = relationship("Event", secondary=association, back_populates="tags")
Thank you, greetings
I think you need to define a Base = declarative_base(), need to use in your models and associations.
from sqlalchemy import Column, Integer, ForeignKey, String, DECIMAL, Text, DateTime, Table, create_engine
from sqlalchemy.dialects.mssql import TINYINT
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import declarative_base, relationship, scoped_session, sessionmaker
Base = declarative_base()
association = Table('event_to_tag',
Base.metadata,
Column('event_id', Integer, ForeignKey('events.id'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True))
class Event(Base):
__tablename__ = "events"
id = Column(Integer, primary_key=True)
title = Column(String(255))
location = Column(String(255))
# organizer_id = Column(Integer, ForeignKey(Organizer.id))
start = Column(DateTime)
end = Column(DateTime)
lang = Column(String(255))
costs = Column(DECIMAL)
registration = Column(UUID)
url = Column(String(255))
description = Column(Text)
tags = relationship("Tag", secondary=association, back_populates="events")
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String(255))
events = relationship("Event", secondary=association, back_populates="tags")
class CreateEngine:
def __init__(self):
self.connection_string = "postgresql+psycopg2://<user_name>:<password>#127.0.0.1/<table_name>"
self.engine = create_engine(self.connection_string)
def create_table(self):
return Base.metadata.create_all(self.engine)
def create_session(self):
session_factory = sessionmaker(bind=self.engine)
Session = scoped_session(session_factory)
with Session() as session:
pass
if __name__ == "__main__":
CreateEngine().create_table()
On this question I learned how to set the schema on an ORM definition:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Notification(Base):
__tablename__ = "dog"
__table_args__ = {"schema": "animal"}
id = Column(Integer, primary_key=True)
name = Column(String)
But I now need to make the schema configurable. I have tried passing the table_args parameter at object creation, but it's still trying the schema I put on the class definition.
The better solution I have found so far is to create a function that returns the class:
function get_notification_class(schema: str):
class Notification(Base):
__tablename__ = "dog"
__table_args__ = {"schema": schema}
id = Column(Integer, primary_key=True)
name = Column(String)
return Notification
And then, to use it
Notification = get_notification_class('animal')
obj = Notification('1', 'doggy')
Please could someone help me with this error? I have actually been really struggling to find solid, simple examples for SQLAlchemy. Whilst there are plenty of Model examples of there is not much examples of how to use these Models.
The Error:
sqlalchemy.exc.NoForeignKeysError:
Could not determine join condition between parent/child tables on relationship Species.sc_genus
- there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
The Code
from sqlalchemy import Integer, Column, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
Base = declarative_base()
class Genus(Base):
__tablename__ = 'genus'
id = Column(Integer, primary_key=True)
common_name = Column(String)
scientific_name = Column(String)
sc_sub_family = "sc_sub_family"
def __repr__(self):
return "<Genus(common_name='%s')>" % (self.scientific_name)
# Species is a child of Genus
class Species(Base):
__tablename__ = 'species'
id = Column(Integer, primary_key=True)
common_name = Column(String)
scientific_name = Column(String)
sc_genus = relation("Genus", backref="species")
def __repr__(self):
return "<Species(common_name='%s')>" % (self.scientific_name)
def addSpecies(session):
species = Species()
species.common_name = "House Cat"
species.scientific_name = "Felis catus"
genus = Genus()
genus.scientific_name = "Felis"
session.add(genus)
species.sc_genus = genus
session.add(species)
session.commit()
if __name__ == "__main__":
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
## A bunch of stuff to make the connection to the database work.
engine = create_engine('sqlite:///foos.db', echo=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
addSpecies(session)
I needed to specify a foreign key for the relationship.
class Genus(Base):
__tablename__ = 'genus'
id = Column(Integer, primary_key=True)
scientific_name = Column(String)
# sc_sub_family = "sc_sub_family"
def __repr__(self):
return "<Genus(common_name='%s')>" % (self.scientific_name)
# Species is a child of Genus
class Species(Base):
__tablename__ = 'species'
id = Column(Integer, primary_key=True)
common_name = Column(String)
scientific_name = Column(String)
sc_genus = relation("Genus", backref="species")
sc_genus_id = Column(Integer, ForeignKey('genus.id'))
def __repr__(self):
return "<Species(common_name='%s')>" % (self.scientific_name)
I have a class Parent and two of its subclass Child and ChildOne.
I am able to add data to each of the table and read the data.
Now I want to update Child class's row such that it becomes a row in ChildOne table that is when I update type to "old" from "young", I want the row to get deleted from child_one table and added to child table, but without losing the "id" value (which is the primary key).
Is there any way that SQLAlchemy itself handle this? or any other idea to achieve this?
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy import Column, Integer, String, case, Boolean
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base, declared_attr
engine = create_engine('sqlite:///testing.db', echo=True)
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
type = Column(String)
def __init__(self, name):
self.name = name
self.type = type
__mapper_args__ = {
'polymorphic_identity':'parent',
'polymorphic_on': case(
[(type == "young", "child_one"),
(type == "old", "child")]
)
}
class Child(Parent):
__tablename__ = "child"
id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
school = Column(String, default="some high school")
def __init__(self, name, school):
self.name = name
self.type = type
self.school = school
__mapper_args__ = {
'polymorphic_identity':'child'
}
class ChildOne(Parent):
__tablename__ = "child_one"
id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
school = Column(String, default="UCLA")
mode = Column(Boolean, default=1)
bool = Column(Boolean, default=0)
def __init__(self, name, type, school, mode, bool):
self.name = name
self.type = type
self.school = school
self.mode = mode
self.bool = bool
__mapper_args__ = {
'polymorphic_identity':'child_one'
}
from sqlalchemy import event
# standard decorator style
#event.listens_for(Child, 'after_update')
def receive_after_update(mapper, connection, target):
"listen for the 'after_update' event"
if target.type == "young":
ChildOne(id=target.id)
But honestly, you should just be creating triggers in the database from something like this. It doesn't require overly complex sqlalchemy code.
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.