Florian's rambling place

Common version constraints for buildout and pip

published 2017-03-18 12:55:00

Recent releases of setuptools started to pull in requirements as packages. This caused issues when installing zc.buildout with pip in a virtualenv and there are packages installed which require one of those packages and there was a conflicting version pin. Namely this happened with pyparsing in my case.

I wrote the following buildout.extensionscripts helper:

def getVersions(buildout):
    from pip.req import parse_requirements
    import os
    import zc.buildout
    fn = os.path.join(
    constraints = parse_requirements(fn, constraint=True, session=object())
    versions = buildout._raw['versions']

    def set_version(constraint, spec):
        if constraint.name in versions and versions[constraint.name] and versions[constraint.name] != spec:
                "Version pin %s out of sync with [versions] %s = %s." % (
                    constraint, constraint.name, versions[constraint.name]))
        elif constraint.name in versions and not versions[constraint.name]:
            buildout._logger.warn("Not setting version constraint for possible develop egg '%s'." % constraint.name)
        versions[constraint.name] = spec

    for constraint in constraints:
        specs = set(spec._spec for spec in constraint.specifier._specs)
        specs = sorted(specs)
        if len(specs) == 1 and specs[0][0] == u'==':
            set_version(constraint, specs[0][1])
            buildout._error("Don't know how to set %s in [versions]." % constraint)

This way there is one version-constraints.txt in the buildout root which I use with pip install -c version-constraints.txt -U setuptools during setup of the virtualenv.

The versions.cfg file for buildout now looks like this:

extension-scripts +=

# mostly set by getVersions from version-constraints.txt
# but we need to pin buildout stuff that's used before getVersions is called

# Buildout infrastructure
buildout.extensionscripts = 1.0
zc.buildout = 2.5.3
zc.recipe.egg = 1.3.2
mr.developer = 1.38

Currently only == version constraints are supported, but others are possible as well and just need to be added properly in [versions].

If you use mr.developer, then you need at least version 1.38 for a fix, otherwise develop packages won't be picked up.

Multiple Python versions with SublimeText-flake8

published 2017-03-17 10:50:00

I couldn't get linting for different Python versions working with SublimeLinter-flake8. It only ever worked with either Python 2.x or Python 3.x.

The following is the way I got it working. If anyone knows a better way, please let me know.

Thanks to the changes in PR #47 and the following script, I was able to get it to work.

import os
import sys

python = "python%s.%s" % sys.version_info[:2]
os.execvp(python, [python, '-m', 'flake8'] + sys.argv[1:])

The flake8 package needs to be installed in the relevant Python versions.

To debug, follow the troubleshooting guide of SublimeLinter3.

Let’s Encrypt certificates for private servers

published 2016-01-30 10:38:00

At the end of January 2016 Let's Encrypt fixed the last bug which prevented letsencrypt-remote from authenticating via DNS.

It is now possible to generate TLS certificates for private servers if you can delegate name resolution via your DNS provider.

Let's say you want to generate a certificate for use on your Laptop. You first need to create a subdomain pointing to Something like this in your zone for localhost.example.com:

localhost  IN A

Additionally you need to delegate _acme-challenge.localhost.example.com to an IP which is reachable by the Let's Encrypt servers and where you can access DNS port 53. For example your web server if you have one:

_acme-challenge.localhost  IN NS  www

If you use your web server, you could use ssh to forward the port 8053 to your laptop:

$ ssh root@example.com -R 8053:localhost:8053

You then need to use something to forward remote UDP packets from port 53 of the server to the forwarded TCP port 8053, for example socat:

# socat -T15 udp4-recvfrom:53,reuseaddr,fork tcp:localhost:8053

Now on your laptop you can use letsencrypt-remote to create a certificate using DNS:

% letsencrypt-remote --dns localhost.example.com

If everything worked correctly:

  • letsencrypt-remote should have started a DNS server
  • requested certificate signing
  • the Let's Encrypt servers should be delegated to your server for the DNS query
  • the request be forwarded to your laptop
  • the answer sent back
  • and you got a signed certificate for localhost.example.com

This also works for other private ip ranges like or

New statically generated website using Lektor

published 2016-01-18 11:40:00

After several years and multiple attempts, I finally have a website again.

I tried quite a few static website generators, including starting my own, until I finally settled with the recently announced Lektor.

The main addition to Lektor is my reStructuredText plugin lektor-rst. I had all the old content in rst already and I favour it over markdown.

The source code for my website is available on GitHub at https://github.com/fschulze/florian-schulze.

Proper exit code for buildout generated scripts

published 2010-09-24 15:30:00

I recently ran into the issue that our Hudson installation didn't report test failures. After digging for a bit, I discovered, that scripts installed by zc.recipe.egg don't return the exit code for some cases. There is a bugreport at https://bugs.launchpad.net/zc.buildout/+bug/164629. In our case the generated coverage script depended on this and because the sys.exit was missing, the test failures were masked.

The following is a way to fix this in your buildouts now until it's fixed in zc.buildout:

extensions = buildout.extensionscripts
extension-scripts =

The patchScriptGeneration function in src/buildout-utils.py looks like this:

def patchScriptGeneration(buildout):
    from zc.buildout import easy_install
    if not 'sys.exit(' in easy_install.script_template:
        easy_install.script_template = easy_install.script_template.replace(