By Ingeniweb. A Django site.
Avril 19, 2009
» URLs in books


I received some complaints about the fact that some links in my books were dead by the time they were printed.

For the next book I am working on, I have proposed to my editor to set up a website to keep track of all references mentioned.

By using unique short ascii references throughout the book, it’s easy to provide a simple redirect service to the target URL, and to fix it when it changes (just by setting up a mail alert if your redirect reaches a 404).

For example, if I am referring to mod_wsgi in my book, I can write this reference: #mod_wsgi, and provide a redirection to http://code.google.com/p/modwsgi into my website, through a unique, mnemotechnic permanent URL : http://ziade.org/urls/mod_wsgi.

This small service, à la Tiny URL is not a burden for the reader imho : he is using his computer anyway when he visits an URL mentioned in a book.

It’s a simple idea I am sure a lot of people have thaught about before, but I fail to see it applied in the books I am buying these days. Is there any good reason I fail to see ?

Avril 10, 2009
» Distutils: introducing the check command (reStructuredText control)


I am introducing the check command in Distutils. This command will check your package metadata, like the sdist and the register command already do (they display warnings).

But the new thing is that it will also allow you to check if long_description is reStructuredText compliant.

Its usage will be:

$ python setup.py check --restructuredtext
running check
warning: check: Title underline too short. (line 32)
warning: check: Title underline too short. (line 32)
warning: check: Could finish the parsing. (line None)

And there’s also a strict mode, that raises an error in case something is wrong

$ python setup.py check --restructuredtext --strict
running check
warning: check: Title underline too short. (line 32)
warning: check: Title underline too short. (line 32)
warning: check: Could finish the parsing. (line None)
error: Please correct your package.

Last, this command will be used by register, and sdist, so you can stop the process in case the metadata are not correct. This is useful to make sure your PyPI home page is not broken for example, since it parses long_description to build it. So a good practice will be to use strict when registering a package:

$ python setup.py register --strict
running register
running check
warning: check: Title underline too short. (line 32)
warning: check: Title underline too short. (line 32)
warning: check: Could finish the parsing. (line None)
error: Please correct your package.

This feature will land in Python 2.7 (I am working on it and it should be commited this week end). Of course, it will not introduce a hard dependency on docutils in Python, neither it will change the current default behavior.

Until then, you can use collective.dist 0.2.3, that provides this feature for Python 2.4 to 2.6

Février 23, 2009
» Raising Distutils test coverage : half-way


After the next commit I will make in Distutils (that adds tests for bdist_rpm), the test coverage of this Python standard library  package will be at 41%. This means that I have doubled the test coverage over the past few months, from 18% to 41%.

My goal is to double it again, and reach 80% in the next 6 months.

This also means I am just half an idiot now ! (since people who don’t have 100% code coverage are idiots ;) ).

So does it make Distutils more robust ?

It would have probably made the latest Python 3 release looks better for this package, since we had a uncovered cmp() call left in Distutils by the time the release was made. In the meantime, as I said before, the “real” Distutils regression test suite is held by all the packages out there in the community, that are built and installed everyday.

Python trunk Distutils test coverage : 41%

Name               Stmts   Exec  Cover
--------------------------------------
__init__               3      0     0%
archive_util          77     61    79%
bcppcompiler         185      0     0%
ccompiler            453    211    46%
cmd                  180    134    74%
config                73     59    80%
core                  93     50    53%
cygwinccompiler      161      0     0%
debug                  3      3   100%
dep_util              43     11    25%
dir_util             109     76    69%
dist                 581    386    66%
emxccompiler         118      0     0%
errors                49      0     0%
extension             97     28    28%
fancy_getopt         233    126    54%
file_util            124     77    62%
filelist             161    102    63%
log                   46     21    45%
msvc9compiler        408      0     0%
msvccompiler         370      0     0%
spawn                 93     28    30%
sysconfig            323     51    15%
text_file            112     61    54%
unixccompiler        160     64    40%
util                 255    157    61%
version               68     62    91%
versionpredicate      61     51    83%
__init__               3      3   100%
bdist                 61     35    57%
bdist_dumb            57     47    82%
bdist_msi            322      0     0%
bdist_rpm            252    198    78%
bdist_wininst        170      0     0%
build                 60     54    90%
build_clib            90      0     0%
build_ext            334    160    47%
build_py             213    178    83%
build_scripts         78     65    83%
clean                 35      0     0%
config               185      0     0%
install              251    156    62%
install_data          44      0     0%
install_egg_info      40     32    80%
install_headers       25      0     0%
install_lib           97     50    51%
install_scripts       33     29    87%
register             173     82    47%
sdist                228    180    78%
upload               112     38    33%
--------------------------------------
TOTAL               7502   3126    41%


