Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

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

Problemas de abandono: rst2pdf

Así que lo voy a re­to­ma­r. Voy a de­di­car­le unas 4ho­ras por se­ma­na.El plan es:

  1. Jun­­tar pa­r­­ches que es­­tán jun­­tan­­do tie­­rra en el is­­sue tra­­cker

  2. Arre­­glar al­­gu­­nos bu­­gci­­tos

  3. Ha­­cer un re­­lea­­se con 1) y 2)

Y por su­pues­to:

  1. No aban­­do­­­na­r­­lo de nue­­vo

Mientras tanto, acá hay algo interesante de lo que recién me enteré. Dimitri Christodoulou hackeó rst2pdf para que soporte la directiva raw:: html.

Eso, ami­go­s, es tan lo­co que lo ten­go que ro­bar (con el cré­di­to co­rrec­to, etc :-)

Y Di­mi­tri, o cual­quier otro que quie­ra ha­cer al­go co­pa­do con rs­t2­pdf, ¡a­vi­sen! ¡Les doy co­m­mi­t!

Python context managers: son fáciles!

Yo es­ta­ba el otro día tra­tan­do de ha­cer co­sas de she­ll scrip­ting con py­thon (co­mo par­te de un se­tu­p.­py mons­truo) y me mo­les­ta­ba que en she­ll es muy fá­cil ha­cer es­to:

cd foo
bar -baz
cd -

O es­to:

pushd foo
bar -baz
popd

O es­to:

(cd foo && bar -baz)

Y en py­thon te­nia que ha­cer es­to, que es lar­go y feo:

cwd = os.getcwd()
try:
    os.chdir('foo')
    os.system('bar -baz')
finally:
    os.chdir(cwd)

Cuan­do en rea­li­dad quie­ro es­to:

with os.chdir('foo'):
    os.system('bar -baz')

Por su­pues­to, eso no es­tá. En­ton­ce­s, pre­gun­té, co­mo se ha­ce eso? Y tu­ve va­rias res­pues­ta­s:

  1. Usá Fa­­bri­­c:

    wi­th cd("­foo"):
        run("­ba­r")
  2. No es di­­fí­­ci­­l:

    cla­ss Dir­Con­text­M(ob­jec­t):
        def __i­ni­t__(sel­f, new_­di­r):
            se­l­f.­new_­dir = new_­dir
            se­l­f.ol­d_­dir = No­ne
    
        def __en­te­r__(sel­f):
            se­l­f.ol­d_­dir = os.­ge­tcw­d()
            os.­ch­di­r(sel­f.­new_­di­r)
    
        def __e­xi­t__(sel­f, *_):
            os.­ch­di­r(sel­f.ol­d_­di­r)
  3. Es más fá­­ci­­l:

    from con­tex­tlib im­port con­text­ma­na­ger
    
    @con­text­ma­na­ger
    def cd(­pa­th):
        ol­d_­dir = os.­ge­tcw­d()
        os.­ch­di­r(­pa­th)
        yield
        os.­ch­di­r(ol­d_­di­r)
  4. Es­­tá bue­­­no, agre­­gué­­mo­s­­lo a pa­­th.­­py pa­­th.­­py

  5. Me­­jor atra­­par ex­­ce­p­­cio­­­nes:

    @con­text­ma­na­ger
    def cd(­pa­th):
        ol­d_­dir = os.­ge­tcw­d()
        os.­ch­di­r(­pa­th)
        tr­y:
            yield
        fi­na­ll­y:
            os.­ch­di­r(ol­d_­di­r)

Apren­dí co­mo ha­cer con­text ma­na­ger­s, so­bre con­tex­tli­b, so­bre fa­bric y so­bre pa­th.­p­y. Na­da mal pa­ra 15 mi­nu­tos :-)

Shipping your PyQt app for windows


I ha­ve wri­tten about this in the pas­t, wi­th the ge­ne­ral con­clu­sion being "i­t's a pain in the ass".

So, no­w, he­re is how it's do­ne.

  1. Start wi­­th a wo­­­rking Py­­Qt appli­­ca­­tio­­n. In this exa­m­­ple, I wi­­ll use de­­vi­­cen­­zo­­.­­py mo­s­­tly be­­­cau­se:

    1. It is a wo­­­­­rking Py­­­Qt appli­­­ca­­­tio­­­n.

    2. It uses a big chunk of Py­­­Qt

    3. It's ea­sy to test

  2. Now you need a se­tu­p.­py. He­re's one that wo­rks, wi­th ex­ten­si­ve co­m­m­men­ts.

