How to create viewonly field in SqlAlchemy and GeoAlchemy2? - python

I am trying to dynamically populate a field using SqlAlchemy 0.8.4 and GeoAlchemy2 0.2.2. The goal is to assign a District to the Facility based on the facility's position when it is read from the database. The code looks like this:
class District(Base):
__tablename__ = 'districts'
id = Column(Integer, primary_key=True)
geom = Column('geog', Geometry(geometry_type='POLYGON'), nullable=False)
class Facility(Base):
__tablename__ = 'facilities'
id = Column(Integer, primary_key=True)
pos = Column('geog', Geometry(geometry_type='POINT'), nullable=True)
district = relationship(District,
viewonly=True,
primaryjoin="District.geom.ST_Contains(Facility.pos)",
foreign_keys=[District.id])
But this gives me the following error:
ArgumentError: Could not locate any relevant foreign key columns for
primary join condition 'ST_Contains(districts.geog, facilities.geog)'
on relationship Facility.district. Ensure that referencing columns
are associated with a ForeignKey or ForeignKeyConstraint, or are
annotated in the join condition with the foreign() annotation
I would really like to avoid having foreignkey relation between these classes since the districts are constantly changing, but would like to get a facility with a district set without querying the database again.
How should I solve this?

Related

Accessing sqlalchemy Foreign Key relationship information programmatically

I'm trying to build tests for flask-sqlalchemy models to confirm they match the tables in the database. Using reflection, I can get the db tables, columns, and foreign keys. With the models I know how to check tablename and column names, but I can't figure out how to see the defined foreign key relationships programatically.
Model example:
class Example(db.Model):
__table_args__ = {'schema': 'defined_schema'}
__tablename__ = 'example'
id = db.Column(db.BigInteger, primary_key=True)
name = db.Column(db.String)
info = db.Column(db.BigInteger, db.ForeignKey(Info.id))
descr = db.Column(db.String)
Given a Model m, I've tried:
dir(m)
m.__dict__
Using sqlalchemy.inspect(model).columns.foreign_keys and a bit of string manipulation, I was able to compare model foreign key relationships to those in the DB

Flask-AppBuilder Many-to-Many relationship within the same Table

I have many to many relationships between the same table and want to define them in Flask-AppBuilder. For example, a parent-child relationship between humans, where both instances belong to the same table "Human". However when I do this, I get the error:
Error
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition
between parent/child tables on relationship Human.child - there are no
foreign keys linking these tables. Ensure that referencing columns
are associated with a ForeignKey or ForeignKeyConstraint, or specify a
'primaryjoin' expression.
Example models.py
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
class HumanHasChild(Model):
parent_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
child_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id")
Contrary to the error message, I do specify the foreign key three times. However I tried adding an explicit join condition as well:
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id",primaryjoin="Human.id==HumanHasChild.parent_id")
However even then I get an error:
sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign
key columns for primary join condition 'human.id =
human_has_child.parent_id' on relationship Human.child. Ensure that
referencing columns are associated with a ForeignKey or
ForeignKeyConstraint, or are annotated in the join condition with the
foreign() annotation.
How can I get many-to-many relationships within the same table to work?
Following SQLAlchemy Many-to-Many Relationship on a Single Table, the answer is:
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey, Table
from sqlalchemy.orm import relationship
HumanHasChild = Table("HumanHashChild",Model.metadata,
Column("parent_id",String(200), ForeignKey("Human.id"), primary_key=True),
Column("child_id",String(200), ForeignKey("Human.id"), primary_key=True)
)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("Human",
secondary=HumanHasChild,
foreign_keys = [HumanHasChild.c.parent_id,HumanHasChild.c.child_id],
primaryjoin=id==HumanHasChild.c.parent_id,
secondaryjoin=id==HumanHasChild.c.child_id,
backref="children")

How to create a field with a list of foreign keys in SQLAlchemy?