Python 2.5.4 Distutils test coverage : 18%

Name               Stmts   Exec  Cover
--------------------------------------
__init__               3      0     0%
archive_util          78     11    14%
bcppcompiler         185      0     0%
ccompiler            453      0     0%
cmd                  180     79    43%
core                  93     15    16%
cygwinccompiler      160      0     0%
debug                  3      3   100%
dep_util              43      4     9%
dir_util             106     50    47%
dist                 578    342    59%
emxccompiler         118      0     0%
errors                49      0     0%
extension             97      9     9%
fancy_getopt         233    121    51%
file_util            121     50    41%
filelist             162      0     0%
log                   46     15    32%
msvccompiler         365      0     0%
mwerkscompiler       140      0     0%
spawn                 93      0     0%
sysconfig            296     10     3%
text_file            146      0     0%
unixccompiler        159      0     0%
util                 235     69    29%
version               68     48    70%
versionpredicate      61     51    83%
__init__               3      3   100%
bdist                 59      0     0%
bdist_dumb            57      0     0%
bdist_msi            320      0     0%
bdist_rpm            248      0     0%
bdist_wininst        159      0     0%
build                 52     47    90%
build_clib            90      0     0%
build_ext            304      0     0%
build_py             213    143    67%
build_scripts         78     64    82%
clean                 35      0     0%
config               185      0     0%
install              220    120    54%
install_data          44      0     0%
install_egg_info      40      0     0%
install_headers       26      0     0%
install_lib           96      0     0%
install_scripts       33     29    87%
register             171      0     0%
sdist                204      0     0%
upload               118      0     0%
--------------------------------------
TOTAL               7026   1283    18%

Novembre 22, 2008
» How to be disappointed with the “printed” in “printed book”


I feel really bad about this comment on my book : How To Be Dissappointed in Something You Recommend.

Just a quick word about the try, return finally code pattern, since I had some feedback about it. I would like to mention that this code pattern is perfectly right:

def function():
    try:
      return something
    finally:
      do something

I should have explained it better, because this pattern is not used a lot by people, so you can think that “do something” is called after the return of the function, which is not the case.

For the typos now:

The first thing I did wrong: when I started the book, I wanted, as I did in my previous book, to run unit tests on the book itself to avoid those mistakes. That said, the previous one was in Latex, which is quite simple to interact with, and this one is in OpenOffice, because that is how the editor works. I had to write a script to extract the Python code from the Ooo file, to unit test it. I didn’t. I simply ran out of time, as usual when you have deadlines on books.

The second thing I did wrong: I should have told the editor to wait a bit, I didn’t.

But Packt does Print On Demand, so I know that the Errata page I am maintaining here : http://atomisator.ziade.org/wiki/Errata, is being processed by the editor, and that the typos will be removed from the book at some point, without having to wait for a second edition.

I’ll update this blog entry as soon as I know the status on this.

I am really sorry Calvin, and all the people that are suffering from these typos.

      

Août 25, 2008
» Visual profiling with Nose and gprof2dot


Nose comes with a handy option to generate profiling stats.

To profile your code, create a test dedicated to this purpose and run it with the right options:

$ nosetests --with-profile --profile-stats-file stats.pf test_performance

This will run the tests that corresponds to the test_performance name and generate a stats.pf file.

Nose uses hotshot, so if you want to generate a file that can be read directly by the pstats module and all the statistics tools out there, you need to convert it using the hotshot.stats module.

From there, there is plenty of tools that can transform such a file into a visual graph. Most of the time, they use Graphviz to render a graph, by generating a file dot can read. This software is most of the time easy to install through a binary distribution on your system. If you need to compile it… good luck.. ;)

Anyway, from there, I use gprof2dot, which renders a nice graph with meaningful colors.

From the author:


The color of the nodes and edges varies according to the total time % value. In the default temperature-like color-map, functions where most time is spent (hot-spots) are marked as saturated red, and functions where little time is spent are marked as dark blue.








