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/