Skip to main content

Ralsina.Me — Roberto Alsina's website

Posts about python (old posts, page 68)

Charla: aplicaciones extensibles con PyQt

Span­ish on­ly, since it's about a video in span­ish ;-)

Acá es­tá, gra­cias a la gente de Junín, un video de mi char­la "Apli­ca­ciones ex­ten­si­bles us­an­do PyQt", en la que in­ten­to mostrar co­mo de­sar­rol­lar una apli­cación con PyQt y yap­sy.

No es una char­la con la que es­té muy con­tento. La otra sal­ió mejor, pero no se filmó, así que quedará so­lo en la memo­ria de los cu­a­tro gatos lo­cos que es­tábamos ahí ;-)

El resto de las char­las: http://un­no­ba.blip.tv/

Reason #219 why you should learn english if you are a programmer

Here's pret­ty much the on­ly place where you can buy Mark Sum­mer­field­'s "Python 3" book in Ar­genti­na: Cúspi­de.

It costs $372.50 in pe­sos which is about $94 in dol­lars.

Oh, and you have to go to a book store to pick it up, or add ship­ping.

How much does it cost to buy that book in eng­land and have it shipped to your door? $16. That's a whoop­ing 17% of the lo­cal cost.

And no, it won't pay im­port tax­es, be­cause books are ex­emp­t.

And did I men­tion that the eng­lish ver­sion came out a year ear­lier?

So, if you don't learn en­glish, you pay al­most 6 times for the book, and wait a year.

Any pro­gram­mer that does­n't know enough eng­lish is a third class cit­i­zen.

Another reason to attend PyCon Argentina

You can get a free Is­sue of PET: Python En­tre To­dos our python mag­a­zine.

400 will be print­ed thanks to BBLUG, Py­ConAr and Py­Day Rafaela.

I hope I can get a few for my­self, be­ing the mag­a­zine re­spon­si­ble ed­i­tor and ev­ery­thing :-)

And if you won't be there: it's CC-by-nc-sa so you can print it too!

Rst2pdf 0.16 is out!

Fi­nal­ly, a new re­lease of rst2pdf!

You can get it at its site: http://rst2pdf.­google­code.­com

rst2pdf is a tool to con­vert re­struc­tured text (a light, cool markup lan­guage) to PDF us­ing re­port­lab in­stead of La­TeX.

It has been used for many things, from book­s, to mag­a­zi­nes, to brochures, to man­u­al­s, to web­sites and has lots of fea­tures:

  • Font em­bed­d­ing (T­TF or Type1 fonts)

  • Cas­­cad­ing Stylesheets

  • Ex­treme­­ly flex­i­ble plug­in ar­chi­tec­­ture (y­ou can do things like ren­der the head­­ings from ar­bi­­trary SVG files!)

  • Sphinx in­­te­­gra­­tion.

  • Con­­fig­urable page lay­outs

  • Cus­­tom cov­­er pages via tem­­plates

  • And much, much more...

The big­gest change in 0.16 is sure­ly the im­proved sup­port for Sphinx 1.0.x so if you are us­ing Sphinx, you re­al­ly want this ver­sion.

Al­so, it has a ton of bug­fix­es, and a few mi­nor but use­ful new fea­tures.

