SQLAlchemy many to many against same table with no hierarchy - python

I would like to build a relationship between instances of a single table in Sqlalchemy using the declarative method.
I have a table called Hex, which represent hexagons on a map. I would like a table called Link which links two hexes together. The backref "links" on each instance should return at most 6 links, meaning a unique constraint on the hex edge type. Additionally, I'm trying to use the association object so I can have methods on this link class.
So:
Given Hex A and Hex B
A -> B (A.links should contain a link to B)
B -> A (B.links should contain the same link object to A)
My question:
What kind of relationship is this called? I've tried other methods but this example I've demonstrated doesn't have a parent child relationship.
Edit:
I tried this configuration:
class Link(Base, Record):
__tablename__ = 'link'
id = Column(Integer, primary_key=True)
hex_a_id = Column(Integer, ForeignKey('hex.id'), index=True)
hex_a = relationship('Hex', backref=backref('links', lazy='dynamic'), foreign_keys='Link.hex_a_id')
hex_b_id = Column(Integer, ForeignKey('hex.id'), index=True)
hex_b = relationship('Hex', backref=backref('links', lazy='dynamic'), foreign_keys='Link.hex_b_id')
type = Column(PythonEnum(HexEdge, 'id'), nullable=False)
__table_args__ = (UniqueConstraint('hex_a_id', 'hex_b_id', name='_link_uc'), )
and got this error:
sqlalchemy.exc.ArgumentError: Error creating backref 'links' on relationship 'Link.hex_b': property of that name exists on mapper 'Mapper|Hex|hex'

Related

SQLAlchemy: Filter the Objects From Relationship When Accessed