If you want to use it, I have created some console scripts for conveniency, you can install using easy_install :

$ easy_install pbp.scripts

It creates a gprof2dot script you can use, following the author documentation, but also a hotshot2dot script that will convert automatically a statistics file and pass it to gprof2dot:

$ hotshot2dot /path/to/my/hotshot/file

This will print in the output a dot file, you can send to the dot program, using a pipe:

$ hotshot2dot /path/to/my/hotshot/file | dot -Tpng -o output.png

You will get the visual result in output.png.

Juin 13, 2008
» collective.buildbot mailing list


Interested in collective.buildbot usage or development ?

Join us in the dedicated google group : http://groups.google.com/group/collectivebuildbo

Reminders:

  • collective.buildbot is a set of zc.buildout recipes and support for declarative configuration for Buildbot.
  • BuildBot is a system to automate the compile/test cycle required by most software projects to validate code changes, to set up a continuous integration system.

Mai 12, 2008
» Plone Paris Sprint wrapup, part #2: collective.buildbot released !


The Pimp my Buildbot project that was started here at Ingeniweb some time ago, to be able to set up a buildbot in a matter of minutes with zc.buildout, was continued during the sprint, and the guys did a great job on it.

It will be used here in customer projects with a Paster that adds buildbot support when a project starts, because it is a waste of time for the developers to set everything everytime.

Jean-Francois Roche, Kai Lautaportti and Gael Pasgrimaud added extensive configuration options (mail, scheduling), and made the SVN Poller works. This feature allows for instance to make the buildbot watch a SVN repository without having to add a hook in the server (post-commit hook for instance), when you don’t own it (SourceForge, collective, etc)

The tool is released in the collective, and available at the Cheeseshop in one single package !

If you want to set a buildbot

  • provide for each one of your project a buildout that has a test script
  • make sure the test script returns exit code (–with-exit-status with zope.testing)
  • create a buildout cfg file using collective.buildbot
  • run buildout, that’s it !
  • run the master, slaves scripts, and go to the /waterfall page

Just try out our own buildbot by running this sequence:

$ cd /tmp/
$ mkdir my_bot
$ cd my_bot/
$ svn co https://ingeniweb.svn.sourceforge.net/svnroot/ingeniweb/buildbot/trunk .
$ python bootstrap.py
$ bin/buildout
$ bin/master start
$ bin/linux_debian start   (that's our slave)

You should have a buildbot running then at http://localhost:9000/waterfall

The tool, without the polling stuff, also works with Mercurial and Bzr, but probably needs more tests with these repositories. We also need to make sure the slaves works fine under Windows, and add a nice front page to the buildbot.

If you use it let us know !

Mai 2, 2008
» Plone Paris Sprint wrapup, part #1: How do you use eggs and zc.buildout in your projects ?


This is the first post of the wrapups I want to do about the sprint that we had in Paris last week-end. We had a Bird of Feather about how people use eggs and zc.buildout in their projects, how they release and deploy them.

There were some people from Headnet (Anton, Mustapha) and Infrae (Kit, Sylvain) and Ingeniweb (Me), and we compared a bit how we are working with eggs, zc.buildout etc.

That is what I love in our community: companies can share their knowledge and grow up all together, technically speaking.

We all have internal recipes, command-line scripts, and are all relatively happy with zc.buildout. This discussion was very instructive.

From there, I thaught it would be a good idea to launch a new project in the community, on the top of zc.buildout (and maybe zc.sourcerelease), that would provide a common set of tools and deploying best practices, for people that works with the buildout, no matter which framework they use (Silva, Plone, etc.)

The first step for this project is to find the common needs and see if we can join forces to build common tools. To start it up, I decided to wrap up and release our internal set of tools into a single package called iw.releaser and publish it. This is the work I have done during the last months with the help of Gael, to help our team to work with zc.buildout in Plone Projects. It is Subversion dependent at this time.

I am expecting some feedback from Anton and Sylvain to see if we can make it a collective tool.

This package provides:

  • a skeleton to build a project structure (buildout, packages; docs, etc.) so all projects have a standardized structure
  • a ‘release’ distutils command that releases a package, upload it to the Cheeseshop or other servers, and send a shout out mail
  • a set of command line tools, that can be used to deploy a buildout. These commands are doing many things besides launching a buildout building (which is a bit different from zc.sourcerelease)

This package is used at Ingeniweb for a few months now, and I tried to summarize how it is used in the docs. I bet a lot of bugs will be found if you try it, so consider this package as a non-mature package yet.

Join us all ! So you will be able to release and deploy your buildout-based apps with a few command-line calls, :D

Avril 1, 2008
» Pimp my buildbot !


When a project team use Test-Driven Development to build the code (everyone should), the next step is to set up automate builds, as explained in continous integration.

This is where Buildbot is great. It is easy and flexible to install, even more since Twisted has been eggified. A few easy_install steps are now sufficient to create a buildbot waterfall. Configuring a buildbot requires writing a few Python scripts though, and this has to be done everytime a project starts.

In my work, I need to be able to set buildbots in a matter of minutes, and they are always similar. They just need a buildmaster, a buildslave, and a few rules.

A first attempt to make things easier is to write a Python Paste script that generates default files. This is not very flexible though, as upgrades are still a bit tedious.

A more interesting solution is to provide zc.buildout recipes that take care of buildmaster and buildslave generation, through a very simple configuration file.

We have started this project, in order to be able to launch buildbot within a buildout.

The project has three parts:

  • iw.buildbot: a thin layer on the top of Buildbot that allows to drive it with configuration files, instead of Python code. In other words, it makes buildbot configuration based on declarative configuration file and dynamic Python code, instead of declarative Python code.
  • iw.recipe.buildmaster: a zc.buildout recipe that creates a buildbot instance together with configuration files.

The result is that creating a buildbot is done in a few lines in the buildout.cfg file:

[buildout]
parts =
  buildmaster
  linux_debian

[buildmaster]
recipe = iw.recipe.buildmaster
project-name = Ingeniweb Public buildbot
project-url = http://ingeniweb.com
port = 8999
wport = 9000
url = http://buildbot.ingeniweb.com
slaves =
  linux_debian    xxxxx
projects =
  iw.recipes 

[linux_debian]
recipe = iw.recipe.buildslave
host = localhost
port = ${buildmaster:port}
password = xxxx 

[iw.recipes]
slave-name=linux_debian
base-url=http://ingeniweb.svn.sourceforge.net
repository=/svnroot/ingeniweb/projects/iw.recipes/
branch=buildout
build-sequence =
    python bootstrap.py
    bin/buildout 

test-sequence =
    bin/test -v –exit-with-status
  • buildmaster defines the project name and url, the buildbot web port, slave port and url, and a list of slaves and projects.
  • each project has his own section, where the Subversion path is defined, as well as the build sequence and the test sequence.
  • each slave is defined in its section

From there other buildouts can be created to group packages to be tested, and to define a script that can be used for tests. For Zope applications, that would be zopectl of course, as long as it is used with –exit-with-status.

This is the case for iw.recipes: it grabs all iw.recipes.* eggs to hook them to a testrunner.

The result can be see here: http://buildbot.ingeniweb.com

The next steps are to make sure everything works fine under Windows, and see how things goes in various projects, then make it work with all kinds of VCS and schedulers.

I will probably propose a sprint task on this in Paris sprint and see if the package meets interest.

Février 25, 2008
» Pylint installation made easier


I love Pylint.

Correctly configured, it is really useful to raise your code quality. But it can be really painful to install if you are not under a Debian-like or OpenSuse-like system, because of its dependencies that are not namespaced packages (logilab-common and logilab-astng).

In other words, don’t try to easy_install the packages that are on PyPI, it will not work.

That’s why I have created an egg, called logilab.pylintinstaller, that will let you install it as easy as:

$  easy_install logilab.pylintinstaller

It bundles all dependencies and grabs pylint 0.14, then installs everything.

Thanks to Sylvain Thénault from Logilab for his help on this.

Janvier 8, 2008
» sys.setdefaultencoding is evil


I have recently found some UnicodeDecodeError bugs on some products, that some people couldn’t reproduced. The bug was due to a call to a CMF API that was doing a str() over the object, right before using it.

This is perfectly fine in that case, because the object is supposed to be a ZODB id, so it has to be full ASCII.

So the bug looks like this :

>>> id = u'éou'
>>> str(id)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in
position 0: ordinal not in range(128)

The people that couldn’t reproduced it because they use that ugly hack which consists of setting Python’s default encoding to utf8:

>>> import sys
>>> sys.setdefaultencoding('utf8')
>>> id = u'éou'
>>> str(id)
'\xc3\xa9ou'

This will be applied to the whole process, and Python itself dynamically removes the method from the module at it first use. From the official doc:

setdefaultencoding(name)
Set the current default string encoding used by the Unicode implementation.
If name does not match any available encoding, LookupError is raised.
This function is only intended to be used by the site module implementation and,
where needed, by sitecustomize. Once used by the site module, it is removed from
the sys module's namespace. New in version 2.0.

I can’t find the link back, but I have read once that this built-in was to be removed because it should not be used outside site.py

The problem is that people tend to add a sitecustomize.py in their environment, then work with str() and unicode() calls and forget about doing it right. The result is a major
misused of strings and unicodes and the code created will be buggy on other computers.

So never ever use this in your code. If you have a UnicodeDecodeError it probably means the function is waiting for a string. If you have a UnicodeEncodeError, it should be unicode. In the same way, do not guess the encoding in your code. You should work with one type (str or unicode) and know exactly what is its encoding.

I think this misued is partly due to a lack of warning here: http://www.diveintopython.org/xml_processing/unicode.html

Because that’s one of the first page a developer finds when he tries to understand why

he has such bugs.

See a similar entry on the topic 2 years ago here: http://faassen.n–tree.net/blog/view/weblog/2005/08/02/0

Décembre 30, 2007
» Is __ prefix considered unpythonic ?


EDIT: Justus provided in the comments a great link where Jim Fulton argues that __ should be marked deprecated, folllowed by Neal Norwiz and Tim Peters answers. This helps a lot understanding what __ should be used for: http://mail.python.org/pipermail/python-dev/2005-December/058555.html 

I am writing on Python OOP best practices and I was wondering what are the best ways to name attributes in classes. My main concern is about the distinction between private and protected attributes.

  • private attributes are attributes that cannot be seen or used outside of the class, even buy subclasses. The Python parser calls the name mangling algorithm when it finds them to prevent name collision;
  • protected attributes are attributes that can be used and seen in subclasses and should not be used outside. Nothing is done on them and they can be used like public attributes.

When should we use them ?

If you read PEP8, it’s clearly said that name mangling (using a ‘__’ prefix) is the best way to protect an attribute from beeing accessed or overriden. So it should be used for all class internals that is not intended to be overriden.

But in the biggest open source code bases like Zope or Plone, ‘__’ usage is very uncommon. The simple ‘_’ prefix is often used instead, to mark attributes that are private to the class or to the module. So there are no real distinction between private and protected attributes. It seems that the ‘private’ concept is not even used, and people often cut their class code in two parts: public and protected.

In other languages (like Delphi) that define protected and private levels though, protected attributes are not used a lot, and people tend to cut their code in private and public parts and make the protected layer as slim as possible.

Practical rules

Based on these remarks, here’s a tentative of ‘__’ and ‘_’ prefixes best usages in Python, for the use cases I know :

  • use __ with property. since properties cannot use overriden methods and are tied to the class, the methods used with it should always be private;
  • use __ for methods that works with private attributes. If your methods works for private attributes, make them private too;
  • use _ on methods when they are clearly intended to be overriden;
  • use __ for all module functions and variables that are private. A protected level is not needed since a module cannot be overriden.

Following these rules would probably make 90% of class attributes private instead of protected, and change all base code conventions. So I am wondering: am I a bit unpythonic if I try to follow this standard in attribute naming ? My guess is that most base code are not clean enough in that matter. For instance, many of them use both new-style and old-style classes under Python 2.5, which lead to a MRO algorithm that differs depending on the classes !

I would love to hear how you people deal with these conventions.

Novembre 30, 2007
» Using ZopeSkel to raise Plone projects quality


At Ingeniweb, we have started to define standards for our Plone projects using ZopeSkel. IngeniSkel is a thin layer on the top of ZopeSkel, that was:

  • injecting Archetype content within existing products
  • defining standard tests skeletons for all elements contained in products.
  • providing other templates like the recipe one, that creates a skeleton for zc.buildout recipes

The injection idea was previously proposed by Martin, and Mustapha started to implement it in a branch. Erik F. has added the archetype content injection yesterday.

It means you can now inject new archetype based content with the Paster directly with ZopeSkel:

$ paster create -t archetype my.package $ cd my.package  $ paster --help
usage: paster [paster_options] COMMAND [command_options]
options:
  ...
Commands:
  create       Create the file layout for a Python distribution
  ...
ZopeSkel local commands:
  addcontent   Adds plone content types to your project  $ paster addcontent --list
Available templates:
  contenttype:  A content type skeleton
  portlet:      A Plone 3 portlet
  view:         A browser view skeleton
  zcmlmeta:     A ZCML meta directive skeleton $ paster addcontent

This is great, now the only thing it misses so we can drop IngeniSkel in favor of this enhanced version of ZopeSkel is generating tests modules on all templates and local commands, and the same way everytime. I started such a work in another branch to backport what we have it and I will propose it.

Why ? Because having tests that are written the same way on all layers of a Plone project is important to:

  • automate some QA and documentation tasks
  • make sure a newcomer won’t get lost on how to startup a new piece of code, with the right test fixture. If he takes too much time to prepare the test fixture, he’ll probably drop the TDD approach…

Edit: I have merged the the recipe template into ZopeSkel trunk already, as I’ve been asked to

Octobre 18, 2007
» tarek


There are many tools available for Python to perform benchmarks and debugging. For example:

  • Hotshot is bundled in the standard library and provide useful data. Maybe you have to install an extra package on some linux distribution if I recall it correctly, because it’s not GPL;
  • iPython provides a nice interface to perform live debugging, like automatic invocation of pdb on exceptions;
  • the standard module test provides pystone, that let you benchmark the computer in use before the timed tests. This is helpfull to bench the code on several computers: the measurements can be expressed in in pystones. In other words, you are able to have a reproducable measure of a piece of code and work on the code complexity to make it faster. In reality, any interference can change the results, but this is true as well for time measures.
  • all big python frameworks are using the logging module, so it’s easy to hook in it if extra logging is needed.

But when trying to equip an application in order to find out why some functionalities are slow, or why something goes wrong, it’s not always easy to set up precisely what you want to log if something is slow or what your want to hook if a bug appears. The simplest way is to call out all the mentioned tool from the code, but it too obtrusive. Another way is to use decorators.

To perform it, you’ll have to:

  • get and install iw.quality
  • create the benchmarking or debugging module

Get and install iw.quality

iw.quality gathers helpers for QA. It has an implementation of the Levenshtein distance discussed earlier, and now a decorator used for benchmarking and debugging purpose. Since it’s available in PyPi, you should be able to install it like this:

$ easy_install iw.quality

See setuptools informations if you need to install easy_install itself.

Preparing the benchmark or the debugging

Whether you are about to benchmark or debug your program, you need to list all the places in your code where you need to hook a log or a pdb. Then you can create a specialized python module that can be used when needed. This module will simply decorate the functions you want to work with.

Benchmarking

Here’s an example, let’s equip sqlalchemy for benchmarking.

benchmarking.py file:

#
# benchmarking queries
#
from iw.quality.decorators import log_time
import sqlalchemy

def logger(msg):
    print msg

simple_logger = log_time(logger=logger)

sqlalchemy.create_engine = simple_logger(sqlalchemy.create_engine)
sqlalchemy.engine.Engine.execute = simple_logger(sqlalchemy.engine.Engine.execute)

The log_time decorator comes with a few parameters, like logger wich is called with the log message. By default it uses logging.info, but you can use your own like in the example. The chosen functions are then decorated.

Let’s use it:

>>> import benchmarking      # applies the decorators
>>>from sqlalchemy import *

>>> db = create_engine('sqlite:///:memory:')
log_time::2007-10-18T21:50:52.352037::0.013::function 'create_engine',args: ('sqlite:///:memory:',), kw: {}
>>> db.execute('create table TEST(id int)')log_time::2007-10-18T21:52:13.761085::0.104::function 'execute', args:
(<sqlalchemy.engine.base.Engine object at 0x12e6e90>, 'create tableTEST(id int)'), kw: {}
<sqlalchemy.engine.base.ResultProxy object at 0x12e6ff0>
>>> db.execute('insert into TEST (id) values (1)')
log_time::2007-10-18T21:52:50.265860::0.000::function 'execute', args:(<sqlalchemy.engine.base.Engine object at 0x12e6e90>, 'insert into TEST
(id) values (1)'), kw: {}<sqlalchemy.engine.base.ResultProxy object at 0x12f50d0>

If you need to display more infos on the call, you can use your own formatter instead of the provoded one. Let’s extend the benchmark file:

def formatter(execution_time, function, args, kw):
    return '%s = %.3f ms' % (function, execution_time)

simple_logger = log_time(logger=logger, formatter=formatter)

And rerun some code:

>>> from sqlalchemy import *
>>> db = create_engine('sqlite:///:memory:')
<function create_engine at 0x12328b0> = 0.014 ms

You can add a treshold on the function timing, to log only functions that are up to this treshold. This is useful to filter a bit.

Debugging

For debugging purpose, you can use the debug parameter:

def debug(e):
    import pdb
    pdb.set_trace()

simple_logger = log_time(logger=logger, formatter=formatter,
                         debugger=debug)

It will be called in case of an Exception:

from sqlalchemy import *

db = create_engine('gcckc')
--Return--
/Users/tziade/tests/benchmarking.py(14)debug()
(Pdb) c
/Users/tziade/tests/banchmarking.py(10)logger()
(Pdb) c
function create_engine at 0x12328f0> = 3.544 ms

Traceback (most recent call last):...

raise esqlalchemy.exceptions.ArgumentError: Could not parse rfc1738 URL from string 'gcckc'

Conclusion

By using this simple decorator, it’s easy to group benchmarking and debugging in a specialized module, and generate custom reports. The best practice is to create a module per each use case. I didn’t hook it on Hotshot or other utilities to let people use the tools they like.

Octobre 5, 2007
» tarek


One of the fundamentals of unit testing is that the unit test should never depend on any external resource. This is true for all data that might be needed to run the tests, but also for third party servers like LDAP or SQL: they have to be faked.

  • LDAP is quite painful to fake. The simplest way is to create all tests with a real LDAP server, then replace it with a class that returns explicit responses for each explicit request. This is managable when the LDAP layer is well done, and easy to patch.
  • Mailhost is also quite easy to patch in the test fixture, and printing back the mail sent instead of calling the smtplib will allow you to write doctests and unittest without depending on a smtp server through telnet.
  • For SQL, the simplest way, as long as you use a library that knows how to call different DBs through DBAPI, is to use a flat file DB system. I use sqlalchemy, and patching it in my test fixtures is easy as patching one line: the sqluri. For example, mysql://user:pass/server/base’ will become pysqlite:///path/to/package/tests/data/test.db’. The tests then interact with a SQLite file, and as long as your code uses sqlalchemy APIs, everything should work like if the DB was MySQL or Postgres. The only difference I can think of is the DB unicode settings, that might be different in the production server, so be careful in your doctest when you test strings.
  • For other third party elements, Mocking can help !

Septembre 30, 2007
» tarek

Before deploying a package with python setup.py install, it’s a good idea to launch the tests with python setup.py test.

This command can be used as well to quickly launch tests within a package that is being developed. Since setuptools can be extended, other commands can be added to be launched from the setup script, while you work in your package.

I have created for example a qa command that launches pyflakes over the package code, to make sure I don’t leave unused import. I could have used a direct pyflakes call, but my QAs test are going to grow so keeping the QA script details under a python setup.py qa call is a good practice. This will also make buildbot integration easier, as I can check for package QA through an unified serie of calls, that plays with the package setup.py script.

Commands are simple class that derives from setuptools.Command, and define some minimum elements, which are:

  • description: describe the command
  • user_options: a list of options
  • initialize_options(): called at startup
  • finalize_options(): called at the end
  • run(): called to run the command

The setuptools doc is still empty about subclassing Command, but a minimal class will look like this:

 class MyCommand(Command):
     """setuptools Command"""
     description = "run my command"
     user_options = tuple()
     def initialize_options(self):
         """init options"""
         pass

     def finalize_options(self):
         """finalize options"""
         pass

     def run(self):
         """runner"""
         XXX DO THE JOB HERE

The class can then be hook as a command, using an entry point in its setup.py file:

 setup(
     # ...
     entry_points = {
     "distutils.commands": [
     "my_command = mypackage.some_module:MyCommand"]}
 )

This will add an entry point when the package is installed, so you can run your new command this way:

 python setup.py my_command

You can give a try of my qa example by installing my eggchecker package:

 easy_install http://programmation-python.org/pycommunity/eggchecker-0.1.tgz

That’s merely a draft, but will show you how pyflakes is launched within the setup.py script.