Here's the whole changel­og if you don't be­lieve me:

  • Fixed Is­­sue 343: Plugged mem­o­ry leak in the RSON pars­er.

  • Fix for Is­­sue 287: there is still a cor­n­er case if you have two sec­­tions with the same ti­tle, at the same lev­­el, in the same page, in dif­fer­­ent files where the links will break.

  • Fixed Is­­sue 367: ger­­man-lo­­cal­ized dates are MM. DD. YYYY so when used in sphinx's tem­­plate cov­­er they ap­­peared weird, like a list item. Fixed with a mi­nor work­around in the tem­­plate.

  • Fixed Is­­sue 366: links to "#" make no sense on a PDF file

  • Made de­f­i­ni­­tions from de­f­i­ni­­tion lists more sty­lable.

  • Moved de­f­i­ni­­tion lists to Split­­Ta­bles, so you can have very long de­f­i­ni­­tion­s.

  • Fixed Is­­sue 318: Im­­ple­­men­t­ed Do­­main spe­­cif­ic in­­dex­es for Sphinx 1.0.x

  • Fixed In­­dex links when us­ing Sphinx/pdf­builder.

  • Fixed Is­­sue 360: Set lit­er­al.­­word­Wrap to None by de­­fault so it does­n't in­­her­it word­Wrap CJK when you use the oth­­er­­wise cor­rect ja­­pa­­nese set­t­ings. In any case, lit­er­al blocks are not sup­­posed to wrap at al­l.

  • Switched pdf­builder to use Split­­Ta­bles by de­­fault (it made no sense not to do it)

  • Fixed Is­­sue 365: some TTF fonts don't val­i­­date but they work any­way.

  • Set a valid de­­fault baseurl for Sphinx (makes it much faster!)

  • New fea­­ture: --use-num­bered-links to show sec­­tion num­bers in links to sec­­tion­s, like "See sec­­tion 2.3 Ter­mi­­na­­tion"

  • Added stylesheets for land­s­cape pa­per sizes (i.e: a4-­­land­s­cape.style)

  • Fixed Is­­sue 364: Some op­­tions not re­spec­t­ed when passed in per-­­doc op­­tions in sphinx.

  • Fixed Is­­sue 361: mul­ti­­ple line­breaks in line blocks were col­lapsed.

  • Fixed Is­­sue 363: strange char­ac­ters in some cas­es in math di­rec­­tive.

  • Fixed Is­­sue 362: Smarter au­­to-en­­clos­ing of equa­­tions in $...$

  • Fixed Is­­sue 358: --re­al--­­foot­notes de­­faults to False, but help text in­­di­­cates de­­fault is True

  • Fixed Is­­sue 359: Wrong --­­fit-back­­­ground-­­mode help string

  • Fixed Is­­sue 356: mis­s­ing cells if a cell spawns rows and col­umn­s.

  • Fixed Is­­sue 349: Work cor­rec­t­­ly with lan­guages that are avail­able in form aa_bb and not aa (ex­am­­ple: zh_c­n)

  • Fixed Is­­sue 345: give file/­­line in­­­fo when there is an er­ror in a raw PDF di­rec­­tive.

  • Fixed Is­­sue 336: JPEG im­ages should work even with­­out PIL (but give a warn­ing be­­cause sizes will prob­a­bly be wrong)

  • Fixed Is­­sue 351: foot­note/c­i­­ta­­tion re­f­er­ences were gen­er­at­ed in­­­cor­rec­t­­ly, which caused prob­lems if there was a ci­­ta­­tion with the same text as a head­­ing.

  • Fixed Is­­sue 353: bet­ter han­dling of graphviz, so that it works with­­out vec­­tor­pdf but gives a warn­ing about it.

  • Fixed Is­­sue 354: make todo_n­ode from sphinx cus­­tom­iz­a­ble.

  • Fixed bug where nest­ed lists broke page lay­out if the page was smal­l.

  • Smarter --in­­line-links op­­tion

  • New ex­ten­­sion: fan­­cyti­tles, see //ral­si­­na.me/we­blog/­­post­s/B­B906.html

  • New fea­­ture: tab-width op­­tion in code-block di­rec­­tive (de­­faults to 8).

  • Fixed Is­­sue 340: end­notes/­­foot­notes were not styled.

  • Fixed Is­­sue 339: class names us­ing _ were not us­able.

  • Fixed Is­­sue 335: ug­­ly crash when us­ing im­ages in some spe­­cif­ic places (looks like a re­­port­lab bug)

  • Fixed Is­­sue 329: make the fig­ure align­­men­t/­­class at­tributes work more like La­­TeX than HT­M­L.

  • Fixed Is­­sue 328: list item styles were be­ing ig­nored.

  • Fixed Is­­sue 186: new --use-float­ing-im­ages makes im­ages with :align: set work like in HT­M­L, with the next flow­able flow­ing be­­side it.

  • Fixed Is­­sue 307: head­­er/­­foot­er from stylesheet now sup­­ports in­­­line rest markup and sub­­sti­­tu­­tions de­fined in the main doc­u­­men­t.

  • New pdf_­­toc_depth op­­tion for Sphinx/pdf­builder

  • New pdf_use_­­toc op­­tion for Sphinx/pdf­builder

  • Fixed Is­­sue 308: com­­pat­i­­bil­i­­ty with re­­port­lab from SVN

  • Fixed Is­­sue 323: er­rors in the con­­fig.sam­­ple made it work weird.

  • Fixed Is­­sue 322: Im­age sub­­sti­­tu­­tions did­n't work in doc­u­­ment ti­­tle.

  • Im­­ple­­men­t­ed Is­­sue 321: un­der­­line and strikethrough avail­able in stylesheet.

  • Fixed Is­­sue 317: Ug­­ly er­ror mes­sage when file does not ex­ist

