Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

A programming challenge for myself

I worked on uRSSus for a cou­ple of hours again, and it's work­ing pret­ty nice­ly.

  • Re­al­­ly us­es the ORM

  • Mul­ti­pro­cess­ing for a non-block­­ing UI (python-pro­cess­ing is awe­­some)

  • Adapts to the quirks of some feeds (why on earth would some­one do a feed with­­out dates? It's AOL Fan­­House­­!)

I in­tend to keep work­ing like this for a cou­ple of week­s, and see how far I can get in fea­ture par­i­ty to akre­ga­tor.

No, I don't ex­pect to reach fea­ture par­i­ty, I on­ly want to strive for it. SInce I lack the fo­cu­sand/or en­er­gy for a mul­ti year com­mit­ment it re­quires to write the av­er­age free soft­ware, I want to see how far a sprint gets me.

urssus2

So far, it's fun.

Why no packaging software should replace your config files

When you up­grade a piece of soft­ware on Lin­ux, there are two paths it can go when there are in­com­pat­i­ble changes in the con­fig files (ok, 3 path­s, De­bian asks you what to do):

  1. The "rp­m­new" way: in­­stall the new con­­fig file as "what­ev­er.rp­m­new", which means the soft­­warewill break im­me­di­ate­­ly, but that's ok, be­­cause you are do­ing up­­­grades, so you should be watch­ing al­ready.

  2. The "rp­m­save" way: re­­place the old file and save a copy as "what­ev­er.rp­m­save".

This has two prob­lem­s:

  1. The soft­­ware may fail or not, or fail in a sub­­­tle way, and you will not no­tice right away.

  2. Maybe the old file will be lost any­way:

    lrwxrwxrwx  1 root root 32 jul 15 22:41 /etc/named.conf -> /var/named/chroot/etc/named.conf
    lrwxrwxrwx  1 root root 32 jul 15 22:36 /etc/named.conf.rpmsave -> /var/named/chroot/etc/named.conf

In this case the "file" was a sym­link, so by "sav­ing a copy" it on­ly saved an­oth­er sym­link to the soon-­to-be-over­writ­ten file.

And that's why, ladies and gen­tle­men, the rpm­new way is the good way.

The world lamest GUI newsreader... in 130 LOC

I start­ed this as an ex­per­i­ment to see how hard it was to build apps us­ing QT and Elixir. This is how it looks af­ter two hours of cod­ing:

urssus1

And here's the code: http://urssus.­google­code.­com

You will need:

  • PyQt 4.4 or lat­er

  • Mark Pil­­grim's feed­­pars­er

  • Python 2.5 (or when­ev­er el­e­­men­t­tree got in­­­clud­ed)

  • A OPML file

  • The Python SQLite bind­ings

  • Elixir (the declar­a­­tive lay­er over SQL Alche­my)

You can find the re­al code at

And then you can use these 131 LOC ;-)

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

# Mark Pilgrim's feed parser
import feedparser as fp

# DB Classes
from elixir import *

metadata.bind = "sqlite:///urssus.sqlite"
metadata.bind.echo = True

class Feed(Entity):
  htmlUrl     = Field(Text)
  xmlUrl      = Field(Text)
  title       = Field(Text)
  text        = Field(Text)
  description = Field(Text)
  children    = OneToMany('Feed')
  parent      = ManyToOne('Feed')
  posts       = OneToMany('Post')
  def __repr__(self):
    return self.text

  def update(self):
    d=fp.parse(self.xmlUrl)

class Post(Entity):
  feed        = ManyToOne('Feed')
  title       = Field(Text)
  post_id     = Field(Text)
  content     = Field(Text)

# This is just temporary
setup_all()
create_all()

# UI Classes
from PyQt4 import QtGui, QtCore
from Ui_main import Ui_MainWindow

