A long time ago I "wrote a web browser". Those there are some very heavy quotes.
You may imagine me doing air quotes while I write it, maybe?
That's because I didn't really, what I actually did was write UI around
Qt's webkit-based widget. It was a fun project, specially because I did it with
the absurd constraint of staying below 128 lines of code.
And then I did not touch if for six years. But yesterday I did.
PySide is official, so I would recommend using it instead of PyQt
Qt is now on version 5 instead of 4
So, with those new constraints in mind, I ported DeVicenzo to the latest
everything, formatted the code properly using black, and expanded by line limit
to a generous 256.
And Here it is ... it's not realy useful
but it is an example of how expressive the Python/Qt combination can be, even while
being an absurdly bad example nobody should follow (Oh, the lambdas!)
Los widgets en un QTreeWidgetItems no se mueven.
Cuando uno pone widgets adentro de los ítems de un QTreeWidget (que no es muy común,
pero a veces es útil), los widgets no se mueven junto con el ítem.
Solución, usar la opción -graphicssystem raster. Hasta se la puede inyectar en
argv si la plataforma es darwin.
Three years ago, I started a series of long posts called "PyQt by Example". It reached five posts
before I abandoned for a series of reasons that don't matter anymore. That series is coming back
starting next week, rewritten, improved and extended.
It will do so in a new site, and the "old" posts will be retired to an archive page. Why? Well,
the technologies used in some of them are obsolete or don't quite work nowadays. So, the new
versions will be the preferred ones.
And while I am not promising anything, I have enough written to make this something quite longer,
more nicely layouted, more interesting and make it cover more ground. BUT, while doing some
checks on the traffic statistics for the old posts, some things popped out.
This was very popular
About 60% of my site's traffic goes to those five posts. Out of about 1200 posts over
12 years, 60% of the viewers go to the 0.4% of the pages. That is a lot.
It's a long tail
The traffic has not decreased in three years. If anything, it has increased
So, all this means there is a desire for PyQt documentation that is not satisfied. I am not
surprised: PyQt is great, and the recommended book is not free, so there is bound to be a lot
of demand.
And, here's the not-so-rosy bit: I had unobtrusive, relevant, out-of-the-way-but-visible ads
in those pages for more than two years. Of the 70000 unique visitors, not even one clicked on
an ad. Don't worry, I was not expecting to get money out of them (although I would love to
some day collect a $100 check instead of having google hold my money for me ad eternum).
But really? Not even one ad click? In more than two years, thousands of people? I have to
wonder if I just attract cheap people ;-)
Así que acá va un pequeño tutorial acerca de como usar algunas de esas APIs. Lo hice usando Python y PyQt por varios motivos:
Son excelentes herramientas para prototipos
Tienen excelente soporte para las cosas que necesito (DBus, HTTP, OAuth)
Es lo que sé y me gusta. Lo hice un domingo, no lo pienso hacer en PHP
y Gtk.
Dicho eso, no hay nada específico de python o de Qt en este código. Donde hago
un request HTTP usando QtNetwork, podés usar libsoup o lo que fuere.
Vayamos a los bifes entonces. Las piezas más importantes de Ubuntu One, desde el
punto de vista de infraestructura, son Ubuntu SSO Client, que se encarga de
login, registración, etc, y SyncDaemos que maneja la sincronización de archivos.
Para interactuar con ellas, en Linux, ofrecen interfaces DBus. Así que, por ejemplo,
este es un fragmento mostrando como obtener las credenciales de Ubuntu One (esto
normalmente sería parte del __init__ de un objeto):
# Get the session busbus=dbus.SessionBus():::# Get the credentials proxy and interfaceself.creds_proxy=bus.get_object("com.ubuntuone.Credentials","/credentials",follow_name_owner_changes=True)# Connect to signals so you get a call when something# credential-related happensself.creds_iface=dbus.Interface(self.creds_proxy,"com.ubuntuone.CredentialsManagement")self.creds_proxy.connect_to_signal('CredentialsFound',self.creds_found)self.creds_proxy.connect_to_signal('CredentialsNotFound',self.creds_not_found)self.creds_proxy.connect_to_signal('CredentialsError',self.creds_error)# Call for credentialsself._credentials=Noneself.get_credentials()
Tal vez notaste que get_credentials no devuelve las credenciales. Lo que hace es, le
dice a SyncDaemon que las obtenga, y entonces, si/cuando aparecen, se emite una de esas
señales, y uno de los métodos conectados se llama. Esto está bueno porque no tenemos
que preocuparnos de que se nos bloquee la aplicación mientras SyncDaemon está buscando
las credenciales.
¿Y qué hay en esos métodos? ¡No mucho!
defget_credentials(self):# Do we have them already? If not, get'emifnotself._credentials:self.creds_proxy.find_credentials()# Return what we've got, could be Nonereturnself._credentialsdefcreds_found(self,data):# Received credentials, save them.print"creds_found",dataself._credentials=data# Don't worry about get_quota yet ;-)ifnotself._quota_info:self.get_quota()defcreds_not_found(self,data):# No credentials, remove old ones.print"creds_not_found",dataself._credentials=Nonedefcreds_error(self,data):# No credentials, remove old ones.print"creds_error",dataself._credentials=None
Así que básicamente, self._credentials contiene unas credenciales, o None. Felicitaciones, ya
entramos a Ubuntu One.
¡Hagamos algo útil! ¿Que tal preguntar cuánto espacio libre hay en la cuenta? Para eso, no
podemos usar las APIs locales, si no conectarnos a los servers, que son los que saben
si estás excedido de quota o no.
El acceso se controla via OAuth, por lo que para acceder a esa API necesitamos firmar
nuestros pedidos. Aquí se ve como se hace. No es particularmente iluminador, yo no lo
escribí, solamente lo uso:
defsign_uri(self,uri,parameters=None):# Without credentials, return unsigned URLifnotself._credentials:returnuriifisinstance(uri,unicode):uri=bytes(iri2uri(uri))print"uri:",urimethod="GET"credentials=self._credentialsconsumer=oauth.OAuthConsumer(credentials["consumer_key"],credentials["consumer_secret"])token=oauth.OAuthToken(credentials["token"],credentials["token_secret"])ifnotparameters:_,_,_,_,query,_=urlparse(uri)parameters=dict(cgi.parse_qsl(query))request=oauth.OAuthRequest.from_consumer_and_token(http_url=uri,http_method=method,parameters=parameters,oauth_consumer=consumer,token=token)sig_method=oauth.OAuthSignatureMethod_HMAC_SHA1()request.sign_request(sig_method,consumer,token)print"SIGNED:",repr(request.to_url())returnrequest.to_url()
¿Y cómo pedimos el estado de quota? Accediendo al punto de entrada https://one.ubuntu.com/api/quota/
con la autorización adecuada, se obtiene un diccionario JSON con el espacio total y el usado.
Acá hay una muestra de como hacerlo:
# This is on __init__self.nam=QtNetwork.QNetworkAccessManager(self,finished=self.reply_finished):::defget_quota(self):"""Launch quota info request."""uri=self.sign_uri(QUOTA_API)url=QtCore.QUrl()url.setEncodedUrl(uri)self.nam.get(QtNetwork.QNetworkRequest(url))
De nuevo: get_quota no devuelve la quota. Sólo lanza un pedido HTTP a los servers de
Ubuntu One, que (eventualmente) responden con los datos. No querés que tu app se quede
ahí trabada mientras tanto, por eso QNetworkAccessManager va a llamar a self.reply_finished
cuando tenga la respuesta:
¿Qué más queremos? ¿Qué tal notificación cuando cambia el status de SyncDaemon? Por ejemplo,
cuando la sincronización está al día, o cuando te desconecta. De nuevo, esas son señales
DBus a las que uno se conecta en __init__:
self.status_proxy=bus.get_object('com.ubuntuone.SyncDaemon','/status')self.status_iface=dbus.Interface(self.status_proxy,dbus_interface='com.ubuntuone.SyncDaemon.Status')self.status_iface.connect_to_signal('StatusChanged',self.status_changed)# Get the status as of right nowself._last_status=self.process_status(self.status_proxy.current_status())
la función process_status is código aburrido para convertir la info de status de syncdaemon en
una cosa legible como "Sync is up-to-date", así que guardamos eso en self._last_status y
actualizamos el menú.
¿Qué menú? ¡Un menú contextual de un QSystemTrayIcon! Lo que leyeron son las piezas principales
que se necesitan para crear algo útil: una aplicación de SystemTray para Ubuntu One que se puede usar
en KDE, XFCE u Openbox. O, si estás en unity y tenés sni-qt instalado, un app indicator.