XP on Rails Extreme Programming Blog

26Jan/080

Snippets SVN hostato su Assembla

Qualche giorno fa ho spostato i sorgenti di Snippets sul sito di assembla.

Ho trovato questo sito veramente ottimo per chiunque voglia fare partire un nuovo progetto, fornendo allo sviluppatore strumenti come:

* gestione membri del team
* upload files
* Scrum dashboard
* chat
* repository subversion
* trac
* altro ancora

La mia idea e’ di ampliare il progetto a chiunque sia disposto a collaborare (gratis), dando una mano in:

* sviluppo
* design
* testing
* quant’altro sia utile

Chiunque voglia partecipare al progetto può scrivere un commento a questo post, iscriversi ad assembla e diventare un membro del team.

Probabilmente il progetto avrà una licenza di tipo MIT license.

7Jan/080

Gestione delle transazioni

La session di SQLAlchemy permette all’applicazione di dialogare col database, implementando il pattern unit of work.

In fase di inizializzazione, si puo’ definire quale sara’ il comportamento della session durante il ciclo di vita dell’applicazione:

engine = create_engine('mysql://dburl')
Session = scoped_session(sessionmaker(bind=engine, autoflush=False, transactional=False))
session = Session()

Nel mio caso, ho preferito gestire manulamente il flushing degli oggetti e la gestione delle transazioni.

Questo perche’ la mia idea e’ quella di aprire una transazione ogni volta che viene chiamato un metodo di un controller, e di eseguire il commit soltanto quando il controller ritorna l’output del metodo senza alcuna eccezione.

In questo modo, posso gestire nella stessa transazione piu’ chiamate al database, cosa utile in caso di update in piu’ tabelle. Inoltre, posso gestire eccezioni non necessariamente derivanti da un accesso al database, per esempio l’invio di email di notifica fallite ecc.

Per fare questo, non ho fatto altro che definire un apposito trasaction decorator, ed applicarlo ad ogni metodo invocabile dall’applicazione.

Come gia’ detto nel post precedente, tutto il routing dell’applicazione si basa sul metodo default del controller Resource, quindi e’ bastato “decorare” solo per questo metodo:

    @cherrypy.expose
    @utils.transaction
    def default(self, *path, **kw):
    """ body """

Il corpo del del decorator appare cosi’:

def transaction(func):
    """
    This is a decorator for wrapping methods in a db transaction
    """

    def trans_func(*args, **kws):
        session.begin()
        try:
            f = func(*args, **kws)
            session.flush()
            session.commit()
        except Exception, e:
            session.rollback()
            session.close()
            method = getattr(Root(), 'error')
            response = method(str(e))
            return response
        else:
            return f
    return trans_func

Come si puo’ vedere, il decorator inizia una nuova transazione, invoca la funzione (nel nostro caso default), eseguendo il flush e il commit in caso di risultato positivo; il rollback in caso di eccezione.

Tagged as: No Comments
3Jan/080

RESTful controller

Come gia’ detto in precedenza, la mia applicazione sara’ dotata di un’interfaccia RESTful.

Implementare un controller REST usando CherryPy non e’ eccessivamente complesso, sfruttando il metodo default.
Questo metodo viene infatti invocato ogni volta che nell’url si fa riferimento al controller, senza specificare alcun particolare metodo.
A questo punto, e’ relativamente semplice stabilire il method della request, se POST, GET, PUT o DELETE e agire di conseguenza.

Purtroppo, i browser attualmente non supportano metodi di tipo PUT o DELETE, quindi tutto si riduce ai due principali (GET per view, POST per new, edit e delete).

Un aspetto molto importante del controller e’ la gestione delle collections come friendly url: se uno snippet avra’ tanti commenti, bastera’ andare nell’url site/snippets/snippet_id/comments/ per visualizzare tutta la lista dei commenti relativi ad uno snippet e cosi’ via.

Il codice del controller e’ stato ripreso e modificato da RESTful TurboGears