Making your app modular: Yapsy

That a plug­in ar­chi­tec­ture for a com­plex app is a good idea is one of those things that most peo­ple kin­da agree on. One thing we don't quite agree is how the heck are we go­ing to make out app mod­u­lar?

One way to do it (if you are cod­ing python) is us­ing Yap­sy.

Yap­sy is awe­some. Al­so, yap­sy is a bit un­der­doc­u­ment­ed. Let's see if this post fix­es that a bit and leaves just the awe­some.

Up­date: I had not seen the new Yap­sy doc­s, re­leased a few days ago. They are much bet­ter than what was there be­fore :-)

Here's the gen­er­al idea be­hind yap­sy:

  • You cre­ate a Plug­in Man­ag­er that can find and load plug­ins from a list of places (for ex­am­­ple, from ["/us­r/share/ap­p­­name/­­plu­g­in­s", "~/.ap­p­­name/­­plu­g­in­s"]).

  • A plug­in cat­e­­go­ry is a class.

  • There is a map­ping be­tween cat­e­­go­ry names and cat­e­­go­ry class­es.

  • A plug­in is a mod­­ule and a meta­­da­­ta file. The mod­­ule de­fines a class that in­­her­its from a cat­e­­go­ry class, and be­­longs to that cat­e­­go­ry.

    The meta­­da­­ta file has stuff like the plug­in's name, de­scrip­­tion, URL, ver­­sion, etc.

One of the great things about Yap­sy is that it does­n't spec­i­fy too much. A plug­in will be just a python ob­jec­t, you can put what­ev­er you want there, or you can nar­row it down by spec­i­fy­ing the cat­e­go­ry class.

In fac­t, the way I have been do­ing the cat­e­go­ry class­es is:

  • Start with an em­p­­ty class

  • Im­­ple­­ment two plug­ins of that cat­e­­go­ry

  • If there is a chunk that's much alike in both, move it in­­­to the cat­e­­go­ry class.

But trust me, this will all be clear­er with an ex­am­ple :-)

I will be do­ing it with a graph­i­cal PyQt ap­p, but Yap­sy works just as well for head­less of CLI app­s.

Let's start with a sim­ple ap­p: an HTML ed­i­tor with a pre­view wid­get.

//ralsina.me/static/yapsy/editor1.jpeg

A sim­ple ed­i­tor with pre­view

Here's the code for the ap­p, which is re­al­ly sim­ple (it does­n't save or do any­thing, re­al­ly, it's just an ex­am­ple):

ed­i­tor1.py

from PyQt4 import QtCore, QtGui, QtWebKit
import os, sys

class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.editor = QtGui.QPlainTextEdit()
        self.preview = QtWebKit.QWebView()
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.preview)
        self.editor.textChanged.connect(self.updatePreview)
        self.setLayout(self.layout)

    def updatePreview(self):
        self.preview.setHtml(self.editor.toPlainText())

def main():
    # Again, this is boilerplate, it's going to be the same on
    # almost every app you write
    app = QtGui.QApplication(sys.argv)
    window=Main()
    window.show()
    # It's exec_ because exec is a reserved word in Python
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Note

From now on list­ings will not in­clude the main func­tion, be­cause it nev­er changes.

But this ap­pli­ca­tion has an ob­vi­ous lim­it: you have to type HTML in it. Why not type python code in it and have it con­vert to HTML for dis­play? Or Wi­ki markup, or re­struc­tured tex­t?

You could, in prin­ci­ple, just im­ple­ment all those mod­es, but then you are as­sum­ing the re­spon­s­abil­i­ty of sup­port­ing ev­ery thing-that-­can-be-­turned-in­to-HTM­L. Your app would be a mono­lith. That's where yap­sy en­ters the scene.

