Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre pyqt

DeVicenzo 2

A long time ago I "wrote a web browser". Those there are some very heavy quotes. You may imag­ine me do­ing air quotes while I write it, may­be?

That's be­cause I did­n't re­al­ly, what I ac­tu­al­ly did was write UI around Qt's we­bkit-based wid­get. It was a fun pro­jec­t, spe­cial­ly be­cause I did it with the ab­surd con­straint of stay­ing be­low 128 lines of code.

And then I did not touch if for six years. But yes­ter­day I did.

commit 0b29b060ab9962a32e671551b0f035764cbeffaa
Author: Roberto Alsina <ralsina@medallia.com>
Date:   Tue Oct 30 12:32:43 2018 -0300

    Initial PySide2 port

commit 831c30d2c7e6b6b2a0a4d5d362ee7bc36493b975
Author: roberto.alsina@gmail.com <roberto.alsina@gmail.com@1bbba601-83ea-880f-26a2-52609c2bd284>
Date:   Fri Jun 1 15:24:46 2012 +0000

    nicer, smaller margins

Six years is a long time. So, nowa­days:

  • I pre­fer my code to be for­mat­ted bet­ter
  • Python 3 is the thing
  • Py­Side is of­fi­cial, so I would rec­om­mend us­ing it in­stead of PyQt
  • Qt is now on ver­sion 5 in­stead of 4

So, with those new con­straints in mind, I port­ed De­Vi­cen­zo to the lat­est ev­ery­thing, for­mat­ted the code prop­er­ly us­ing black, and ex­pand­ed by line lim­it to a gen­er­ous 256.

And Here it is ... it's not re­aly use­ful but it is an ex­am­ple of how ex­pres­sive the Python/Qt com­bi­na­tion can be, even while be­ing an ab­surd­ly bad ex­am­ple no­body should fol­low (O­h, the lamb­das!)

screenshot

Serbo-Croatian version of PyQt By Example!

A whi­le ago I got an email from An­ja Skr­ba asking me for per­mis­sion to trans­la­te Py­Qt by Exam­ple in­to Ser­bo­-­Croa­tian.

And he­re it is all ni­ce and trans­late­d. Lo­ts of thanks to An­ja for the hard wo­rk!

Qt Mac Tips

Los diá­lo­gos na­ti­vos no an­dan. Usan­do QFi­le­Dia­lo­g.­ge­tE­xis­tin­gDi­rec­to­ry no­ta­mos és­tos sín­to­ma­s:

  • Si no ha­­cés na­­da, el diá­­lo­­­go des­a­pa­­ra­­se por su cuen­­ta en más o me­­nos 20 se­­gun­­do­­s.

  • Des­­pués de usar­­lo una ve­­z, tal vez apa­­re­z­­ca y des­a­pa­­re­z­­ca in­­me­­dia­­ta­­men­­te. O no.

So­lu­ció­n: usar la op­ción Don­tU­seNa­ti­ve­Dia­log op­tio­n.

Los wi­dge­ts en un QTreeWi­dge­tI­te­ms no se mue­ven.

Cuan­do uno po­ne wi­dge­ts aden­tro de los íte­ms de un QTreeWi­dget (que no es muy co­mú­n, pe­ro a ve­ces es úti­l), los wi­dge­ts no se mue­ven jun­to con el íte­m.

Solución, usar la opción -gra­phi­css­ys­tem ras­ter. Hasta se la puede inyectar en argv si la plataforma es darwin.

The Future of PyQt by Example


Th­ree years ago, I started a se­ries of long pos­ts ca­lled "P­y­Qt by Exam­ple". It rea­ched fi­ve pos­ts be­fo­re I aban­do­ned for a se­ries of rea­sons that do­n't ma­tter an­y­mo­re. That se­ries is co­ming ba­ck star­ting next week, rew­ri­tten, im­pro­ved and ex­ten­de­d.

It wi­ll do so in a new si­te, and the "ol­d" pos­ts wi­ll be re­ti­red to an ar­chi­ve pa­ge. Wh­y? We­ll, the te­ch­no­lo­gies us­ed in so­me of them are ob­so­le­te or do­n't qui­te wo­rk no­wa­da­ys. So, the new ver­sions wi­ll be the pre­fe­rred ones.

And whi­le I am not pro­mi­sing an­y­thin­g, I ha­ve enou­gh wri­tten to make this so­me­thing qui­te lon­ge­r, mo­re ni­ce­ly la­youte­d, mo­re in­te­res­ting and make it co­ver mo­re groun­d. BU­T, whi­le doing so­me che­cks on the tra­ffic sta­tis­ti­cs for the old pos­ts, so­me things po­pped ou­t.

This was very popular

About 60% of my si­te's tra­ffic goes to tho­se fi­ve pos­ts. Out of about 1200 pos­ts over 12 year­s, 60% of the viewers go to the 0.4% of the pa­ges. That is a lo­t.

It's a long tail

