Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre programming (publicaciones antiguas, página 72)

¡Unicode en Python es Divertido!

>>> 'á'.decode('utf8')
u'\xe1'

Sin em­bar­go, hay una tram­pa. Te­nés que es­tar ab­so­lu­ta­men­te se­gu­ro que la co­sa que es­tás de­co­dean­do es un string de by­tes, y no un ob­je­to uni­co­de. Por­que los ob­je­tos uni­co­de tie­nen un mé­to­do de­co­de pe­ro es to­tal­men­te inú­ti­l, y su úni­co pro­pó­si­to en la vi­da es cau­sar es­te error pe­cu­lia­r:

>>> u'á'.decode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1'
in position 0: ordinal not in range(128)

¿Por­qué es pe­cu­lia­r? Por­que es un error de En­co­de. Cau­sa­do por lla­mar a de­co­de. Lo que pa­sa es que en los ob­je­tos uni­co­de, de­co­de, en la prác­ti­ca es al­go así:

def decode(self, encoding):
    return self.encode('ascii').decode(encoding)

El usua­rio quie­re un ob­je­to uni­co­de. Él tie­ne un ob­je­to uni­cde. Por de­fi­ni­ció­n, no exis­te un co­sa que sea "de­co­dear co­mo utf-8 un ob­je­to uni­co­de". No tie­ne sen­ti­do. Es co­mo pei­nar un pes­ca­do, o es­ca­lar una la­gu­na.

¡Lo que debería devolver es self! Además es suamente molesto que la única manera de evitar el error es chequear el tipo del objeto, lo que es activamente antipitónico.

Aún me­jo­r, no ten­ga­mos un mé­to­do de­co­de en ob­je­tos uni­co­de, que creo es la si­tua­ción en py­thon 3, pe­ro nun­ca lo va a ser en py­thon 2.

Así que aho­ra ya sa­be­n, y bue­na suer­te.

Como se hace

Lo hi­ce por va­rias ra­zo­nes:

  1. Po­r­­que un co­­­men­­ta­­rio pa­­re­­cía su­­ge­­ri­r­­lo ;-)

  2. Po­r­­que es al­­go li­n­­do de ha­­ce­­r. Me gus­­ta reS­­t, me gus­­ta­­ría que lo use más gen­­te, y mo­s­­trar lo fá­­cil que es, es­­tá bue­­no.

  3. Es el ca­­mino del so­­­ftwa­­re li­­bre. Les doy la ma­­ne­­ra pre­­fe­­ri­­da de mo­­­di­­fi­­car mis po­s­­ts.

  4. Era una pa­­va­­da ha­­ce­r­­lo.

Si ven al­gu­na co­sa que fal­te en el blo­g, o les pa­re­ce que quer­da­ría bue­no agre­gar al­go, co­men­ten y ve­mo­s.

Nikola está Cerca

  • Im­­ple­­men­­té tags (i­n­­clu­­yen­­do fee­­ds pa­­ra ca­­da ta­­g).

  • Si­m­­pli­­fi­­qué los te­m­­pla­­tes.

  • Se­­pa­­ré có­­­di­­go y co­n­­fi­­gu­­ra­­ció­­n.

El úl­ti­mo fué el más com­pli­ca­do. Y pa­ra que se vea co­mo es, és­ta es la con­fi­gu­ra­ción com­ple­ta, ex­cep­to pe­da­ci­tos de HT­ML que no va­len la pe­na ve­r, co­mo el có­di­go de google cus­tom sear­ch. ¡Es­pe­ro que sea cla­ro!

# -*- coding: utf-8 -*-

# post_pages contains (wildcard, destination, template) tuples.
#
# The wildcard is used to generate a list of reSt source files (whatever/thing.txt)
# That fragment must have an associated metadata file (whatever/thing.meta),
# and opcionally translated files (example for spanish, with code "es"):
#     whatever/thing.txt.es and whatever/thing.meta.es
#
# From those files, a set of HTML fragment files will be generated:
# whatever/thing.html (and maybe whatever/thing.html.es)
#
# These files are combinated with the template to produce rendered
# pages, which will be placed at
# output / TRANSLATIONS[lang] / destination / pagename.html
#
# where "pagename" is specified in the metadata file.
#

post_pages = (
    ("posts/*.txt", "weblog/posts", "post.tmpl"),
    ("stories/*.txt", "stories", "post.tmpl"),
)

# What is the default language?

DEFAULT_LANG = "en"

# What languages do you have?
# If a specific post is not translated to a language, then the version
# in the default language will be shown instead.
# The format is {"translationcode" : "path/to/translation" }
# the path will be used as a prefix for the generated pages location

TRANSLATIONS = {
    "en": "",
    "es": "tr/es",
    }

# Data about this site
BLOG_TITLE = "Lateral Opinion"
BLOG_URL = "//ralsina.me"
BLOG_EMAIL = "ralsina@kde.org"
BLOG_DESCRIPTION = "I write free software. I have an opinion on almost "\
    "everything. I write quickly. A weblog was inevitable."

# Paths for different autogenerated bits. These are combined with the translation
# paths.

# Final locations are:
# output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
# output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
# output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
TAG_PATH = "categories"
# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
INDEX_PATH = "weblog"
# Final locations for the archives are:
# output / TRANSLATION[lang] / ARCHIVE_PATH / archive.html
# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
ARCHIVE_PATH = "weblog"
# Final locations are:
# output / TRANSLATION[lang] / RSS_PATH / rss.xml
RSS_PATH = "weblog"

# A HTML fragment describing the license, for the sidebar.
LICENSE = """
    <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/ar/">
    <img alt="Creative Commons License" style="border-width:0; margin-bottom:12px;"
    src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>
"""

