Search code examples
mongodbpython-3.xormpymongopymodm

Querying related collection by object in pymodm?


I'm using pymodm for ORM in my project.

I have the following simple case:

class IdentityRecord(MongoModel):
    alias = fields.CharField()  # normal name for identity


class IdentityImage(MongoModel):
    filename = fields.CharField()  # filename on filesystem
    source_identity = fields.ReferenceField(IdentityRecord)  # reference to an identity

As we can see, each IdentityImage refers to IdentityRecord.

If I have object IdentityRecord, then how do I find all records from IdentityImage that refer to this object inside python code?

Of course, I can do the following:

IdentityImage.objects.raw({'source_identity': identity.pk})

However, necessity to user 'source_identity' string literal kinda defeats the purpose of ORM. Is there any way in this framework to query IdentityImage collection by somehow using an instance of IdentityRecord object?


Solution

  • Selective queries in a pymodm.manager.Manager (such as IdentityImage.objects in the example from the question) seem to require a dict as the argument for the get and raw methods. In contrast, the filter_by method in a sqlalchemy query accepts keyword arguments.

    If you prefer keyword arguments to string literals, the following expression seems to work.

    IdentityImage.objects.raw(dict(source_identity=identity.pk))
    

    Omitting the dict wrapper in that expression reveals that the raw method does not accept field names as keyword arguments. If pymodm were modified to allow that, the notation for such a query would be simpler.

    The following code is a mutation of the original code from the question. The first import line is implicit in the original code, and explicit here. The other two import lines enable the definition and the use of a new class, KWQuerySet. Aside from a helper function for the new class, args, the only other change is the last line, a new attribute in one of the classes from the original code, which uses the new class.

    from pymodm import MongoModel, fields
    from pymodm.queryset import QuerySet
    from pymodm.manager import Manager
    
    def args(arg=None, **kwargs):
        return {**arg, **kwargs} if arg else kwargs
    
    class KWQuerySet(QuerySet):
        def raw(self, raw_query=None, **kwargs):
            return super().raw(args(raw_query, **kwargs))
        def get(self, raw_query=None, **kwargs):
            return super().get(args(raw_query, **kwargs))
    
    class IdentityRecord(MongoModel):
            alias = fields.CharField()  # normal name for identity                                                                                                                                                                                                                                                                                                          
    
    class IdentityImage(MongoModel):
        filename = fields.CharField()  # filename on filesystem                                                                                                                                                                                                                                                                                                             
        source_identity = fields.ReferenceField(IdentityRecord)  # reference to an identity                                                                                                                                                                                                                                                                                 
        objects = Manager.from_queryset(KWQuerySet)()
    

    With the definition of IdentityImage from the mutated code, the following query appears to work correctly in python 3.5.3, with the same meaning for identity (an instance of IdentityRecord) implied in the example query from the question.

    IdentityImage.objects.raw(source_identity=identity.pk)
    

    It's likely that versions of python prior to 3.5 would require an alternate implementation of the args function in the mutated code. I have not fully explored the implications of this change to the notation for queries, but I believe any query which passes raw_query as a dict should still work, with or without the notation which uses keyword arguments, or a combination of both notations, a dict for raw_query and separate keyword arguments for other field names.