2011-03-08 1 views
7

Actuellement, la manière dont fonctionne la sérialisation de nos frameworks, ainsi que celle de la plupart des frameworks Web, est qu'il existe un type d'invocation de méthode qui permet de faire passer le modèle dans un certain type de format. Dans notre cas, nous avons une méthode to_dict() sur chaque modèle qui construit et retourne un dictionnaire de valeurs-clés avec la clé étant le nom du champ et la valeur étant la variable d'instance.Sérialisation de modèles SQLAlchemy pour une API REST tout en respectant le contrôle d'accès?

Tout au long de notre code, nous avons des extraits comme suit: json.dumps(**some_model_object.to_dict()) qui va sérialiser un some_model_object à JSON. Récemment, nous avons décidé d'exposer certaines ressources internes à nos utilisateurs, mais certaines de ces ressources ont des valeurs d'instance privées spécifiques que nous ne voulons pas transmettre lors de la sérialisation si l'utilisateur demandeur n'est pas un super utilisateur. J'essaie de trouver un design propre qui permettra une sérialisation plus facile, ainsi que nous permet de sérialiser à un format autre que JSON. Je pense que c'est un très bon cas d'utilisation pour Aspect Oriented Design/Programming, où les aspects respectent les contrôles d'accès demandeurs et sérialisent l'objet en fonction des persmissions de l'utilisateur demandeur.

est ici quelque chose de semblable à ce que j'ai maintenant:

from framework import current_request 


class User(SQLAlchemyDeclarativeModel): 
    __tablename__ = 'users' 

    id = Column(Integer, primary_key=True) 
    first_name = Column(Unicode(255)) 
    last_name = Column(Unicode(255)) 
    private_token = Column(Unicode(4096)) 

    def to_dict(self): 
     serialized = dict((column_name, getattr(self, column_name)) 
          for column_name in self.__table__.c.keys()) 

     # current request might not be bound yet, could be in a unit test etc. 
     if current_request and not current_request.user.is_superuser(): 
      # we explicitly define the allowed items because if we accidentally add 
      # a private variable to the User table, then it might be exposed. 
      allowed = ['id', 'first_name', 'last_name'] 
      serialized = dict((k, v) for k, v in serialized.iteritems() if k in allowed) 

     return serialized 

Comme on peut le voir, c'est idéal parce que moins maintenant je dois coupler le modèle de base de données à la demande actuelle. Bien que ce soit explicitement, le couplage de demande est une odeur de code et j'essaie de voir comment le faire proprement.

Une façon que je l'ai pensé à le faire est d'enregistrer des champs sur le modèle comme ceci:

class User(SQLAlchemyDeclarativeModel): 
    __tablename__ = 'users' 
    __public__ = ['id', 'first_name', 'last_name'] 
    __internal__ = User.__exposed__ + ['private_token'] 

    id = Column(Integer, primary_key=True) 
    first_name = Column(Unicode(255)) 
    last_name = Column(Unicode(255)) 
    private_token = Column(Unicode(4096)) 

Ensuite, j'aurais une classe sérialiseur qui est lié à la demande actuelle sur chaque appel WSGI cela prendra le sérialiseur désiré. Par exemple:

import simplejson 

from framework import JSONSerializer # json serialization strategy 
from framework import serializer 

# assume response format was requested as json 
serializer.register_serializer(JSONSerializer(simplejson.dumps)) 
serializer.bind(current_request) 

Alors à mon avis quelque part, je voudrais juste faire:

from framework import Response 

user = session.query(User).first() 
return Response(code=200, serializer.serialize(user)) 

serialize seraient mis en œuvre comme suit:

def serialize(self, db_model_obj): 
    attributes = '__public__' 
    if self.current_request.user.is_superuser(): 
     attributes = '__private__' 

    payload = dict((c, getattr(db_model_obj, c)) 
        for c in getattr(db_model_obj, attributes)) 

    return self.serialization_strategy.execute(payload) 

Réflexions sur la lisibilité et la clarté de cette approche? Est-ce une approche pythonique du problème?

Merci d'avance.

+0

Je ne suis pas sûr de nommer une variable c est une très bonne idée. :) Et j'ai mes réserves à propos de quelque chose comme serialization_strategy. Comme la classe est déjà sérialisante, elle est redondante et ressemble à un nom de motif. Je suppose que c'est quelque chose de plus spécifique comme la stratégie pour visiter les classes liées? J'espère également que vous n'allez pas nommer votre cadre de travail. :) –

Répondre

7

établir le contrat "sérialisation" via un mixin:

class Serializer(object): 
    __public__ = None 
    "Must be implemented by implementors" 

    __internal__ = None 
    "Must be implemented by implementors" 

    def to_serializable_dict(self): 
     # do stuff with __public__, __internal__ 
     # ... 

rester simple avec l'intégration WSGI. "register", JSONSerializer comme un objet, et tout ce qui est une sorte de chose Java/Spring, n'ont pas besoin de cette fanfare. Ci-dessous est ma solution de pylônes 1.0-style, je ne suis pas encore sur la pyramide:

def my_controller(self): 
    # ... 

    return to_response(request, response, myobject) 


# elsewhere 
def to_response(req, resp, obj): 
    # this would be more robust, look in 
    # req, resp, catch key errors, whatever. 
    # xxx_serialize are just functions. don't need state 
    serializer = { 
     'application/json':json_serialize, 
     'application/xml':xml_serialize, 
     # ... 
    }[req.headers['content-type']] 

    return serializer(obj) 
+0

Excellent conseil.Merci Michael. –