1

I have a web server running on App Engine that uses ndb for data storage.

The data model looks something like this:

@acl
class MyModel(ndb.Model): 
    ... 
    access_control = ndb.JsonProperty(default={})

I use the @acl decorator to augment my model with some access control methods. The decorator looks something like this:

def acl(model):
    def grant(self, subject, credentials): 
        logging.debug("ACL before: {}".format(self.access_control))
        self.access_control[subject] = { ... }  # Set correct value.
        logging.debug("ACL after: {}".format(self.access_control))
    model.grant = grant
    ...
...

From my application, then I would expect to call it like this:

>>> mdl = MyModel(...)
>>> mdl.grant("someone", "creds")
ACL before: {}
ACL after: { < properly populated access_control > }

But instead I get something similar to this:

>>> mdl1 = MyModel(...)
>>> mdl1.grant("someone", "creds")
ACL before: {}
ACL after: { < properly populated access_control > }

>>> mdl2 = MyModel(...)
>>> mdl2.grant("someone else", "other creds")
ACL before: { < values from mdl1 > }
ACL after: { < values from mdl1 concatenated with mdl2 > }

This bug is making me suspect that self in the grant() function is somehow acting like a global value, since it's accumulating data from previous calls, even when these calls are performed on different instances.

The question is: Why are my models spilling data between them? Is self in context of the decorator, the same as self in the context of a class method?

ButterDog
  • 5,115
  • 6
  • 43
  • 61

1 Answers1

1

The model properties are static elements. See also correct way to define class variables in Python.

The presence of the class decorator may be interfering with their normal operations from the ndb internals, which would normally take care of the proper initialisation of the property values.

I'm not familiar enough with class decorators, so I'm unsure if/how you could address this with a modified decorator. Maybe something like this (explicitly initializing the property)?

def acl(model):
    def grant(self, subject, credentials): 
        self.access_control = {}  # Always start with the default value
        logging.debug("ACL before: {}".format(self.access_control))
        self.access_control[subject] = { ... }  # Set correct value.
        logging.debug("ACL after: {}".format(self.access_control))
    model.grant = grant

The (IMHO easier to comprehend) solution I chose to achieve a similar functionality was to use plain class inheritance instead of decorators (but note the same resetting of the value):

class ACLModel(ndb.Model):
    access_control = ndb.JsonProperty(default={})

    def grant(self, subject, credentials):
        self.access_control = {}
        self.access_control[subject] = { ... }  # Set correct value.

class MyModel(ACLModel):
    ...
    # other specific properties

The inheritance method allows me to easily use the same access control code (self-contained) for multiple models. The decorator method would place additional requirements for the models with which it could be used - they'd all need to have an access_control property - not self-contained.

Dan Cornilescu
  • 39,470
  • 12
  • 57
  • 97