Random notes from mg

a blog by Marius Gedminas

Marius is a Python hacker. He works for Programmers of Vilnius, a small Python/Zope 3 startup. He has a personal home page at http://gedmin.as. His email is marius@gedmin.as. He does not like spam, but is not afraid of it.

Sun, 13 Sep 2009

Starting a Pylons project with zc.buildout

For software development I prefer buildout to virtualenv. This is because buildout has a text file describing the state of your working environent, which can be versioned and used later to recreate it, as well as during development to modify the environment slightly.

To start a new Pylons project, first create an empty directory. Let's call our new project AlliterationSharing1, because everybody is sick of 'foo' and 'bar'.

1 Generated by randomly picking two words from /usr/share/dict/words, then chosen over among 120 other variants that weren't as good.

$ mkdir -p ~/src/AlliterationSharing
$ cd ~/src/AlliterationSharing

Now create a file called buildout.cfg with the following content:

$ cat buildout.cfg
[buildout]
parts = pylons

[pylons]
recipe = zc.recipe.egg
eggs = Pylons
       PasteScript
interpreter = python

Download bootstrap.py to it and run it to get bin/buildout. Note: you can chose which Python version you want to use by running bootstrap.py with it. All other scripts under bin/ will be generated by buildout and will use the same Python interpreter.

$ wget http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py
$ python bootstrap.py
Creating directory '.../AlliterationSharing/bin'.
Creating directory '.../AlliterationSharing/parts'.
Creating directory '.../AlliterationSharing/eggs'.
Creating directory '.../AlliterationSharing/develop-eggs'.
Generated script '.../AlliterationSharing/bin/buildout'.

Run bin/buildout to install Pylons into your sandbox.

$ bin/buildout
Installing pylons.
Generated script '.../AlliterationSharing/bin/paster'.
Generated interpreter '.../AlliterationSharing/bin/python'.

Aside: buildout has this very nice feature where it can share Python packages between projects. This will save you enormous amounts of time that would otherwise be spent downloading and unpacking eggs. To make use of this facility, create a file ~/.buildout/default.cfg with

$ cat ~/.buildout/default.cfg 
[buildout]
eggs-directory = /home/mg/tmp/buildout-eggs
# XXX replace /home/mg with the full path of *your* home directory
# it would be much nicer if buildout let me use ~ or $HOME
# see https://bugs.launchpad.net/zc.buildout/+bug/190260

Another useful trick is to pass the -N flag to bin/buildout, which will tell it not to bother looking for newer versions of packages on the Internet when there's already an existing version installed in your eggs directory.

Back to business: now you've got two new scripts: bin/python and bin/paster. You can use the first one to play with the interactive Python console where you can now import pylons and all the dependencies; it has no other value.

Now is a good point to add the files you've created into a version control system. I'll arbitrarily use Bazaar.

$ bzr init .
$ bzr add bootstrap.py buildout.cfg
$ bzr ignore bin parts eggs develop-eggs .installed.cfg
$ bzr commit -m "Create AlliterationSharing project"

Run bin/paster create -t pylons to create a skeleton project.

$ bin/paster create -t pylons asharing
$ bzr ignore *.egg-info
$ bzr add asharing
$ bzr commit -m "Generated project files with paster create"

Now paster creates a directory structure that I don't like:

AlliterationSharing/
  buildout.cfg
  bin/
  asharing/
    setup.py
    README.txt
    MANIFEST.in
    asharing/
      __init__.py
      config/
      controllers/
      templates/
      public/

I'd like the README and setup.py to be in the top level, and I dislike repeating 'asharing' twice in directory names. I'll move some files around

$ cd asharing/
$ bzr mv development.ini docs MANIFEST.in README.txt setup.* test.ini ../
$ bzr rm ez_setup.*
$ cd ..
$ bzr mv asharing src
$ bzr ci -m "Moved some files around"

Now the tree looks like this:

AlliterationSharing/
  buildout.cfg
  setup.py
  README.txt
  MANIFEST.in
  bin/
  src/
    asharing/
      __init__.py
      config/
      controllers/
      templates/
      public/

We have to tell setup.py where to find the source tree

$ bzr diff
=== modified file 'MANIFEST.in'
--- MANIFEST.in	2009-09-13 13:04:00 +0000
+++ MANIFEST.in	2009-09-13 13:05:59 +0000
@@ -1,3 +1,3 @@
-include asharing/config/deployment.ini_tmpl
-recursive-include asharing/public *
-recursive-include asharing/templates *
+include src/asharing/config/deployment.ini_tmpl
+recursive-include src/asharing/public *
+recursive-include src/asharing/templates *