# A search form to search this site, for the sidebar. Has to be a <li>
# for the default template (base.tmpl).
SEARCH_FORM = """
    <!-- google custom search -->
    <!-- End of google custom search -->
"""

# Google analytics or whatever else you use. Added to the bottom of <body>
# in the default template (base.tmpl).
ANALYTICS = """
        <!-- Start of StatCounter Code -->
        <!-- End of StatCounter Code -->
        <!-- Start of Google Analytics -->
        <!-- End of Google Analytics -->
"""

# Put in global_context things you want available on all your templates.
# It can be anything, data, functions, modules, etc.
GLOBAL_CONTEXT = {
    'analytics': ANALYTICS,
    'blog_title': BLOG_TITLE,
    'blog_url': BLOG_URL,
    'translations': TRANSLATIONS,
    'license': LICENSE,
    'search_form': SEARCH_FORM,
    # Locale-dependent links
    'archives_link': {
        'es': '<a href="/tr/es/weblog/archive.html">Archivo</a>',
        'en': '<a href="/weblog/archive.html">Archives</a>',
        },
    'tags_link': {
        'es': '<a href="/tr/es/categories/index.html">Tags</a>',
        'en': '<a href="/categories/index.html">Tags</a>',
        },
    }

execfile("nikola/nikola.py")

Bienvenidos a Nikola

Así que, ahí va una ex­pli­ca­ció­n:

  • Ca­m­­bié to­­­do el so­­­ftwa­­re y los te­m­­pla­­tes pa­­ra es­­te blo­­­g.

  • Sí, es­­tá to­­­do sin te­r­­mi­­na­­r.

  • El nue­­vo so­­­ftwa­­re se lla­­ma Niko­­­la.

  • Sí, creo que es­­tá bue­­no.

¿Porqué cambiar?

¿En se­rio? EL ge­ne­ra­dor an­te­rior (Son of Bar­tle­Blo­g) no es­ta­ba en buen es­ta­do. Los ar­chi­vos cu­brían só­lo 2000-2010, el li­nk "pos­ts an­te­rio­res" era una qui­nie­la, y a la ver­sión en es­pa­ñol le fal­ta­ban pá­gi­nas en­te­ra­s.

¿Qué es Nikola?

Niko­la es un ge­ne­ra­dor de si­tios es­tá­ti­co­s. Una co­sa in­te­re­san­te de es­te si­tio es que es, y siem­pre ha sio, pu­ro HT­M­L. Ca­da co­sa "di­ná­mi­ca" que veas acá, co­mo ser co­men­ta­rio­s, es un ser­vi­cio de ter­ce­ro­s. Es­te si­tio es na­da más que unas car­pe­tas lle­nas de HT­M­L.

¿Cómo funciona Nikola?

Niko­la to­ma una car­pe­ta lle­na de ar­chi­vos txt es­cri­tos en res­truc­tu­red text, y ge­ne­ra frag­men­tos de HT­M­L.

Esos frag­men­to­s, con un po­co de me­ta­da­ta (tí­tu­lo, tags, nom­bre del ar­chi­vo de sali­da, li­nks a fuen­tes ex­ter­na­s) y unos Mako Tem­pla­tes crean pá­gi­nas HT­M­L.

Esas pá­gi­nas usan boots­trap pa­ra no ser una re­ve­ren­da ba­zo­fia (nun­ca di­je ser un di­se­ña­do­r).

Pa­ra ase­gu­rar­me de no ha­cer tra­ba­jo inú­ti­l, doit se en­car­ga de re­crear lo mí­ni­mo in­dis­pen­sa­ble.

¿Por qué no usar <esto>?

Por­que, por di­ver­sas ra­zo­nes, que­ría man­te­ner exac­ta­men­te las URLs que siem­pre tu­ve.

  • Si mue­­vo una pá­­gi­­na, man­­te­­ner aso­­­cia­­dos los co­­­men­­ta­­rios de Dis­­qus es un ba­r­­do.

  • Pue­­de ha­­ber gen­­te que te­­ga book­­ma­­rks.

Ade­más quie­ro:

  • Mako te­m­­pla­­tes (po­r­­que me gus­­tan)

  • Res­­tru­c­­tu­­red text (po­r­­que ten­­go más de 1000 po­s­­ts es­­cri­­tos en eso)

  • Py­­thon (pa­­ra ha­­ckea­r­­lo)

  • Fá­­cil de ha­­ckear (Niko­­­la es­­tá por las 600 LO­­­C, y es ca­­si fea­­tu­­re co­m­­ple­­te)

  • So­­­po­r­­te de blogs mu­l­­ti­­li­n­­gües co­­­mo es­­te.

Y por su­pues­to:

  • So­­­na­­ba co­­­mo un pro­­­ye­c­­to co­r­­to y di­­ve­r­­ti­­do. Te­­nía la so­s­­pe­­cha que con un po­­­co de pe­­ga­­men­­to las he­­rra­­mien­­tas exis­­ten­­tes ha­­cían el 90% del tra­­ba­­jo. Pa­­re­­ce que te­­nía ra­­zó­­n, ya que lo pu­­de es­­cri­­bir en unos po­­­cos dia­s.

¿Lo vas a mantener?

Y, lo es­toy usan­do­...

¿Es útil para alguien más?

No por aho­ra, por­que ha­ce mon­to­nes de su­po­si­cio­nes vá­li­das só­lo pa­ra es­te si­tio. hay que lim­piar­lo un po­co an­tes de que que­de lin­do.

¿Lo pueden usar los demás?

Pronti­to, es­pe­ro.

¿Faltan features?

No tie­ne tags, y al­gu­na co­si­ta me­no­r.

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-2023 Roberto Alsina