I am trying to store a list of models within the field of another model. Here is a trivial example below, where I have an existing model, Actor, and I want to create a new model, Movie, with the field Movie.list_of_actors:
import uuid
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy.schema import ForeignKey
rom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
# This is my new model:
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
# How do I make this a list of foreign keys???
list_of_actors = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
I understand that this can be done with a many-to-many relationship, but is there a more simple solution? Note that I don't need to look up which Movie's an Actor is in - I just want to create a new Movie model and access the list of my Actor's. And ideally, I would prefer not to add any new fields to my Actor model.
I've gone through the tutorials using the relationships API, which outlines the various one-to-many/many-to-many combinations using back_propagates and backref here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html But I can't seem to implement my list of foreign keys without creating a full-blown many-to-many implementation.
But if a many-to-many implementation is the only way to proceed, is there a way to implement it without having to create an "association table"? The "association table" is described here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many ? Either way, an example would be very helpful!
Also, if it matters, I am using Postgres 9.5. I see from this post there might be support for arrays in Postgres, so any thoughts on that could be helpful.
Update
It looks like the only reasonable approach here is to create an association table, as shown in the selected answer below. I tried using ARRAY from SQLAlchemy's Postgres Dialect but it doesn't seem to support Foreign Keys. In my example above, I used the following column:
list_of_actors = Column('actors', postgresql.ARRAY(ForeignKey('actors.id')))
but it gives me an error. It seems like support for Postgres ARRAY with Foreign Keys is in progress, but still isn't quite there. Here is the most up to date source of information that I found: http://blog.2ndquadrant.com/postgresql-9-3-development-array-element-foreign-keys/
If you want many actors to be associated to a movie, and many movies be associated to an actor, you want a many-to-many. This means you need an association table. Otherwise, you could chuck away normalisation and use a NoSQL database.
An association table solution might resemble:
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
actors = relationship('ActorMovie', uselist=True, backref='movies')
class ActorMovie(Base):
__tablename__ = 'actor_movies'
actor_id = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
movie_id = Column(UUID(as_uuid=True), ForeignKey('movies.id'))
If you don't want ActorMovie to be an object inheriting from Base, you could use sqlachlemy.schema.Table.

How can I create a many-to-many relationship with SQLAlchemy using a SQL function?

I want to create a relationship between two models that have Geometry columns. For example:
from geoalchemy2.types import Geometry
from flask.ext.sqlalchemy import SQLAlchemy
from myapp import app
db = SQLAlchemy(app)
class Property(db.Model):
id = db.Column(db.Integer, primary_key=True)
street_address = db.Column(db.Text)
geom = db.Column(Geometry(geometry_type='POINT'))
service_areas = db.relationship(
'ServiceArea',
primaryjoin='ServiceArea.geom.ST_Contains(Geocode.geom)',
lazy='joined',
uselist=True,
viewonly=True,
)
class ServiceArea (db.Model):
name = db.Column(db.Text)
value = db.Column(db.Text)
geom = db.Column(Geometry(geometry_type='MULTIPOLYGON'))
In this example, a Property may be associated with many ServiceAreas, and a ServiceArea may be associated with many properties. However, there's not a secondary table for me to use for the relationship -- it is all determined by the ST_Contains function.
Whenever I run this code, I get an sqlalchemy.exc.ArgumentError exception telling me to "Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or are annotated in the join condition with the foreign() annotation."
When I add foreign around the ServiceArea.geom (even though it's not a foreign key), I get an error suggesting that I "Consider using the remote() annotation to accurately mark those elements of the join condition that are on the remote side of the relationship."
I've tried using foreign and remote separately as well as together (e.g., foreign(remote(ServiceArea.geom)) and remote(foreign(ServiceArea.geom))) but always get back one of the above errors. What am I doing wrong?

How do I correct this sqlalchemy.exc.NoForeignKeysError?

Why do I get the TraceBack
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition
between parent/child tables on relationship County.Legislators -
there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a
ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
with the following models:
class County(Base):
__tablename__ = 'tblCounty'
CountyCode = Column('CountyCode', String, primary_key=True)
Legislators = relationship('Legislators', backref='County', lazy='dynamic')
class Legislators(Base):
__tablename__ = 'VLegislators'
EmployeeNo = Column('EmployeeNo', String, primary_key=True)
CountyCode = Column('CountyCode', String, ForeignKey('County.CountyCode'))
I'm trying to map a public facing MS SQL database provided by the State of New Hampshire. So no schema changes allowed.
Why does it complain about the lack of a ForeignKey relation when one is clearly defined in class Legislators?
AFAIK you should use tablename in ForeignKey:
CountyCode = Column('CountyCode', String, ForeignKey('tblCounty.CountyCode'))

Categories