=== modified file 'setup.py'
--- setup.py	2009-09-13 13:04:00 +0000
+++ setup.py	2009-09-13 13:04:40 +0000
@@ -17,7 +17,8 @@
         "SQLAlchemy>=0.5",
     ],
     setup_requires=["PasteScript>=1.6.3"],
-    packages=find_packages(exclude=['ez_setup']),
+    packages=find_packages('src', exclude=['ez_setup']),
+    package_dir={'': 'src'},
     include_package_data=True,
     test_suite='nose.collector',
     package_data={'asharing': ['i18n/*/LC_MESSAGES/*.mo']},

(I'm not sure if you also need to change package_data and/or setup.cfg; it's possible that I left i18n in a broken state. Can somebody comment on this?)

And we have to tell buildout that we've got a new Python package to enable in the project environment

$ bzr diff buildout.cfg 
=== modified file 'buildout.cfg'
--- buildout.cfg	2009-09-13 12:57:21 +0000
+++ buildout.cfg	2009-09-13 13:08:05 +0000
@@ -1,8 +1,10 @@
 [buildout]
+develop = .
 parts = pylons
 
 [pylons]
 recipe = zc.recipe.egg
 eggs = Pylons
        PasteScript
+       asharing
 interpreter = python

Now you can re-run bin/buildout and start your hello-world project

$ bzr commit -m "Include the new package in the build"
$ bin/buildout -N
$ bin/paster serve --reload development.ini

Happy hacking!

To be continued: telling buildbot to create bin/test; using ctags and omelette.

posted at 16:13 | tags: , , | permanent link to this entry | 17 comments
I'd also add:

download-cache = /home/mg/tmp/buildout-cache

to the .buildout/default.cfg (this helps to save trafic when you tell buildout to get newest eggs, or want to install your product  on a different version of python)

and remove

setup_requires=["PasteScript>=1.6.3"],

line from setup.py (I dislike setuptools downloading Paste all the time into the top directory in my project)

also - you might keep in mind that if you want to use actions like
"bin/paster controller" or "bin/paster shell" you have to do:

bin/paster --plugin=AlliterationStorage

or at least

bin/paster --plugin=Pylons

to have access to them. It's because your egg-info is generated in
src, while paster only looks for it in the directory it is executed
from.
posted by Ignas at Mon Sep 14 13:55:25 2009
Thank you about the paster --plugins tip!  I was always doing

cd src; ../bin/paster controller ...; cd ..
posted by Marius Gedminas at Mon Sep 14 14:01:45 2009
What? A makefile to run buildout? That really surprised me. Buildout itself is basically a make-replacement, so writing a makefile to run "python bootstrap.py;bin/buildout" looks strange to me.

That would be the equivalent of writing a makefile to run "./configure;make;make install".


I must say that the paster line is somewhat elaborate, so a wrapper around that is handy.  Was that the main reason for the makefile?
posted by Reinout van Rees at Wed Sep 16 12:14:02 2009
Reinout: they're both build tools, but the things you can do easily with buildout and the things you can do easily with a Makefile have little overlap.

The Makefile is not required, but I think it may be helpful.  Make is a widely-known tool; buildout isn't yet (and may never be, being confined to a smaller niche).  People see a Makefile, they know they can run 'make'.  python bootstrap.py && bin/buildout is not that obvious.

I admit that the paster line was the final straw for me personally.  I wanted to have buildout create a bin/run as a simple shell script (or a Python equivalent) running that paster line.  I balked at trying to figure out how to do that with buildout (uh, I need a recipe ... how do I find it?  Oh dear.  Topic for a new lazyweb post, I suppose).
posted by Marius Gedminas at Wed Sep 16 13:50:57 2009
I use:

bin/paster serve development.ini --reload --monitor-restart

so that the server would not stop after the first syntax error you save to disk.

Also - scripts depend on buildout.cfg and setup.py, because you want to rerun buildout every time any of those change.
posted by Ignas Mikalajūnas at Wed Sep 16 14:57:15 2009
Marius-
Having started messing with tags I'm curious.  Do you only use it for Python in your Pylons project?  Do you have it working with html/css/js?

cheers
posted by matt harrison at Wed Sep 16 16:47:58 2009
@Matt: exuberant-ctags supports 41 languages; JavaScript and HTML are in that list (I had no idea!), CSS isn't.

For some reason the tags db built by z3c.recipe.tag doesn't contain anything about .js/.html files in my project's public/ subdir, I don't know why.  I see a few tags about Weave's HTML documentation; it appears that ctags indexes <a name="identifier">.