The tra­ffic has not de­crea­sed in th­ree year­s. If an­y­thin­g, it has in­crea­sed

https://p.twimg.com/Aw0MHhoCAAAXmro.png:large

A long and ta­ll tai­l.

So, all this means the­re is a de­si­re for Py­Qt do­cu­men­ta­tion that is not sa­tis­fie­d. I am not sur­pri­s­e­d: Py­Qt is grea­t, and the re­co­m­men­ded book is not free, so the­re is bound to be a lot of de­man­d.

An­d, he­re's the no­t-­so­-­ro­sy bi­t: I had unob­tru­si­ve, re­le­van­t, ou­t-o­f-­the-wa­y-­bu­t-­vi­si­ble ads in tho­se pa­ges for mo­re than two year­s. Of the 70000 uni­que vi­si­tor­s, not even one cli­cked on an ad. Do­n't wo­rr­y, I was not ex­pec­ting to get mo­ney out of them (al­thou­gh I would lo­ve to so­me day co­llect a $100 che­ck ins­tead of ha­ving google hold my mo­ney for me ad eter­nu­m).

But rea­ll­y? Not even one ad cli­ck? In mo­re than two year­s, thou­san­ds of peo­ple? I ha­ve to won­der if I just attract cheap peo­ple ;-)

APIs de Ubuntu One en Ejemplos (parte 1)

Así que acá va un pe­que­ño tu­to­rial acer­ca de co­mo usar al­gu­nas de esas APIs. Lo hi­ce usan­do Py­thon y Py­Qt por va­rios mo­ti­vo­s:

  • Son ex­­ce­­len­­tes he­­rra­­mien­­tas pa­­ra pro­­­to­­­ti­­pos

  • Tie­­nen ex­­ce­­len­­te so­­­po­r­­te pa­­ra las co­­sas que ne­­ce­­si­­to (DBus, HTTP, OAu­­th)

  • Es lo que sé y me gus­­ta. Lo hi­­ce un do­­­mi­n­­go, no lo pien­­so ha­­cer en PHP y Gtk.

Di­cho eso, no hay na­da es­pe­cí­fi­co de py­thon o de Qt en es­te có­di­go. Don­de ha­go un re­quest HTTP usan­do QtNe­two­rk, po­dés usar lib­soup o lo que fue­re.

Va­ya­mos a los bi­fes en­ton­ce­s. Las pie­zas más im­por­tan­tes de Ubun­tu One, des­de el pun­to de vis­ta de in­fra­es­truc­tu­ra, son Ubun­tu SSO Clien­t, que se en­car­ga de lo­gi­n, re­gis­tra­ció­n, etc, y Syn­c­Dae­mos que ma­ne­ja la sin­cro­ni­za­ción de ar­chi­vo­s.

Pa­ra in­te­rac­tuar con ella­s, en Li­nu­x, ofre­cen in­ter­fa­ces DBus. Así que, por ejem­plo, es­te es un frag­men­to mos­tran­do co­mo ob­te­ner las cre­den­cia­les de Ubun­tu One (es­to nor­mal­men­te se­ría par­te del __i­ni­t__ de un ob­je­to­):

# Get the session bus
bus = dbus.SessionBus()

:
:
:

# Get the credentials proxy and interface
self.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 happens
self.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 credentials
self._credentials = None
self.get_credentials()

Tal vez no­tas­te que ge­t_­cre­den­tials no de­vuel­ve las cre­den­cia­le­s. Lo que ha­ce es, le di­ce a Syn­c­Dae­mon que las ob­ten­ga, y en­ton­ce­s, si/­cuan­do apa­re­cen, se emi­te una de esas se­ña­le­s, y uno de los mé­to­dos co­nec­ta­dos se lla­ma. Es­to es­tá bue­no por­que no te­ne­mos que preo­cu­par­nos de que se nos blo­quee la apli­ca­ción mien­tras Syn­c­Dae­mon es­tá bus­can­do las cre­den­cia­le­s.

¿Y qué hay en esos mé­to­do­s? ¡No mu­cho!

def get_credentials(self):
    # Do we have them already? If not, get'em
    if not self._credentials:
        self.creds_proxy.find_credentials()
    # Return what we've got, could be None
    return self._credentials

def creds_found(self, data):
    # Received credentials, save them.
    print "creds_found", data
    self._credentials = data
    # Don't worry about get_quota yet ;-)
    if not self._quota_info:
        self.get_quota()

def creds_not_found(self, data):
    # No credentials, remove old ones.
    print "creds_not_found", data
    self._credentials = None

def creds_error(self, data):
    # No credentials, remove old ones.
    print "creds_error", data
    self._credentials = None

Así que bá­si­ca­men­te, se­l­f._­cre­den­tials con­tie­ne unas cre­den­cia­le­s, o No­ne. Fe­li­ci­ta­cio­nes, ya en­tra­mos a Ubun­tu One.

