-1

I'm trying to create dynamic filtering in SQLAlchemy with relationship entities. I have the following SQLAlchemy models:

Owner(Model):
    __tablename__ = 'Owners'
    Id = Column(Integer, primary_key=True, autoincrement=True)
    Name = Column(String(100))

Vehicle(Model):
    __tablename__ = 'Vehicles'
    Id = Column(Integer, primary_key=True, autoincrement=True)
    LicensePlate = Column(String(20))
    Model = Column(String(20))
    Owner_id = Column(Integer, ForeignKey('Owners.Id'))
    Owner = relationship("Owner")

So lets say I have the following filters:

filters = [("LicensePlate", "TEST"), ("Model", "TEST")]

This would be equivalent to the following query:

Vehicle.query.filter(Vehicle.LicensePlate == "TEST", Vehicle.Model == "TEST")

I then construct my query with:

query_filters = []
for f in filters:
    query_filter.append(getattr(Vehicle, f[0]) == f[1])
Vehicle.query.filter(*query_filters)

But now lets say I would also like to filter my Vehicle model by the Owner property: In order to achieve that I can construct the following query:

Vehicle.query.filter(Vehicle.LicensePlate == "TEST", Vehicle.Model == "TEST").join(Vehicle.Owner).filter(Owner.Name == "John")

And I would like my dynamic filters to be constructed as:

filters = [("LicensePlate", "TEST"), ("Model", "TEST"), ("Owner.Name", "John")]

I could then easily split the propery name and use it to perform the join and creating filter.

property_type, property_attr = "Owner.Name".split(".")

However I have not found a way on how to use reflection in Python go get the Object from a string name as can be done for the property by using the getattr

The join part is not a problem, since Owner is an attribute on the Vehicle:

.join(getattr(Vehicle, property_type))

So for achieving the same as:

.filter(Owner.Name == "John")

I would need to somehow get the object from its name and then use it with the attribute name to construct the filter.

my_obj = ? property_type ?
.filter(getattr(my_obj, property_attr) == "John")

I have temporarily solved this by creating a custom dictionary that has the mapping between names and objects:

mapping = {
    "Owner": Owner
}

So I can then construct the filter as:

property_type, property_attr = "Owner.Name".split(".")
.filter(getattr(mapping[property_type], property_attr) == "John")

However this dictionary is something that I have to maintain manually so I would prefer a solution that would use reflection to get the object from its name.

Denis Vitez
  • 608
  • 11
  • 32
  • [Get class by name string](https://stackoverflow.com/questions/17959996/how-to-get-class-object-from-class-name-string-in-the-same-module). Although you still need to import all your models to one file. You can change your filters to look like dict (with relations to join as keys (or `None` if no join) and list of colums as value, this will avoid multiple joins that are happening in your case) – sudden_appearance Feb 28 '23 at 12:59
  • So you mean if for example, I would have two filters [("Owner.Name", "John"), ("Owner.Name2", "Doe")] this would result in .join(Vehicle.Owner).join(Vehicle.Owner)? I am aware of that, just did not want to overcomplicate my question :) – Denis Vitez Feb 28 '23 at 15:09

1 Answers1

1

You can use getattr on the module that contains your models.

from mylib import mymodels

owner_cls = getattr(mymodels, 'Owner')

You can also self-reference the same module where your getattr logic is located:

import sys

class Owner(Model):
    pass

mymodels = sys.modules[__name__]

owner_cls = getattr(mymodels, 'Owner')
Kuroneko
  • 146
  • 6