# We will be using py2exe to build the binaries.
# You may use other tools, but I know this one.

from distutils.core import setup
import py2exe

# Now you need to pass arguments to setup
# windows is a list of scripts that have their own UI and
# thus don't need to run in a console.

setup(windows=['devicenzo.py'],
      options={

# And now, configure py2exe by passing more options;

          'py2exe': {

# This is magic: if you don't add these, your .exe may
# or may not work on older/newer versions of windows.

              "dll_excludes": [
                  "MSVCP90.dll",
                  "MSWSOCK.dll",
                  "mswsock.dll",
                  "powrprof.dll",
                  ],

# Py2exe will not figure out that you need these on its own.
# You may need one, the other, or both.

              'includes': [
                  'sip',
                  'PyQt4.QtNetwork',
                  ],

# Optional: make one big exe with everything in it, or
# a folder with many things in it. Your choice
#             'bundle_files': 1,
          }
      },

# Qt's dynamically loaded plugins and py2exe really don't
# get along.

data_files = [
            ('phonon_backend', [
                'C:\Python27\Lib\site-packages\PyQt4\plugins\phonon_backend\phonon_ds94.dll'
                ]),
            ('imageplugins', [
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qgif4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qjpeg4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qsvg4.dll',
            ]),
],

# If you choose the bundle above, you may want to use this, too.
#     zipfile=None,
)
  1. Run py­thon se­tu­p.­py py2exe and get a dist fol­der fu­ll of bi­na­ry good­ness.

And tha­t's it. Ex­cept of cour­se, tha­t's not it.

What this wi­ll do is crea­te a bi­na­ry se­t, ei­ther a fol­der fu­ll of things, or a sin­gle EXE fi­le. And tha­t's not enou­gh. You ha­ve to con­si­der at least the fo­llo­win­g:

  1. Put eve­­r­­y­­thing in re­­sou­r­­ce fi­­le­s: ima­­ges, qss fi­­le­s, ico­n­s, etc. Eve­­ry fi­­le your app nee­­d­s? Put it in a re­­sou­r­­ce fi­­le and load it from the­­re. That way you do­­n't ha­­ve to ca­­re about them if you go the "o­­­ne exe" road.

  2. Co­m­­pi­­le .ui fi­­les to .py (s­a­­me rea­­so­­n)

  3. Fi­­gu­­re out if you use Qt's plu­­gi­n­s, and make them wo­­­rk. This in­­clu­­des: using Pho­­­no­­n, using QtS­­Q­­L, and using any ima­­ge fo­r­­ma­­ts other than PN­­G.

After you ha­ve tha­t, are you do­ne? NO!

Your win­do­ws user wi­ll want an ins­ta­lle­r. I am not going to go in­to de­tail­s, but I had a good ti­me using Bi­tRo­ck's Ins­ta­ll­Buil­der for Qt. It's a ni­ce tool, and it wo­rks. Tha­t's a lot in this fiel­d.

But is that all? NO!

You ha­ve to take ca­re of the Vi­sual Stu­dio Runti­me. My su­gges­tio­n? Get a co­py of the 1.1MB vcre­dis­t_­x86.exe (not the lar­ger one, the 1.1MB one), and ei­ther te­ll peo­ple to ins­ta­ll it ma­nua­ll­y, or add it to your ins­ta­lle­r. You are le­ga­lly allo­wed (A­FAIK) to re­dis­tri­bu­te that thing as a who­le. But not wha­t's in it (un­le­ss you ha­ve a VS li­cen­se).

And we are do­ne? NO!

On­ce you run your app "ins­ta­lle­d", if it ever prin­ts an­y­thing to stde­rr, you wi­ll get ei­ther a dia­log te­lling you it di­d, or wor­se (if you are in ay­thing newer than XP), a dia­log te­lling you it can't wri­te to a log fi­le, and the app wi­ll ne­ver wo­rk agai­n.

This is be­cau­se py2exe ca­tches stde­rr and tries to save it on a lo­gfi­le. Whi­ch it tries to crea­te in the sa­me fol­der as the bi­na­r­y. Whi­ch is usua­lly not allo­wed be­cau­se of per­mis­sion­s.