class MainWindow(QtGui.QMainWindow):
  def __init__(self):
    QtGui.QMainWindow.__init__(self)

    # Set up the UI from designer
    self.ui=Ui_MainWindow()
    self.ui.setupUi(self)

    # Initialize the tree from the Feeds
    self.model=QtGui.QStandardItemModel()

    # Internal function
    def addSubTree(parent, node):
      nn=QtGui.QStandardItem(unicode(node))
      parent.appendRow(nn)
      nn.feed=node
      if not node.children:
        return
      else:
        for child in node.children:
          addSubTree(nn, child)

    roots=Feed.query.filter(Feed.parent==None)
    iroot=self.model.invisibleRootItem()
    iroot.feed=None
    for root in roots:
      addSubTree(iroot, root)

    self.ui.feeds.setModel(self.model)

    QtCore.QObject.connect(self.ui.feeds, QtCore.SIGNAL("clicked(QModelIndex)"), self.openFeed)
    QtCore.QObject.connect(self.ui.posts, QtCore.SIGNAL("clicked(QModelIndex)"), self.openPost)

  def openFeed(self, index):
    item=self.model.itemFromIndex(index)
    feed=item.feed

    if not feed.xmlUrl:
      return

    d=fp.parse(feed.xmlUrl)
    posts=[]
    for post in d['entries']:
      print post
      print '----------------------------------\n\n'
      if 'content' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content='<hr>'.join([ c.value for c in post['content']])))
      elif 'summary' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content=post['summary']))
      elif 'value' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content=post['value']))
    session.flush()

    self.ui.posts.__model=QtGui.QStandardItemModel()
    for post in posts:
      item=QtGui.QStandardItem(post.title)
      item.post=post
      self.ui.posts.__model.appendRow(item)
    self.ui.posts.setModel(self.ui.posts.__model)

  def openPost(self, index):
    item=self.ui.posts.__model.itemFromIndex(index)
    post=item.post
    self.ui.view.setHtml(post.content)

if __name__ == "__main__":
  import sys
  # For starters, lets import a OPML file into the DB so we have some data to work with
  from xml.etree import ElementTree
  tree = ElementTree.parse(sys.argv[1])
  current=None
  for outline in tree.findall("//outline"):
    xu=outline.get('xmlUrl')
    if xu:
      f=Feed(xmlUrl=outline.get('xmlUrl'),
             htmlUrl=outline.get('htmlUrl'),
             title=outline.get('title'),
             text=outline.get('text'),
             description=outline.get('description')
             )
      if current:
        current.children.append(f)
        f.parent=current
    else:
      current=Feed(text=outline.get('text'))
    session.flush()
  app=QtGui.QApplication(sys.argv)
  window=MainWindow()
  window.show()
  sys.exit(app.exec_())

Thanks Philip Pearson!

This blog was host­ed for a long time in http://lat­er­al.pyc­s.net ... un­til the site kin­da died. And there are hun­dreds of links around, all point­ing to URLs in pyc­s.net for the old­er sto­ries. Now they all work again!

So, thanks Philip!

Creating and sending nice HTML+Text mails from python

I de­cid­ed I need­ed an au­to­mat­ic re­port of some things on my email ev­ery day, and I want­ed it to look nice both in plain text and HTM­L. Here's what I came up with.

Let's as­sume you cre­at­ed the HTML ver­sion us­ing what­ev­er mech­a­nism you wish, and have it in a vari­able called "re­port".

Here's the im­ports we will use:

import smtplib,email,os,tempfile
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.Charset import Charset

And here's the code:

# Create a HTML mail part
hpart=MIMEText(reporte, _subtype='html', _charset='utf-8')

# Create a plain text mail part
# Ugly and requires links, but makes for a great-looking plain text version ;-)
tf=tempfile.mkstemp()
t=open(tf,'w')
t.write(report)
t.close()
tpart=MIMEText(os.popen('links -dump %s'%tf,'r').read(), _subtype='plain', _charset='utf-8')
os.unlink(tf)

# Create the message with both parts attached
msg=MIMEMultipart('alternative')
msg.attach(hpart)
msg.attach(tpart)

# Standard headers (add all you need, for example, date)
msg['Subject'] = 'Report'
msg['From']    = 'support@yourcompany.com'
msg['To']      = 'you@yourcompany.com'

#If you need to use SMTP authentication, change accordingly
smtp=smtplib.SMTP('mail.yourcompany.com'')
smtp.sendmail('support@yourcompany.com','you@yourcompany.com',msg.as_string())

Contents © 2000-2022 Roberto Alsina