Scille / umongo

sync/async MongoDB ODM, yes.
MIT License
448 stars 63 forks source link

Using inhertiance to share model fields #284

Open emarthinsen opened 4 years ago

emarthinsen commented 4 years ago

We have an application that uses an async driver and we are extracting some of it into an application that will use a sync driver. In a lot of ways, it is similar to how the docs talk about using model definitions across an async web app and a sync console app. However our existing models that we use with the async driver have some methods on them that are async themselves, so I can't just use the same model definition, but register them with different instances. The app that uses the sync driver doesn't need much more than the field mappings, so I was thinking I'd just create two models and have them share the same mappings. Here was my naive approach:

class UserFields(Document):
  email = fields.EmailField(required=True, unique=True)

  class Meta:
    collection_name = 'users'
    strict = False

@sync_instance.register
class UserSync(UserField):
  pass

@async_instance.register
class UserAsync(UserFields):
  async def some_async_method(self):
    pass

That doesn't work at all. I get an exception that says:

umongo.exceptions.NotRegisteredDocumentError: Unknown document `<Template class 'app.models.UserFields'>`

I'm new to umongo, so I suspect user error on my part. Does anyone know the best way to do what I'm doing? Maybe there's a better way to approach this problem that I haven't considered?

lafrech commented 4 years ago

Yes, you can only inherit from a document that is also registered in the instance.

You need to register UserSync in the async instance too.

Note there are side effects side effects with inheritance. UserAsync will be a child of concrete UserSync class, so it will add a _cls field to the document in DB. If you use both drivers to modify the document in DB, one relying on this _cls field and the other not, you might get in trouble.

emarthinsen commented 4 years ago

@lafrech Thanks for your reply. That gets me a little bit closer, but if I do something like this:

class UserFields(Document):
  email = fields.EmailField(required=True, unique=True)

  class Meta:
    allow_inheritance = True
    collection_name = 'users'
    strict = False

@sync_instance.register
class UserSync(UserField):
  pass

@async_instance.register
class UserAsync(UserField):
  pass

Then it looks like if I run u = UserSync.find_one({'email': email}), then it will only find instances where _cls is UserSync. It won't find any of the users that were created via UserAsync. That's what I'm inferring from my tests. Is that the case? Is there a way to get the child classes to ignore the _cls field?

lafrech commented 4 years ago

You're right about the _cls field.

I can't think of a clean way to support your use case.

Not saying it is not feasible with or even without modifications to umongo.

I can't investigate this right now.

Please keep us posted with your progress. I things can be done in umongo to achieve this, now (with umongo 3 in beta) would be a good time.

emarthinsen commented 4 years ago

I'm new enough to umongo that I'm not going to be very useful on the investigation, but I'll poke around.

One thing that I did notice is that the build_from_mongo method take a use_cls parameter. Maybe that's something that could be leveraged? It looks like it is only used internally though and there's no way to set the value of that externally. It might work if something in the Meta could be set that said use_cls = False. Just speculating though.