So, let's cre­ate a plug­in cat­e­go­ry, called "For­mat­ter" which takes plain text and re­turns HTM­L. Then we add stuff in the UI so the us­er can choose what for­mat­ter he wants, and im­ple­ment two of those.

Here's our plug­in cat­e­go­ry class:

cat­e­gories.py

class Formatter(object):
    """Plugins of this class convert plain text to HTML"""

    name = "No Format"

    def formatText(self, text):
        """Takes plain text, returns HTML"""
        return text

Of course what good is a plug­in ar­chi­tec­ture with­out any plug­ins for it? So, let's cre­ate two plug­ins.

First: a plug­in that takes python code and re­turns HTM­L, thanks to pyg­ments.

plu­g­in­s/pyg­men­tize.py

from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter

from categories import Formatter

class Pygmentizer(Formatter):
    name = "Python Code"

    def formatText(self, text):
        return highlight(text, PythonLexer(), HtmlFormatter(full=True))

See how it goes in­to a plug­ins fold­er? Lat­er on we will tell yap­sy to search there for plug­ins.

To be rec­og­nized as a plug­in, it needs a meta­da­ta file, too:

plu­g­in­s/pyg­men­tize.yap­sy-­plu­g­in

[Core]
Name = Python Code
Module = pygmentize

[Documentation]
Author = Roberto Alsina
Version = 0.1
Website = //ralsina.me
Description = Highlights Python Code

And re­al­ly, that's all there is to mak­ing a plug­in. Here's an­oth­er one for com­par­ison, which us­es do­cu­tils to for­mat re­Struc­tured Tex­t:

plu­g­in­s/rest.py

from categories import Formatter
import docutils.core
import docutils.io


class Rest(Formatter):
    name = "Restructured Text"

    def formatText(self, text):
        output = docutils.core.publish_string(
            text, writer_name = 'html'
        )
        return output

plu­g­in­s/rest.yap­sy-­plu­g­in

[Core]
Name = Restructured Text
Module = rest

[Documentation]
Author = Roberto Alsina
Version = 0.1
Website = //ralsina.me
Description = Formats restructured text

And here they are in ac­tion:

//ralsina.me/static/yapsy/editor2.jpeg

reSt mode

//ralsina.me/static/yapsy/editor3.jpeg

Python mode

Of course us­ing cat­e­gories you can do things like a "Tool­s" cat­e­go­ry, where the plug­ins get added to a Tools menu, too.

And here's the ap­pli­ca­tion code:

ed­i­tor2.py

from categories import Formatter
from yapsy.PluginManager import PluginManager

class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.formattersCombo = QtGui.QComboBox()
        self.editor = QtGui.QPlainTextEdit()
        self.preview = QtWebKit.QWebView()

        self.layout.addWidget(self.formattersCombo)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.preview)

        self.editor.textChanged.connect(self.updatePreview)
        self.setLayout(self.layout)

        # Create plugin manager
        self.manager = PluginManager(categories_filter={ "Formatters": Formatter})
        self.manager.setPluginPlaces(["plugins"])

        # Load plugins
        self.manager.locatePlugins()
        self.manager.loadPlugins()

        # A do-nothing formatter by default
        self.formattersCombo.addItem("None")
        self.formatters = {}
        print self.manager.getPluginsOfCategory("Formatters")
        for plugin in self.manager.getPluginsOfCategory("Formatters"):
            print  "XXX"
            # plugin.plugin_object is an instance of the plugin
            self.formattersCombo.addItem(plugin.plugin_object.name)
            self.formatters[plugin.plugin_object.name] = plugin.plugin_object

    def updatePreview(self):
        # Check what the current formatter is
        name =  unicode(self.formattersCombo.currentText())
        text = unicode(self.editor.toPlainText())
        if name in self.formatters:
            text = self.formatters[name].formatText(text)
        self.preview.setHtml(text)

In short: this is easy to do, and it leads to fix­ing your ap­pli­ca­tion's in­ter­nal struc­ture, so it helps you write bet­ter code.

Full source code for ev­ery­thing.


Contents © 2000-2020 Roberto Alsina