Source code for qtr

#!/usr/bin/env python2
# coding: utf-8
#                        VENI, SANCTE SPIRITUS

from PySide import QtCore
from PySide import QtNetwork


[docs]class QClient(QtCore.QObject): """ Esta clase realiza las peticiones a servidores HTTP Ejemplo de uso: >>> from qtr import QClient >>> from PySide import QtGui >>> QtGui.QApplication([]) >>> client = QClient() >>> client.addstep_seturl("http://www.gnu.org") >>> client.addstep_setscraper(scraper) >>> client.do() >>> results = client.getinternaldata() Revise la documentación para descubrir que otros pasos más tiene disponible. """ nextstep = QtCore.Signal() finished = QtCore.Signal() abortexecution = QtCore.Signal(str, int) accumulate = QtCore.Signal() def __init__(self, qnamattr=None, parent=None): super(QClient, self).__init__(parent=parent) # logging.debug("QClient instace started!") self.__stack = [] self._url = QtCore.QUrl() self.__stepindex = None self.__request = QtNetwork.QNetworkRequest(self._url) self.__request.setOriginatingObject(self) self.___reply = None self.__data = QtCore.QByteArray() self.__internaldata = None self.__accumdata = [] self._eventloop = QtCore.QEventLoop() self._finished = False if not parent: self.__qnm = QtNetwork.QNetworkAccessManager(self) else: # get the QNetworkAcessManager from the parent givin # its attribute name self.__qnm = getattr(self.parent(), qnamattr) self.nextstep.connect(self.step) self.__qnm.finished[QtNetwork.QNetworkReply].connect(self._endrequest) self.abortexecution.connect(self.abort) self.accumulate.connect(self._accumulatedata) def __add_step(self, **kwords): """ Método de bajo nivel que añade pasos dentro de la pila de pasos :param dict kwords: debe contener al menos el la llave ``method``,\ puede ser una cadena o un método o función de Python. Actualmente\ la única cadena que tiene significado para este método es ``get``,\ el verbo para hacer peticiones a servidores web. """ if "method" in kwords: method = kwords.get("method") if isinstance(method, str): # Is indicating the name of a predefined method to add if method == "get": kwords["verb"] = "get" # FIXME: Agregar el verbo post kwords["method"] = self._executerequest self.__stack.append(kwords) else: self.abortexecution.emit("No 'method' argument was giving!", 1)
[docs] def _seturl(self, **kwords): """ Método privado que estable la URL :param dict kwords: debe contener las llaves ``fromscraped``,\ ``url`` y ``query``. :keyword bool fromscraped: indica si la URL a establecer se\ encuentra en el almacenamiento interno\ :attr:`qtr.QClient.__internaldata`. :keyword str url: La URL a usar. :keyword dict query: los *queries* a establecer en la URL. """ fromscraped = kwords.get("fromscraped") if fromscraped: url = self.__internaldata else: url = kwords.get("url") query = kwords.get("query") if not url: self.abortexecution.emit("Setting empty urls is bad idea!", 1) return else: self._url.setUrl(url) if not self._url.isValid(): self.abortexecution.emit( "This url \"{}\" is not valid!".format(url), 1) return # Usually we would want to # overwrite the queries in the URL if query: for key in query.keys(): self._url.removeQueryItem(key) for key, value in query.iteritems(): self._url.addQueryItem(key, value)
[docs] def _executerequest(self, **kwords): """ Ejecuta la petición al servidor web. :param dict kwords: debe contener la llave ``verb``\ que indica el verbo HTTP que se usara para la\ petición. Este método usa :attr:`qtr.QClient.__request`\ previamente modificado para realizar la petición. Si la instancia QClient es hija de un objeto QtObject, establece como origen de la petición a sí misma. """ # setting the URL self.__request.setUrl(self._url) # Sets the object origin of the request if self.parent(): self.__request.setOriginatingObject(self) # doing the request if "verb" not in kwords: self.abortexecution.emit( "there is no HTTP verb in the arguments", 1) return else: verb = getattr(self.__qnm, kwords.get("verb")) verb(self.__request)
def __execute_step(self): """ Ejecuta el paso actual directamente de la pila cada paso debe contener la llave ``method``. Este método busca también por los siguientes llaves: :keyword bool block: indica si este paso bloquea el flujo de ejecución\ hasta que algo le indique terminar. ``False`` por defecto. :keyword bool store: indica que lo devuelto por este paso, debe ser\ almacenado en ``__internaldata``. ``False`` por defecto. :keyword bool isScrap: indica que este paso es un raspador. ``False``\ por defecto. :keyword tuple args: indica que el paso contiene una llave con\ una tupla dentro. ``tuple()`` por defecto. :keyword bool passargs: indica que hay que pasar ``args`` a\ ``method``. ``False`` por defecto. :keyword bool passkwords: indica que hay que pasar ``step`` a\ ``method``. ``True`` por defecto. :keyword bool passselfattribute: indica que hay que pasar un atributo\ de la instancia del objeto ``QClient``. ``False`` por defecto. :keyword str selfattributename: indica el nombre del atributo de la\ instancia del objeto ``QClient``. ``None`` por defecto. :keyword classmethod selfattribute: contiene el atributo tomado de la\ instancia del objeto ``QClient``. No existe si\ ``passselfattribute`` es ``False`` :keyword bool selfattributeismethod: indica que el atributo no es\ un atributo, sino un método que retorna algo. ``False`` por defecto. """ # logging.debug("executing step {}...".format(self.__stepindex)) # get the step from the stack if self.__stepindex + 1 > len(self.__stack): return True index = self.__stepindex step = self.__stack[index] # every step is just a key=value dictionary method = step.get("method") # required, the method to execute block = step.get("block", False) # block in this step? store = step.get("store", False) # store what method has returned? # should we scrap what is inside self.__data? stepisscrap = step.get("isScrap", False) args = step.get("args", tuple()) # get the tuple, if any. # should we pass the tuple to the method? passargs = step.get("passargs", False) # should we pass the dict to the method? passkwords = step.get("passkwords", True) # should we pass an attribute of this object passselfattribute = step.get("passselfattribute", False) # what's the name of that attribute? selfattributename = step.get("selfattribute", None) # the attribute itself if passselfattribute: selfattribute = getattr(self, selfattributename) # this attribute is not an attribute but a method! selfattributeismethod = step.get("selfattributeibuteismethod", False) # execute it if passkwords: tmpinternaldata = method(**step) elif passargs: tmpinternaldata = method(*args) elif passargs and passkwords: tmpinternaldata = method(*args, **step) elif passselfattribute: if selfattributeismethod: tmpinternaldata = method(selfattribute()) else: tmpinternaldata = method(selfattribute) else: if stepisscrap: tmpinternaldata = method(self.__data.data()) else: tmpinternaldata = method() # should we store the results? if store or stepisscrap: self.__internaldata = tmpinternaldata # blocks on this step # until something else makes exit the loop if block: self._eventloop.exec_() # set the next step self.__stepindex += 1 # logging.debug(self.__stepindex) @QtCore.Slot()
[docs] def getinternaldata(self, accumulated=False): """ Retorna los datos internos. :param bool accumulated: si es ``True``, retorna los datos\ acumulados internos en vez de los internos. :return: una cadena, o una lista. """ if accumulated: return self.__accumdata else: return self.__internaldata
@QtCore.Slot(str, int)
[docs] def abort(self, message, exception): """ Aborta la ejecución de la secuencia de ordenes. """ self._finished = True self.finished.emit() QtCore.QTimer.singleShot(5 * 1000, self._eventloop.quit) if exception == 1: raise RuntimeError(message)
@QtCore.Slot(QtNetwork.QNetworkReply)
[docs] def _endrequest(self, reply): """ Almacena los datos devueltos por el servidor web. Este método hace que el loop de eventos se rompa y el flujo de la ejecución pueda continuar. """ if self.parent() is None or\ (self.parent() and reply.request().originatingObject() == self): self.__data = reply.readAll() self.__reply = reply if self._eventloop.isRunning(): # exits the eventloop, if any self._eventloop.quit()
@QtCore.Slot()
[docs] def _accumulatedata(self): """ Acumula los datos para guardarlos de forma segura y evitar que sean sobre-escritos. """ self.__accumdata.extend(self.__internaldata)
[docs] def do(self): """ Inicia la secuencia de ordenes pre-programadas. """ # logging.debug("Starting!") self.nextstep.emit()
[docs] def step(self): """ Emite una señal para que se ejecute un paso. Este método es llamado de forma recursiva hasta que se alcanza el final de la pila. """ if self.__stepindex is None: self.__stepindex = 0 self._finished = self.__execute_step() if not self._finished: self.nextstep.emit() else: self.finished.emit()
[docs] def addstep_seturl(self, url, query={}): """ Establecerá la URL sobre la cual realizar la petición web. Se debe usar este método antes que :meth:`qtr.QClient.addstep_setrequest`. """ if url is None or url == "": fromscraped = True else: fromscraped = False self.__add_step(method=self.__data.clear, passkwords=False) self.__add_step(method=self._seturl, url=url, fromscraped=fromscraped, query=query)
[docs] def addstep_setrequest(self, verb): """ Establecerá el verbo de la petición web """ self.__add_step(method=verb, block=True)
[docs] def addstep_setscraper(self, method): """ Establece el método que raspara el contenido web """ self.__add_step(method=method, isScrap=True, store=True, passkwords=False)
[docs] def addstep_setscraperpageurls(self, method, perpagescraper): """ Establece el raspador que extraerá la lista de URLs en una lista y la función que repasara cada enlace raspando su contenido. :param classmethod method: el raspador de URLs. :param classmethod perpagescraper: el raspador que raspa el\ contenido de cada URL """ self.__add_step(method=method, isScrap=True, store=True, passkwords=False) self.__add_step(method=self._setpagescraper, scraper=perpagescraper)
[docs] def _setpagescraper(self, **kwords): """ Establece el raspador que raspara el contenido de cada enlace Este método es usado por :meth:`qtr.QClient.addstep_setscraperpageurls` y no hay razón para usarlo directamente. """ scraper = kwords.get("scraper") for link in self.__internaldata: # for every link to a page scraped by addstep_setscraperpageurls self.addstep_seturl(url=link) self.addstep_setrequest("get") self.addstep_setscraper(scraper) self.__add_step(method=self.accumulate.emit, passkwords=False)
[docs] def status(self): """ Retorna el código HTTP de la petición realizada :return: código HTTP :rtype: int """ return self.__reply.attribute( QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
[docs] def error(self): """ Retorna el código de error de la petición realizada :return: código de error :rtype: int """ return self.__reply.error()
[docs] def geturl(self): """ Retorna la URL actual :return: url :rtype: unicode """ self._url.toString()