¡Ha­ga­mos al­go úti­l! ¿Que tal pre­gun­tar cuán­to es­pa­cio li­bre hay en la cuen­ta? Pa­ra eso, no po­de­mos usar las APIs lo­ca­le­s, si no co­nec­tar­nos a los ser­ver­s, que son los que sa­ben si es­tás ex­ce­di­do de quo­ta o no.

El ac­ce­so se con­tro­la via OAu­th, por lo que pa­ra ac­ce­der a esa API ne­ce­si­ta­mos fir­mar nues­tros pe­di­do­s. Aquí se ve co­mo se ha­ce. No es par­ti­cu­lar­men­te ilu­mi­na­do­r, yo no lo es­cri­bí, so­la­men­te lo uso:

def sign_uri(self, uri, parameters=None):
    # Without credentials, return unsigned URL
    if not self._credentials:
        return uri
    if isinstance(uri, unicode):
        uri = bytes(iri2uri(uri))
    print "uri:", uri
    method = "GET"
    credentials = self._credentials
    consumer = oauth.OAuthConsumer(credentials["consumer_key"],
                                   credentials["consumer_secret"])
    token = oauth.OAuthToken(credentials["token"],
                             credentials["token_secret"])
    if not parameters:
        _, _, _, _, 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())
    return request.to_url()

¿Y có­mo pe­di­mos el es­ta­do de quo­ta? Ac­ce­dien­do al pun­to de en­tra­da http­s://o­ne.u­bun­tu.­co­m/a­pi/­quo­ta/ con la au­to­ri­za­ción ade­cua­da, se ob­tie­ne un dic­cio­na­rio JSON con el es­pa­cio to­tal y el usa­do. Acá hay una mues­tra de co­mo ha­cer­lo:

    # This is on __init__
    self.nam = QtNetwork.QNetworkAccessManager(self,
        finished=self.reply_finished)

:
:
:

def get_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 nue­vo: ge­t_­quo­ta no de­vuel­ve la quo­ta. Só­lo lan­za un pe­di­do HTTP a los ser­vers de Ubun­tu One, que (e­ven­tual­men­te) res­pon­den con los da­to­s. No que­rés que tu app se que­de ahí tra­ba­da mien­tras tan­to, por eso QNe­two­rkAc­ce­ss­Ma­na­ger va a lla­mar a se­l­f.­re­pl­y_­fi­nis­hed cuan­do ten­ga la res­pues­ta:

def reply_finished(self, reply):
    if unicode(reply.url().path()) == u'/api/quota/':
        # Handle quota responses
        self._quota_info = json.loads(unicode(reply.readAll()))
        print "Got quota: ", self._quota_info
        # Again, don't worry about update_menu yet ;-)
        self.update_menu()

¿Qué más que­re­mo­s? ¿Qué tal no­ti­fi­ca­ción cuan­do cam­bia el sta­tus de Syn­c­Dae­mo­n? Por ejem­plo, cuan­do la sin­cro­ni­za­ción es­tá al día, o cuan­do te des­co­nec­ta. De nue­vo, esas son se­ña­les DBus a las que uno se co­nec­ta en __i­ni­t__:

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 now
self._last_status = self.process_status(
    self.status_proxy.current_status())

Y es­ta es sta­tus_­chan­ge­d:

def status_changed(self, status):
    print "New status:", status
    self._last_status = self.process_status(status)
    self.update_menu()

la fun­ción pro­ce­ss_s­ta­tus is có­di­go abu­rri­do pa­ra con­ver­tir la in­fo de sta­tus de syn­c­dae­mon en una co­sa le­gi­ble co­mo "S­ync is up-­to­-­da­te", así que guar­da­mos eso en se­l­f._­las­t_s­ta­tus y ac­tua­li­za­mos el me­nú.

¿Qué me­nú? ¡Un me­nú con­tex­tual de un QS­ys­te­m­Tra­yI­co­n! Lo que le­ye­ron son las pie­zas prin­ci­pa­les que se ne­ce­si­tan pa­ra crear al­go úti­l: una apli­ca­ción de Sys­te­m­Tray pa­ra Ubun­tu One que se pue­de usar en KDE, XFCE u Open­bo­x. O, si es­tás en uni­ty y te­nés sni-­qt ins­ta­la­do, un app in­di­ca­to­r.

http://ubuntuone.com/7iXTbysoMM9PIUS9Ai4TNn

El in­di­ca­dor en ac­ció­n.

El có­di­go fuen­te del ejem­plo com­ple­to es­tá en mi pro­yec­to u1-­to­ys en laun­ch­pad y és­te es el có­di­go fuen­te com­ple­to (ex­cep­to los ico­nos, ba­jen­se el re­po, me­jo­r)

Vi­nien­do pron­to (es­pe­ro­), más apps de ejem­plo, y co­sas co­pa­das que se pue­den ha­cer con nues­tras APIs.


Contents © 2000-2024 Roberto Alsina