Previously I've found --extra=+q (create tags for each source file) useful (you can do :tag yourcontrollername.py), but this feature seems to be broken in ctags 5.7.
posted by Marius Gedminas at Wed Sep 16 17:20:43 2009
Hmm, following these instructions in Ubuntu Jaunty worked fine until the bit where I tried to run "./bin/paster create -t pylons asharing": paster complains about the installed version of Mako being too old. I have Mako 0.2.2 installed systemwide, but buildout downloaded a more recent version which it put in the eggs dir. For some reason the systemwide version overrides that. Any idea how to fix that?
posted by Ilkka at Sat Sep 19 20:21:56 2009
This is one of the advantages that virtualenv has over buildbot: virtualenv can ignore packages installed system-wide (if you create the virtualenv with the --no-site-packages flag).  I believe buildbot will gain that functionality eventually (I heard there's a branch with a prototype, although I must admit I'm not following buildout's development closely).  I personally avoid that problem by not installing frequently-changing packages globally. ;)

A simple solution is to use virtualenv in conjunction with buildout:

$ virtualenv --no-site-packages ./venv
$ ./venv/bin/python bootstrap.py

Everything else is the same:

$ ./bin/buildout ...

Virtualenv is nice in that it is a single self-contained python script that you can download and run from the current directory.  Sort of like bootstrap.py.

My ex-coworker Ignas Mikalajūnas once built a hybrid bootstrap.py that set up both virtualenv and buildout.  I think the SchoolTool project uses it.
posted by Marius Gedminas at Sat Sep 19 20:46:15 2009
Thanks for the clarification, I guess I sort of misunderstood the roles of buildout and virtualenv the first time round :)

What should one commit to VC for the combined virtualenv + buildout setup? Buildout.cfg naturally, but perhaps also the things that the virtualenv invocation creates?
posted by Ilkka at Sun Sep 20 13:50:46 2009
I'm also running into a problem with the combined virtualenv + buildout setup: the buildout invocation fails with an internal error while installing Pylons:

  File "/home/.../venv/eggs/zc.buildout-1.4.1-py2.6.egg/zc/buildout/easy_install.py", line 1109, in _pyscript
  open(dest, 'w').write(contents)
IOError: [Errno 26] Text file busy: '/home/.../venv/bin/python'
posted by Ilkka at Sun Sep 20 13:53:52 2009
No, files that are generated during the build process should never be checked into a VCS.

I think your combined problem is that you're trying to create a virtualenv in the same directory where buildout lives, so both of them are trying to maintain the bin/ subdirectory.  Perhaps this is possible: edit buildout.cfg and change

  interpreter = python

with

  interpreter = py

so that buildout won't try to overwrite virtualenv's bin/python.  If I'm wrong about my guess, then I don't know why that failure occurred.

I personally try to keep buildout and virtualenv-maintained directories separate, because I'm afraid that too much magic in a single place will make my brain explode. ;)
posted by Marius Gedminas at Sun Sep 20 15:01:55 2009
When I am using virtual env and buildout I do it like this:

mkdir sandbox
cd sandbox
virtualenv python
python/bin/python bootstrap.py

this avoids the clashes.

As for the "special" bootstrap.py used by schooltool project - http://pow.lt/bootstrap.py

It combines virtualenv and buildout bootstrap in one script, or at least tries to ;) Works for me though...
posted by Ignas Mikalajūnas at Sun Sep 20 17:34:57 2009
I think when I created the tags recipe, there were problems generating tags for javascript used in Zope3, I think exuberant-ctags were crashing so I disabled javascript tags.

Don't know about html.

Also - I keep meaning, but fail to find the time to update idutils file to include mako templates when generating the index.
posted by Ignas Mikalajūnas at Sun Sep 20 17:39:02 2009
I added this part to get a ./bin/serve command:

[paster-serve]
recipe = zc.recipe.egg
eggs = ${pylons:eggs}
initialization = sys.argv[1:] = ['serve', '--reload', 'ini/development.ini']
scripts = paster=serve
posted by Wyatt Baldwin at Wed Sep 23 22:37:27 2009
Wyatt: awesome!  I've been wanting a 'bin/run' for a long time, and was going to go look for a recipe Some Day Real Soon Now.
posted by Marius Gedminas at Thu Sep 24 11:29:13 2009
Here's another way to create a `paster serve` run command:


[paster-serve]
recipe = zc.recipe.egg
eggs = ${pylons:eggs}
arguments = args=['serve', '--reload', 'ini/development.ini']
scripts = paster=serve


The value of `arguments` is Python source that's inserted into the bin/serve script file as the arguments to the entry point function.

Found here: http://pypi.python.org/pypi/zc.recipe.egg#script-generation
posted by Wyatt Baldwin at Sat Sep 26 01:41:27 2009

Name (required)


E-mail (will not be shown)


URL


Comment (some HTML allowed)