2011-03-08 7 views
7

Actualmente, la forma en que funciona nuestra serialización, así como la mayoría de los web frameworks, hay algún tipo de invocación de método que vuelca el modelo en algún tipo de formato. En nuestro caso, tenemos un método to_dict() en cada modelo que construye y devuelve un diccionario de clave-valor con la clave siendo el nombre del campo y el valor siendo la variable de instancia.¿Serializar modelos SQLAlchemy para una API REST respetando el control de acceso?

Todo a lo largo de nuestro código, tenemos fragmentos como el siguiente: json.dumps(**some_model_object.to_dict()) que serializará un some_model_object a json. Recientemente, hemos decidido exponer algunos recursos internos a nuestros usuarios, pero algunos de estos recursos tienen valores de instancia privados específicos que no queremos transmitir durante la serialización si el usuario solicitante no es un súper usuario.

Estoy tratando de llegar a un diseño limpio que permita una serialización más fácil, así como nos permita serializar a un formato distinto de json. Creo que este es un caso de uso bastante bueno para Diseño/Programación Orientada a Aspectos, donde los aspectos respetan los controles de acceso solicitados y serializan el objeto en función de las necesidades del usuario solicitante.

Aquí es algo similar a lo que tengo ahora:

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 

Como se puede ver, esto es menos que ideal porque ahora tengo que acoplar el modelo de base de datos con la solicitud actual. Si bien esto es muy explícito, el acoplamiento de solicitud es un olor a código y estoy tratando de ver cómo hacerlo limpiamente.

Una forma que he pensado en hacerlo es registrar algunos campos en el modelo de esta manera:

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)) 

Entonces, tendría una clase serializador que se une a la solicitud actual en cada llamada WSGI eso tomará el serializador deseado. Por ejemplo:

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) 

A continuación, en mi opinión, en alguna parte, yo sólo lo haría:

from framework import Response 

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

serialize se pondrían en práctica de la siguiente manera:

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) 

Reflexiones sobre la legibilidad y claridad de este enfoque? ¿Es este un enfoque pitónico para el problema?

Gracias de antemano.

+0

No estoy seguro de que nombrar una variable c sea una muy buena idea. :) Y tengo mis reservas sobre algo así como serialization_strategy. Como la clase ya es serializador, es redundante y parece que se está cayendo el nombre del patrón de diseño. ¿Asumo que esto es algo más específico como la estrategia para visitar clases relacionadas? También espero que en realidad no vayas a nombrar tu "marco" de marco. :) –

Respuesta

7

establecer el contrato "serialización" a través de 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__ 
     # ... 

Debe ser sencillo con la integración WSGI. "registrar", JSONSerializer como un objeto, y todo lo que es algún tipo de cosa de Java/Spring, no necesita esa fanfarria. A continuación está mi solución de estilo pylons 1.0, todavía no estoy en pirámide:

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

Un gran consejo.Gracias Michael. –