I need to get a list of a model's properties which are actually relationships (that is, they were created by relationship()).
Say I have a model Foo in a models:
class Thing(db.Model):
id = db.Column(...)
bar_id = db.Column(...)
foo_id = db.Column(...)
foo = db.relationship('Foo')
bar = db.relationship('Bar')
Later on, I want to take models.Thing and get a list of relationship-properties, that is ['foo', 'bar'].
Currently I'm checking every attribute indicated by dir(models.Thing) that happens to be of type sqlalchemy.orm.attributes.InstrumentedAttribute for the class of its property attribute — which can be either a ColumnProperty or RelationshipProperty. This does the job but I was wondering if there's another way.
I could probably just find all attributes ending in _id and derive the relationship name, but this could break for some cases.
How about setting a __relationships__ = ['foo', 'bar']?
Or is there something built into SQLAlchemy to help me out?
There is indeed - take a look at sqlalchemy.inspection.inspect. Calling inspect on a mapped class (for example, your Thing class) will return a Mapper, which has a relationships attribute that is dict like:
from sqlalchemy.inspection import inspect
thing_relations = inspect(Thing).relationships.items()
Instead of using inspect you can also use
model.__mapper__.relationships
You just need to use the inspect module from sqlalchemy
from sqlalchemy import inspect
i = inspect(model)
i.relationships
If you need the class of each referred model you can do:
referred_classes = [r.mapper.class_ for r in i.relationships]
Related
Say I have a Thing class that is related to some other classes, Foo and Bar.
class Thing(Base):
FooKey = Column('FooKey', Integer,
ForeignKey('FooTable.FooKey'), primary_key=True)
BarKey = Column('BarKey', Integer, ForeignKey('BarTable.BarKey'), primary_key=True)
foo = db.relationship('Foo')
bar = db.relationship('Bar')
I want to get a list of the classes/tables related to Thing created by my relationships() e.g. [Foo, Bar]. Any way to do this?
This is a closely related question:
SQLAlchemy, Flask: get relationships from a db.Model. That identifies the string names of the relationships, but not the target classes.
Context:
I'm building unit tests for my declarative base mapping of a SQL database. A lot of dev work is going into it and I want robust checks in place.
Using the Mapper as described in that other question gets you on the right path. As mentioned on the doc [0], you will get a bunch of sqlalchemy.orm.relationships.RelationshipProperty, and then you can use class_ on the mapper associated with each RelationshipProperty to get to the class:
from sqlalchemy.inspection import inspect
rels = inspect(Thing).relationships
clss = [rel.mapper.class_ for rel in rels]
Suppose I have the following 2 Django models:
class MyModelA(models.Model):
my_int = models.IntegerField()
class MyModelB(models.Model):
my_int = models.IntegerField()
my_a = models.ForeignKey(MyModelA, related_name="MyModelB_a")
I can create instances of MyModelA in two ways:
# First technique:
>>> a = MyModelA.objects.create(my_int=5)
# Second technique:
>>> a = MyModelA(my_int=5)
>>> a.save()
If I know that I won't need to change a between instantiation and saving, I go with the first technique (to reduce lines of code). If I know that a will be changing between instantiation and saving, I usually go with the second technique shown above (to minimize database accesses).
Analogously when I want to create an instance of MyModelB from a method within MyModelA through the reverse-foreign-key relationship, I do the following:
# First technique:
>>> b = self.MyModelB_a.create(my_int=6)
But I don't know how to create instances of MyModelB through the reverse-foreign-key relationship using the second technique. How to do it? What function to call? I want to create an instance using the related_name MyModelB_a without saving it to the DB until I explicitly call save()
# Second technique:
>>> b = a.MyModelB_a.WHAT-GOES-HERE(my_int=6)
>>> b.save()
If I understand what you mean .. you want something like a.MyModelB_a.new(my_int=6). Something like .build in rails for example! I'm afraid that's not exist in django.
But if you just don't want to import MyModelB for some reason, you can use a work around if you are OK with that.
You can use a.MyModelB_a.model(my_int=6, my_a=a)
Edit:
Or you can override Manager class to implement your own method. I didn't try to override RelatedManager before, but it appears that it's allowed in django by use_for_related_fields.
I have a simple SqlAlchemy application:
import sqlalchemy as sa
import sqlalchemy.ext.declarative as dec
import sqlalchemy.engine.url as saurl
import sqlalchemy.orm as saorm
import sqlalchemy.schema as sch
import abc
class ItemTable():
__tablename__ = 'book_items'
#abc.abstractmethod
def _source_key(self):
pass
rowid = sa.Column(sa.Integer, sa.Sequence('book_page_id_seq'), primary_key=True)
src = sa.Column(sa.String, nullable=False, index=True, default=_source_key)
dlState = sa.Column(sa.Integer, nullable=False, index=True, default=0)
url = sa.Column(sa.String, nullable=False, unique=True, index=True)
# [...snip...]
Base = dec.declarative_base(cls=ItemTable)
class TestItem(Base):
_source_key = 'test'
def __init__(self, *args, **kwds):
# Set the default value of `src`. Somehow, despite the fact that `self.src` is being set
# to a string, it still works.
self.src = self._source_key
print(self)
print(type(self))
print(super())
print("IsInstance of ItemTable", isinstance(self, ItemTable))
print("IsInstance of Table", isinstance(self, sch.Table))
super().__init__(*args, **kwds)
def test():
test = TestItem()
if __name__ == "__main__":
test()
The idea is that the table schema is defined in ItemTable, and certain member attributes are defined as abstract. This ensures child-classes define certain member attributes, that are then used as value defaults by the instantiated child-class via some __init__() hijinks.
Anyways, this much works.
The issue I'm having is that I cannot for the life of me figure out what the hell the parents of TestItem(Base) are. I know it inherits from ItemTable(), but the intermediate inheritance of dec.declarative_base(cls=ItemTable) is inserting a whole bunch of methods and "stuff" into TestItem(Base), and I don't know what is there, or where it's coming from.
I'm pretty sure there are some functions that would make my life a LOT easier with regard to modifying a row in the table, but since I don't know what TestItem(Base) is actually inheriting from, I have no idea where to look at all in the SqlAlchemy documentation.
The documentation does say about declarative_base():
The new base class will be given a metaclass that produces appropriate
Table objects and makes the appropriate mapper() calls based on the
information provided declaratively in the class and any subclasses of
the class.
Which makes me think that possibly TestItem(Base) is a child-class of Table, but isinstance(self, sch.Table) returns false, so either it's not, or the metaclass muckery is completely breaking isinstance.
Also, TestItem(Base) being a child-class of Table wouldn't make any sense logically, because you get instances of TestItem(Base) returned when you query, with each instance representing a row.
Anyways, I'm thoroughly confused.
Update:
#Veedrac in the comments pointed out that ClassName.mro() gives you the full inheritance. In this case:
TestItem.mro() ->
[<class '__main__.TestItem'>, <class 'sqlalchemy.ext.declarative.api.Base'>, <class '__main__.ItemTable'>, <class 'object'>]
The fun thing here is that there are zero instances of sqlalchemy.ext.declarative.api.Base anywhere in the SqlAlchemy documentation.
The only thing documented along the sqlalchemy.ext.declarative.api path at all is _declarative_constructor, and there are ~2 unhelpful sentences there.
Well, the end solution to my issues here was to just flat-out dump SqlAlchemy entirely.
I know exactly how to achieve what I want using SQL. I assumed that SqlAlchemy would make things easier, but it just lead to inheritance nightmares and lots of bizarre issues.
I'm sure there is a way to manage what I want in SqlAlchemy, but the documentation is so terrible that I'm unable to find it.
I have following code:
class ArchaeologicalRecord(Base, ObservableMixin, ConcurrentMixin):
author_id = Column(Integer, ForeignKey('authors.id'))
author = relationship('Author', backref=backref('record'))
horizont_id = Column(Integer, ForeignKey('horizonts.id'))
horizont = relationship('Horizont', backref=backref('record'))
.....
somefield_id = Column(Integer, ForeignKey('somefields.id'))
somefield = relationship('SomeModel', backref=backref('record'))
At the moment I have one of entry (Author or Horizont or any other entry which related to arch.record). And I want to ensure that no one record has reference to this field. But I hate to write a lot of code for each case and want to do it most common way.
So, actually I have:
instance of ArchaeologicalRecord
instance of child entity, for example, Horizont
(from previous) it's class definition.
How to check whether any ArchaeologicalRecord contains (or does not) reference to Horizont (or any other child entity) without writing great chunk of copy-pasted code?
Are you asking how to find orphaned authors, horzonts, somefields etc?
Assuming all your relations are many-to-one (ArchaelogicalRecord-to-Author), you could try something like:
from sqlalchemy.orm.properties import RelationshipProperty
from sqlalchemy.orm import class_mapper
session = ... # However you setup the session
# ArchaelogicalRecord will have various properties defined,
# some of these are RelationshipProperties, which hold the info you want
for rp in class_mapper(ArchaeologicalRecord).iterate_properties:
if not isinstance(rp, RelationshipProperty):
continue
query = session.query(rp.mapper.class_)\
.filter(~getattr(rp.mapper.class_, rp.backref[0]).any())
orphans = query.all()
if orphans:
# Do something...
print rp.mapper.class_
print orphans
This will fail when rp.backref is None (i.e. where you've defined a relationship without a backref) - in this case you'd probably have to construct the query a bit more manually, but the RelationshipProperty, and it's .mapper and .mapper.class_ attributes should get you all the info you need to do this in a generic way.
I have two mapped classes with a one-to-many relation:
class Part(...):
product = relationship('products', backref=backref('parts'))
class Product(...):
pass
Given Part.product, I can introspect this relationship, namely get the attribute name, and also get the backref attribute name:
>>> rel = Part.product # image it's passed in as a function parameter
>>> rel.property.key
'product'
>>> rel.property.backref[0]
'parts'
I can also access the relationship the other way round:
>>> rel = Product.parts
>>> rel
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x3744fd0>
>>> rel.property.key
'parts'
However, I cannot find out how to access the original attribute name (aka the backref' backref attribute, aka 'product' in the example):
>>> rel.property.backref is None
True
Where do I have to tickle Product.parts to obtain 'product'?
I tried to reproduce situation your described and got Product.parts.property.backref = None too.
After debugging in pycharm I found that other property holds the name of property in parts:
print Product.parts.property.backref
>>>None
print Product.parts.property.back_populates
>>>product
I would suggest to consider using back_populates in this case as a hack.
back_populates is described in documentation Linking Relationship Configuration:Relationships with Backref. According to documentation you would need to define your model like that:
class Part(...):
product = relationship('products', back_populates='parts')
class Product(...):
parts = relationship('products', back_populates='product')
pass