class Resource(object):
    children = {}

    @classmethod
    def get_child(cls, token):
        return cls.children.get(token, None)

    @cherrypy.expose
    @utils.transaction
    def default(self, *path, **kw):
        request = cherrypy.request
        path = list(path)
        resource = None
        http_method = request.method.lower()
       
        #check the http method is supported.
        try:
            method_name = dict(get='get', post='post')[http_method]
        except KeyError:
            raise cherrypy.HTTPError(501)

        if not path: #If the request path is to a collection.  
            if http_method == 'get':        
                #If the method is a get, call the self.index method, which
                #should list the contents of the collection.
                return self.listAll(**kw)
            else:
                #Any other methods get rejected.
                raise cherrypy.HTTPError(501)

        if resource is None:
           
            token = path.pop(0)
            token = token.split(";")
           
            #check for additionals methods on resource
            if len(token)>1:
                if token[1] == 'edit':
                    resource = self.load(token[0])
                    method_name = 'modify'              
                elif token[1] == 'new':
                    resource = self.create(**kw)
                    method_name = 'new'
                else:
                    raise cherrypy.HTTPError(501)
            else:                    
                resource = self.load(token[0])
               
            if resource is None:
                #No resource found?
                raise cherrypy.HTTPError(404)

        #if we have a path, check if the first token matches this
        #classes children.
        if path:
            token = path.pop(0)        
            child = self.get_child(token)
            if child is not None:
                child.parent = resource
                #call down into the child resource.
                return child.default(*path, **kw)
            else:
                raise cherrypy.HTTPError(404)

        #run the requested method, passing it the resource
        method = getattr(self, method_name)
        response = method(resource, **kw)

        return response


    def load(self, token):
        """
        loads and returns a resource identified by the token.
        """

        return None
       
    def get(self, resource, **kw):
        """
        fetches the resource, and returns a representation of the resource.
        """

        raise cherrypy.HTTPError(501)
       
    def listAll(self, **kw):
        """
        returns the representation of a collection of resources.
        """

        raise cherrypy.HTTPError(501)

    def create(self, **kw):
        """
        returns a class or function which will be passed into the self.new
        method.
        """

        raise cherrypy.HTTPError(501)

    def new(self, resource, **kw):
        """
        uses resources factory to create a resource, commit it to the
        database.
        """

        raise cherrypy.HTTPError(501)

    def modify(self, resource, **kw):
        """
        uses kw to modifiy the resource.
        """

        raise cherrypy.HTTPError(501)

Gli altri controller REST, tipo UserController o SnippetController, dovranno solamente estendere Resource e implementare i metodi:

* load
* get
* listAll
* create
* new
* modify

3Jan/081

La scelta del framework PART 2

Nello scorso post stavo analizzando i framework Python per lo sviluppo di web applications.

Devo dire che Python mette a disposizione dello sviluppatore web una vasta gamma di framework, piu’ o meno completi e complessi.

Senza dubbio, veramente interessante e’ Turbogears, megaframework che unisce in un’unica applicazione framework per l’accesso al dato (SQLObject o SQLALchemy); per il routing (CherryPy); per la parte di view (Kid, Genshi). A tutto questo si aggiunge un sistema di autenticazione gia’ incluso nel “pacchetto”, tools di amministrazione e scaffolding, tools per il testing.

Tutte queste caratteristiche mi hanno fatto adottare in principio Turbogears come framework di riferimento, ma poi alcune considerazioni mi hanno fatto rinunciare all’idea:

* Turbogears si trova in questo momento in un periodo di transizione, dalla versione 1.x alla 2. L’upgrade determinera’ l’adozione di SQLAlchemy, CherryPy e Genshi come framework di default, e la retro compatibilita’ non e’ assicurata (i tools di amministrazione funzionano solo con SQLObject e Kid).

* L’utilizzo delle API di Turbogears, ottimamente costruite sopra il framework CherryPy mi sembrano troppo costrittive alla tecnologia, a scapito di possibili espansioni o evoluzioni dei framework sottostanti.

* Un’interfaccia di tipo RESTful non e’ facilmente applicabile.

A questo punto, Django sembrava una valida alternativa. La critica sul web pare approvarlo e consigliarlo come il Rails su Python (ovviamente a grandi linee). In questo caso pero’ mi sono scontrato su quello che ritengo il peggior difetto di Rails, cioe’ essere un framework monolitico. Non avere possibilita’ di scelta mi fa sentire troppo vincolato. Magari, in un fututo, penso di dare un’occhiata piu’ profonda a questo framework.

Scartate le grandi applicazioni, non mi rimaneva altro che iniziare a costruire l’applicazione con i framework che preferivo. La mia scelta e’ dunque ricaduta su:

* SQLAlchemy per la parte di ORM
* CherryPy per il routing
* Genshi per la parte di view
* MochiKit per il javascript

Tagged as: , 1 Comment