So­lu­tio­n? Your app should ne­ver wri­te to stde­rr. Wri­te an ex­cep­thook and ca­tch tha­t. And then re­mo­ve stde­rr or re­pla­ce it wi­th a log fi­le, or so­me­thin­g. Just do­n't let py2exe do it, be­cau­se the way py2exe does it is bro­ken.

And is that it?

We­ll, ba­si­ca­lly ye­s. Of cour­se you should get 4 or 5 di­ffe­rent ver­sions of win­do­ws to test it on, but you are pre­tty mu­ch free to ship your app as you wis­h. Oh, mind you, do­n't upload it to do­wn­load­s.­com be­cau­se they wi­ll wrap your ins­ta­ller in a lar­ger one that ins­ta­lls bloa­twa­re and cra­p.

So, the­re you go.

Creando un foro de la manera fácil (32 líneas)

Aquí es­tán al­gu­nos de los fea­tu­res que quie­ro:

  • Lo­­­gin via twi­­tter / Fa­­ce­­book / Google / Ope­­nID

  • Nú­­me­­ro ili­­mi­­ta­­do de th­­rea­­ds

  • So­­­po­r­­te de like / dis­­like en th­­rea­­ds y en po­s­­ts

  • Ava­­ta­­res

  • HT­­ML en los po­s­­ts

  • Que man­­de mail al usua­­rio si le res­­po­n­­den

  • Fee­­ds RSS pa­­ra los th­­rea­­ds

Se lo pue­de ver en ac­ción en http://­fo­ro­.­ne­t­ma­na­ger­s.­co­m.ar (por un tiem­po li­mi­ta­do ;-)

Y aquí es­tá el có­di­go:

import bottle
import disqusapi as disqus
import json
shortname = 'magicmisteryforum'
api = disqus.DisqusAPI(open("key").read().strip())

@bottle.route('/', method='GET')
def index():
    msg = bottle.request.GET.get('msg', '')
    threads = api.forums.listThreads(forum=shortname, limit=100)
    print threads[0]
    return bottle.template('main.tpl', threads=threads, shortname=shortname, msg=msg)

@bottle.route('/new', method='POST')
def new():
    title = bottle.request.forms.get('title', None)
    if not title:
        bottle.redirect('/?msg=Missing%20Thread%20Name')
        return
    thread = api.threads.create(forum=shortname, title = title)
    thread_id = thread.__dict__['response']['id']
    # Redirecting to /thread/thread_id doesn't work
    # because threads take a few seconds to appear on the listing
    bottle.redirect('/')

@bottle.route('/thread/:id')
def thread(id):
    t = api.threads.details(thread=id)
    return bottle.template('thread.tpl', shortname=shortname, id=id, thread=t.__dict__['response'])

@bottle.route('/static/:path#.+#')
def server_static(path):
    return bottle.static_file(path, root='./static')

app = bottle.app()
app.catchall = False #Now most exceptions are re-raised within bottle.
bottle.run(host='184.82.108.14', port=80, app=app)

Re­quie­re Bo­ttle y la Dis­qus py­thon API

Por su­pues­to que hay un po­qui­to de tem­pla­tes, acá es­tá mai­n.­tpl y th­rea­d.­tpl. Co­mo apes­to pa­ra el HT­M­L, usa Blue­trip CSS y es sen­ci­llo de cus­to­mi­za­r.

POR SU­PUES­TO QUE HA­GO TRAM­PA!

Es­ta co­sa es ape­nas una ca­pa de pin­tu­ra en­ci­ma de Dis­qus! Más un blog sin pos­ts pe­ro con co­men­ta­rios que un fo­ro! Pe­ro­... qué le fal­ta pa­ra ser un fo­ro de ver­da­d? Fun­cio­na, no? Has­ta se po­drían usar ca­te­go­rías de Dis­qus pa­ra crear su­bfo­ro­s...

Te­nien­do to­do en cuen­ta, creo que es un ha­ck bo­ni­to.

Y si es­pe­rás unos día­s, es­to lle­va a otra co­sa que es mu­cho más má­gi­ca...

Có­di­go fuen­te com­ple­to en http://­ma­gi­cfo­ru­m.­google­co­de.­com


Contents © 2000-2023 Roberto Alsina