skip to Main Content

I have a mongoengine document such as:

from mongoengine.document import Document
from mongoengine import StringField

class Class_A(Document):
    field_a = StringField()
    field_b = StringField()

I’d like to lock field_b so it cannot be altered, e.g.

var = <fetch Class_a document from DB>
var.field = 'abc'

would raise an error.

This on itself is not a problem, but I’d like to be able to set field_b when field_a is set. To give an example, field_a could be some data and field_b would be computed hash for this data – user can set the data, but not the hash for it (it should be only set automatically when data is assigned).

I tried using __setattr__/__dict__, but mongoengine seems to be doing some attributes magic behind the scene and I couldn’t make it work. I also had an idea to subclass StringField and use a metaclass to wrap it’s __setattr__, with similar effect.

How to achieve such a behaviour?

2

Answers


  1. As mentioned in comments, I think using Change Streams to update field_b when field_a is updated/setted under the hood seems more decent.

    On Document struct level, we should mark field_b as protected so no unintentional update is performed in codes.

    Firstly, It’s hard for interpreter to do something like:

    if field_a.is_update():  # pseudo code
        # ok we can update field_b inside if statement
        field_b = "bbbbb"
    field_b = "ccccc" # NO! you can't do it here
    

    Secondly, I don’t think using metaclass or other stuff to change the behavior of a third-party library is a good idea since a break change from upstream could possibly break everything…

    Finally, if you insists, I believe snippet below could be used as a work-around:

    from mongoengine.document import Document
    from mongoengine import StringField
    
    # pseudo code
    class ProtectedField(StringField):
        def __set__(self, instance, value):
            # we can set field_b based on instance and discard value
            # or we can raise error if value is not None
            raise AttributeError("can't set attribute to it")
        # other possible methods to override
    
    class Class_A(Document):
        field_a = StringField()
        field_b = ProtectedField()
    
        @property
        def field_a(self):
            return self._field_a
    
        @field_a.setter
        def field_a(self, value):
            self._field_a = value
    
    Login or Signup to reply.
  2. Approach Using a Custom Property for field_a and Signal to Handle Field Modification

    from mongoengine import Document, StringField
    import hashlib
    
    class Class_A(Document):
        _field_a = StringField()
        field_b = StringField()
    
        @property
        def field_a(self):
            return self._field_a
    
        @field_a.setter
        def field_a(self, value):
            # Automatically set the hash of field_a when field_a is set
            self._field_a = value
            self.field_b = hashlib.sha256(value.encode()).hexdigest()
    
        def save(self, *args, **kwargs):
            # Prevent field_b from being set directly
            if 'field_b' in self._changed_fields:
                raise AttributeError("field_b cannot be modified directly.")
            super(Class_A, self).save(*args, **kwargs)
    

    Explanation:

    1. Custom Property for field_a:

      • We define a custom property for field_a. The field_a setter automatically computes a hash (or any derived value) and assigns it to field_b whenever field_a is updated.
    2. Prevent Direct Modification of field_b:

      • In the save method, we check if field_b has been modified directly by the user (it’s part of the _changed_fields list). If it has, an exception is raised, preventing the user from modifying field_b.
    3. Hash Function:

      • The hashlib.sha256 function is used as an example to compute a hash for field_a. You can replace this with any logic depending on your use case.

    Example Usage:

    # Creating and saving a document
    doc = Class_A(field_a="my data")
    doc.save()
    print(doc.field_b)  # Outputs the computed hash based on field_a
    
    # Trying to modify field_b directly
    doc.field_b = "some value"  # This won't raise an error immediately but...
    doc.save()  # Raises AttributeError: field_b cannot be modified directly.
    
    # Updating field_a
    doc.field_a = "new data"
    doc.save()  # field_b is automatically updated with the new hash
    print(doc.field_b)  # Outputs the updated hash
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search