0

I'm writing a REST api over Google App Engine NDB

I excluded existing libraries because I need control over transactions and caching. I excluded Google Endpoints for a similar reason and also because I don't want to use the javascript client they provide.

When evaluating architectural decisions I encountered some problems and weird situations, this is because my experience with python and pythonic style is probably not big enough.

In this moment I tried to come up with some guidelines that should shape my codebase:

  • in Handlers -> create the dictionary representation of the objects
    and return them as json; perform authentication and authorization
    checks
  • encapsulate ndb interaction in Services
  • Model classes always receive Model objects or keys as parameters and return Model objects or Model lists
  • Model classes are imported and used in services

One particular thing I encountered is this I have a many to many relationship that I implemented with a mapping Model, something like UserOrganizationMembership, that has the keys of the User and the Organization

now, in my Service, at some point I want to return an object that has the list of the organizations the current user is member of and recursively fetch the companies that are in each organization:

'organizations': [
  {
    'name': 'TEST'
    'companies': [ {company1}, {company2}]
  },
  {
    ...
  }
]

I do this

  def user_organizatios_with_companies(user):
    def fetch_companies(x):
      x.companies = Company.by_organization(x) #NOTICE THIS
      return x

    user_organizations = [ membership.organization.get() for membership in UserOrganizationMembership.by_user(user)]
    return [fetch_companies(x) for x in user_organizations]

in the highlighted line I attach the dynamic property 'companies' to the Organization Model

now if I call this method in my Handler, when I create the dictionary representation to output json, ndb.Model.to_dict() implementation ignores dynamically attached properties.

One solution I tried is this (in my Handler)

xs = Service.user_organizatios_with_companies(u)
organizations = [x.to_dict() for x in xs]
for x in xrange(0,len(xs)):
  organizations[x]['companies'] = xs[x].to_dict()

but I don't like it because I need to know that each organization has a 'companies' property and the code seems a bit complicated and not obvious

another approach is to override ndb.Model.to_dict() isolating dynamically attached properties and providing a dictionary representation, this simplifies my code in the Handler letting me to only call to_dict() on the stuff returned by the Service.

from google.appengine.ext import ndb import util

class BaseModel(ndb.Model):
  created = ndb.DateTimeProperty(auto_now_add = True)
  updated = ndb.DateTimeProperty(auto_now = True)
  # App Engine clock times are always 
  # expressed in coordinated universal time (UTC).s

  def to_dict(self, include=None, exclude=None):
    result = super(BaseModel,self).to_dict(include=include, exclude=exclude)
    result['key'] = self.key.id() #get the key as a string
    # add properties dynamically added to the class
    dynamic_vars = {k:v for (k,v) in vars(self).iteritems() if not k.startswith('_')}
    for prop, val in dynamic_vars.iteritems():
      result[prop] = val.to_dict() if not isinstance(val, list) else [x.to_dict() for x in val]
    return util.model_db_to_object(result)

do you have any recommendation against this approach? any thought will be appreciated!

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
JackNova
  • 3,911
  • 5
  • 31
  • 49

1 Answers1

1

ndb supports dynamic properties through the Expando type.

Instead of defining your models as:

class BaseModel(ndb.Model):

Define it using Expando:

class BaseModel(ndb.Expando):

Now, if you write x.companies = [...], calling _to_dict() will output those companies. Just be careful about when you put() these entities, as any dynamically added properties will also be put into the Datastore.

Patrick Costello
  • 3,616
  • 17
  • 22