APIs e Microsserviços

14 fev, 2013

Exposição simples RESTO ful- (ish) de APIs de Python – Parte 03

Publicidade

Na parte 02, conectei a API a CherryPy de uma forma muito rudimentar, e desta vez vamos ver como podemos adicionar handlers para recursos de uma maneira menos desajeitada. Eu decidi manter os handlers em um só “nível” – isto é, /sketch/parrot e /sketch serão ambos tratados pelo /sketch handler. Isso acontece porque eu acho que o mesmo sub-recurso está presente frequentemente em vários lugares (e os /props/parrot?) e ter handlers como estes simplifica as coisas e torna a mágica mais legível.

A mágica é assim – é passado um pacote, encontra todos os módulos que possuem pelo menos um dos get/post/put/delete implementados, e os armazena em um dict name->module.

def get_handlers(package):
    handlers = {}
    for member_name, member in
        [module for module in inspect.getmembers(package)
                if inspect.ismodule(module[1])]:
        if [fn for name, fn in inspect.getmembers(member)
               if name in ('get', 'post', 'put', 'delete')]:
            print("Adding handler %s" % member_name)
            handlers[member_name]  = member
    return handlers

Mais tarde, quando obtivermos uma solicitação, nós interpretaremos a primeira parte do caminho como nome de recurso (embora eu tenha montado isso em /api, então se tornou /api/<resource>), e então usaremos essa string para obter o módulo correto, verificaremos se existe um handler para o método específico e o chamaremos caso ele exista.

def requesthandler(handlers, method, resource, *pathargs, **kwargs):
    """Main dispatch for calls to PyRest; no framework specific
    code to be present after this point"""
    if not resource in handlers:
        return Response('404 Not Found', 'No such resource')
    if not  hasattr(handlers[resource], method):
        return Response('405 Method Not Allowed',
                        'Unsupported method for resource')
    return_data = getattr(handlers[resource],
                          method)(*pathargs, **kwargs)
    return Response('200 OK', json.dumps(return_data))

Agora, não tem nada de emocionante acontecendo na API, então a lógica de roteamento só chama hgapi e pressupõe que tudo estará em ordem:

def get(ref=None):
   rev = hgapi.Repo('.')[ref]
   return {
       'node': rev.node,
       'desc': rev.desc
   }

Então, quando nós fizermos o GET /api/changeset/1, o requesthandler será passado: ({‘changeset’: <module>}, ‘get’, ‘changeset’, (‘1’,)). Ele vai procurar o  “changeset” para obter o módulo, depois recuperar e chamar ‘get’ usando getattr e passar o ‘1 ‘. O changeset.get() irá chamar hgapi, colá-lo em um mapa, e o requesthandler vai codificá-lo como JSON e retorná-lo. Como nenhuma das partes envolvidas realmente se importa com o que os argumentos são, você pode muito bem usar /api/changeset/tip ou /api/changeset/default.

Assim, a próxima parte deverá, provavelmente, adicionar alguns testes, mas como eu não estou totalmente decidido sobre a forma como eu quero escrever meus testes, vou avançar com a separação do código em vez disso – a classe PyRest atual e tudo o que tem a ver com CherryPy deve ir em um pacote pyrest.cherrypy ou algo semelhante, as funções requesthandler e get_handler devem permanecer como parte da pyRest adequada, e o pacote de backend provavelmente deve acabar em um pacote de exemplo.

O código está, como sempre, disponível em Bitbucket.

***

Texto original disponível em http://blaag.haard.se/pyRest-part-3–Routing-and-responsibilities/