Query Statement: Get Children that have their Names start with 'A'. Link Them With their Parents.
Schema:
I Have a One-to-Many Relationship. Parent(id, Name, children(rel->child)) -> Child(id, Name)
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
Name = Column(String)
children = relationship("Child", lazy='joined')
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
Name = Column(String)
Want to Filter the Relationship Object. (ie, the List of Objects you get when an instantiated Parent.children) is accessed.
Eg: [Parent(id=1, Name='Parent1' children=[Child(id='1', Name='AChild1'), Child(id='2', Name='Child2')] Needs to be Filtered to: [Parent(id=1, Name='Parent1' children=[Child(id='1', Name='AChild1')] when the resulting query is accessed.
How do I write a Statement to get the Above Result?
A solution that Filters them Once Loaded, I want to Filter them While Loading.
Perhaps I should've googl-fu'ed Harder, but this was the result of some searching.
From SQLAlchemy Documentation:
When we use contains_eager(), we are constructing ourselves the SQL that will be used to populate collections. From this, it naturally follows that we can opt to modify what values the collection is intended to store, by writing our SQL to load a subset of elements for collections or scalar attributes.
Resulting in the Statement:
db.Query(Parent).join(Parent.children)\
.filter(Parent.children.any(Child.Name.like("A%")))\
.options(contains_eager(Parent.children))

Adding many to many relation in SQLAlchemy after Reflection

Follwing the documentation, I have the following tables defined and populated but no relations have been defined yet.
class CountryAssociation(Base):
__tablename__ = 'Country_Region_Mapping'
country_id = Column(Integer, ForeignKey('countries.uid'), primary_key=True)
region_id = Column(Integer, ForeignKey('regions.uid'), primary_key=True)
region = relationship('Region', back_populates='country')
country = relationship('Countries', back_populates='region')
extra_data = Column(String(50))
class Countries(Base):
__tablename__ = 'countries'
uid = Column(Integer, primary_key=True)
countryname = Column('English_short_name',
String(255), unique=True, nullable=False)
region = relationship('CountryAssociation',
back_populates='country')
class Region(Base):
__tablename__ = 'regions'
uid = Column(Integer, primary_key=True)
region = Column(String(255), unique=True, nullable=False)
country = relationship('CountryAssociation',
back_populates='region')
I now want to create many to many relations between the tables. docs
Base = automap_base() #reflecting the orm way
engine = create_engine('sqlite:///mydatabse.db')
Base.prepare(engine, reflect=True)
Session = sessionmaker(bind=engine)
session = Session()
table_countries = Base.classes.countries
table_regions = Base.classes.regions
r = session.query(table_regions).filter(table_regions.region == "Western Europe").first()
c = session.query(table_countries).filter(table_countries.English_short_name == "Germany").first()
c.region.append(r) # this fails with
AttributeError: 'countries' object has no attribute 'region'
This however works:
c.countryname # Germany
I dont get what I am doing wrong here (beginner)...
Because you've used the association object pattern with extra_data the automap relationship detection will not recognize Country_Region_Mapping as a secondary table of a many to many:
If the table contains two and exactly two ForeignKeyConstraint objects, and all columns within this table are members of these two ForeignKeyConstraint objects, the table is assumed to be a “secondary” table, and will not be mapped directly.
Put another way: not all columns within Country_Region_Mapping are members of the foreign key constraints, so the table is not a secondary, so no many to many relationship is created.
Another thing you've overlooked is the naming scheme. If you had a working secondary table, then the created relationship would be named regions_collection by default (because of the plural table name).
What does happen is that the many to one / one to many relationships between regions and Country_Region_Mapping, and countries and Country_Region_Mapping are created:
In [22]: table_countries.country_region_mapping_collection
Out[22]: <sqlalchemy.orm.attributes.InstrumentedAttribute at 0x7f2d600fb258>
In [23]: table_regions.country_region_mapping_collection
Out[23]: <sqlalchemy.orm.attributes.InstrumentedAttribute at 0x7f2d600fbaf0>
In [28]: table_country_region_mapping.countries
Out[28]: <sqlalchemy.orm.attributes.InstrumentedAttribute at 0x7f2d535a2990>
In [29]: table_country_region_mapping.regions
Out[29]: <sqlalchemy.orm.attributes.InstrumentedAttribute at 0x7f2d535a2938>
Note that due to plural table naming the scalar relationship attributes on Country_Region_Mapping also have plural naming.
With these in mind you'd add a new association like so:
In [36]: c.country_region_mapping_collection.append(
...: Base.classes.Country_Region_Mapping(countries=c, regions=r))

SQLAlchemy querying with joins

With this models:
class Ball(Base):
__tablename__ = 'balls'
id = Column(Integer, primary_key=True)
field_id = Column(Integer, ForeignKey('fields.id'))
field = relationship("Field", back_populates="fields")
class Field(Base):
__tablename__ = 'fields'
id = Column(Integer, primary_key=True)
nickname = Column(String)
fields= relationship("Ball", order_by=Ball.id, back_populates="field")
I'm trying to write query to access Field.nickname and Ball.field_id. With use of
result = session.query(Field, Ball).all()
print(result)
I'm able to retrieve
(<Field(id=1, nickname=stadium>, <Ball(id=1, field_id=newest)>), (<Field(id=1, nickname=stadium>, <Ball(id=2, field_id=oldest)>
but when using
result = session.query(Field).join(Ball).all()
print(result)
I'm getting empty list [], doesn't matter if I'm applying any filters or not. According to official docs, join would come in handy if dealing with two tables, so this way I would be able to filter for the same id in both and I guess it would came in handy when displaying query results in jinja template.
You have a typo in your Field class - back_populates='fields' doesn't match up with the attribute name in Ball.
Try changing it to back_populates='field' so that it matches the attribute defined in Ball. With the relationship defined correctly sqlalchemy should be able to do the join automatically.

Many-to-many relations from table to itself in SQLAlchemy

I'm using SQLAlchemy to represent a relationship between authors. I'd like to have authors related to other authors (coauthorshp), with extra data in the relation, such that with an author a I can find their coauthors.
How this is done between two different objects is this:
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(80))
child = relationship('Child', backref='parent_assocs')
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship('Association', backref='parent')
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
but how would I do this in my case?
The nature of a coauthorship is that it is bidirectional. So, when you insert the tuple (id_left, id_right) into the coauthorship table through a coauthoship object, is there a way to also insert the reverse relation easily? I'm asking because I want to use association proxies.
if you'd like to literally have pairs of rows in association, that is, for every id_left, id_right that's inserted, you also insert an id_right, id_left, you'd use an attribute event to listen for append events on either side, and produce an append in the other direction.
If you just want to be able to navigate between Parent/Child in either direction, just a single row of id_left, id_right is sufficient. The examples in the docs regarding this kind of mapping illustrate the whole thing.

SQLAlchemy One-to-Many relationship on single table inheritance - declarative

Basically, I have this model, where I mapped in a single table a "BaseNode" class, and two subclasses. The point is that I need one of the subclasses, to have a one-to-many relationship with the other subclass.
So in sort, it is a relationship with another row of different class (subclass), but in the same table.
How do you think I could write it using declarative syntax?.
Note: Due to other relationships in my model, if it is possible, I really need to stick with single table inheritance.
class BaseNode(DBBase):
__tablename__ = 'base_node'
id = Column(Integer, primary_key=True)
discriminator = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class NodeTypeA(BaseNode):
__mapper_args__ = {'polymorphic_identity': 'NodeTypeA'}
typeB_children = relationship('NodeTypeB', backref='parent_node')
class NodeTypeB(BaseNode):
__mapper_args__ = {'polymorphic_identity': 'NodeTypeB'}
parent_id = Column(Integer, ForeignKey('base_node.id'))
Using this code will throw:
sqlalchemy.exc.ArgumentError: NodeTypeA.typeB_children and
back-reference NodeTypeB.parent_node are both of the same direction
. Did you mean to set remote_side on the
many-to-one side ?
Any ideas or suggestions?
I was struggling through this myself earlier. I was able to get this self-referential relationship working:
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String(64), nullable=False)
Employee.manager_id = Column(Integer, ForeignKey(Employee.id))
Employee.manager = relationship(Employee, backref='subordinates',
remote_side=Employee.id)
Note that the manager and manager_id are "monkey-patched" because you cannot make self-references within a class definition.
So in your example, I would guess this:
class NodeTypeA(BaseNode):
__mapper_args__ = {'polymorphic_identity': 'NodeTypeA'}
typeB_children = relationship('NodeTypeB', backref='parent_node',
remote_side='NodeTypeB.parent_id')
EDIT: Basically what your error is telling you is that the relationship and its backref are both identical. So whatever rules that SA is applying to figure out what the table-level relationships are, they don't jive with the information you are providing.
I learned that simply saying mycolumn=relationship(OtherTable) in your declarative class will result in mycolumn being a list, assuming that SA can detect an unambiguous relationship. So if you really want an object to have a link to its parent, rather than its children, you can define parent=relationship(OtherTable, backref='children', remote_side=OtherTable.id) in the child table. That defines both directions of the parent-child relationship.

Categories