Retry failed tests
To have py.test automatically re-run failed tests when the code changes, until they pass, install pytest-xdist and run py.test -f.
See also xdist: pytest distributed testing plugin.
Skip tests
To mark a test to be skipped, use the decorators in pytest.mark, such as:
- pytest.mark.skipif("condition"): skip if "condition" evals to True.
- pytest.mark.xfail: mark the test as being expected to fail.
You can also skip tests if a module cannot be imported, by using module = pytest.importorskip("module_name").
Finally, you can manually skip a test by calling pytest.skip("reason") inside of the test.
See also Skip and xfail: dealing with tests that can not succeed, and more information about marking test functions with attributes.
Add command-line options
def pytest_addoption(parser):
parser.addoption("--funnyoption", action="store_true",
help="enable the funny option")
def pytest_generate_tests(metafunc):
if metafunc.config.option.funnyoption:
# do something
See also Parametrizing tests.
I wrote simplecurry, which provides a very simple curried decorator that makes curried versions of Python functions, just by returning a functools.partial. There are alternative implementations on PyPi, but they seem unnecessarily complex to me.
You can install it by running pip install simplecurry.
Example
This is an example on how to use it:
from simplecurry import curried
@curried
def add(a, b, c):
return a + b + c
add(1)(2)(3) # Returns 6
add(1, 2)(3) # Returns 6
add(1)(2, 3) # Returns 6
add(1, 2, 3) # Returns 6
There’s a pattern I’ve seen a few times, even in code of experienced programmers. It’s better explained by example:
def do_stuff(...):
if some_very_long_conditions:
actually_do_stuff
“do stuff” is, like the cake, a lie: it’s not really “do stuff”, because it will only sometimes do the stuff. This goes against the Principle of Least Surprise, and it makes the code harder to work with: with code like this, function names can no longer be trusted, so others will have to read all the code to make sure that it does what they think it does. And after hours of debugging, it comes that “wow” moment where everything falls into place and you see that that assumption you made is false.
This naming problem hides a deeper structural problem, which is easy to see once you tackle the naming problem. The mental process goes like this:
Maybe we do need a function that sometimes does stuff, but then we should name it appropriately. How about maybe_do_stuff? Nah, that’s silly. What does “maybe” mean?
We look at our code and we see that in some occasions we don’t need to do stuff. Ah, then a better name would be do_stuff_if_needed!
But, wait a second, what does “if needed” mean? Well, sometimes stuff is already done, and in other cases we operate in read-only mode, and we don’t want to do anything that has side effects, like “stuff” and “other_stuff”. We look into the code and, sure enough, a few lines below we see that decision taken for the do_other_stuff. Hey, “other stuff”? But our function only deals with “stuff”!
Maybe the name do_stuff was correct after all, and it’s the decision what’s wrong and it should be moved somewhere else.
But then... the function only has one instruction left (actually_do_stuff), and it’s a very simple one, so maybe the function is not needed at all!
The improved version of the code looks like this:
if not readonly:
do_other_stuff()
...
if stuff_enabled:
actually_do_stuff()
Now the code is not surprising, it’s shorter, delicious and moist, and it means every bit of what it says.
tl;dr: if you call your function “do_something”, make sure it does it every time (or raises an error). If not, change its name. Sometimes this will make you see deeper problems, which you can fix and you will get a much better code. And you get cake.
I had a problem due to a difference of the configuration of two servers that should have been configured the same. I thought it would be useful to have a tool that would allow me to quickly audit the configurations of a collection of servers, namely:
- Have a list of all config files that must be present in the servers.
- Check these in all servers, and make sure that they are all equal, and if they are different, see why they are different and whether that's what we want.
This tool displays, for each configuration file:
- Which servers have it and which don't (servers that don't have the file will appear in italics.)
- The servers that do have the file, will be grouped so that servers with identical file contents are together.
- For each group of servers, you'll see a diff of the file against the same file in the previous group of servers.
- Files that are equal in all servers will appear in italics.
The app is in a single file. To get it running either download the watchconf.py or install it with pip install watchconf (http://pypi.python.org/pypi/watchconf).
Here's how to use it:
watchconf -f FILES -s SERVERS [-p PORT] [-u|--username USERNAME] [-d|--debug]
Starts an HTTP server that shows the differences between the specified files
in the specified servers.
options:
-f --files Files to compare
-s --servers Servers to compare
-p --port Port to listen to (default: 5000)
-u --username ssh username (default: use the current user)
-d --debug Debug mode
-h --help display help
For example:
watchconf -s server1,server2 -f file1,file2
Watchconf expects a memcache daemon listening to the default port on localhost.
Sometimes we add flexibility that we don’t need, just because it’s easy and maybe in the future we’ll need it. But it’s not always for free: often we pay a price of maintainability for this flexibility.
Let’s consider Items such as an Item always belongs to an ItemCollection. Another restriction is that two Items that have the same name cannot be in the same ItemCollection.
During design, even though we are sure that in the foreseeable future an Item will always belong to only one ItemCollection, we decide to allow Items to belong to more than one ItemCollection. “It’s easy to do now”, we think, “and so we are prepared for the future. We are flexible!”
However, this feature prevents us from having a unique index that would ensure that Item names do not repeat in an ItemCollection.
The index cannot be in the item table, because it's legit to have two items with the same name, if they belong to different groups.
mysql> desc item;
+--------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(80) | YES | | NULL | |
+--------------------+-------------+------+-----+---------+----------------+
The index cannot be in item_item_collection either, because we don’t have the name column, and it would not prevent two different items with the same name being present in the same collection.
mysql> desc item_item_collection;
+--------------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_collection_id | int(11) | NO | MUL | NULL | |
+--------------------+---------+------+-----+---------+----------------+
Without this unwanted, unused and uncared for feature, these two tables would be one, where it would be trivial to define a unique key:
mysql> desc item;
+--------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(80) | YES | | NULL | |
| item_collection_id | int(11) | NO | UNI | NULL | |
+--------------------+-------------+------+-----+---------+----------------+
Also, the design and the code would be simpler.
On Friday, March 04, 2011
about
code,
geek.
I stumbled upon some overcomplicated code (that failed) to parse the ip address out of an ec2 hostname (basically, get 1.2.3.4 from ec2-1-2-3-4-somejunk), and I replaced it with something simpler and which works.
Now that would be totally unremarkable; however, it got me thinking: the code that I replaced was overcomplicated, yes, but not THAT overcomplicated. If we make something overcomplicated we should go all in, or not at all!
So I've just written it as complicated as I could. You'll see that it's actually longer than it should be, but it works.
Here's proof that it works:
% echo -n ec2-46-111-123-119-aoe3usome5junk | beef parse_ip
46.111.123.119
And here's the brainfuck source:
Read more...
If you are using zsh, with the magic of vcs_info you can add mercurial-related information to your shell prompt, for example like:
- Current bookmark (white if there are uncommitted changes, blue otherwise).
- Current branch.
- Repository name.
- Path within the repository.
When I cd away from the repository, the regular prompt comes back. You can also have always the regular prompt and just have it add some extra info if you are in a repository... the fun is limitless!
The same information is also available for git, bzr, etc.
See my .zshrc for the details on how to set it up.
If you’re on ubuntu, you’ll probably need zsh-beta.
If you'd like to run pyflakes on your code before you commit it, and check that you didn't forget to remove pdb calls, here's how to:
sudo pip install pyflakes
it needs the last version of pyflakes: upgrade if it tells you so.
Add these to your .hgrc, under [hooks]
pretxncommit.pyflakes = python:hghooks.code.pyflakeshook
pretxncommit.pdb = python:hghooks.code.pdbhook
Voilà!
% hg ci -m test a.py
/tmp/hghookspoLu0k-r8221/a.py:1: 'myproject' imported but unused
/tmp/hghookspoLu0k-r8221/a.py:2: undefined name 'some_undefined_variable'
2 warnings found in revision 8221
transaction abort!
rollback completed
abort: pretxncommit.pyflakes hook failed
For more detailed information: http://pypi.python.org/pypi/hghooks/
If you are running your project via mod_wsgi, surely you want to restart Apache often. Just put the following script somewhere in your path and assign a hotkey to it (e.g. System/Keyboard shortcuts if you use Gnome). You'll get a nice Notify-OSD balloon with any restart messages.
#!/usr/bin/python
from pynotify import Notification, init
import commands
init("cli notify")
n = Notification('apache restart', commands.getoutput("gksudo apache2ctl restart"))
n.show()
Recalling previous commands:
- The last command !!
- This is equivalent to !-1
- The command before the last !-2
- The latest command that starts with "perl" !perl
- The latest command that contains "something": !?something
Arguments:
- The first argument of the last command: !!:1
- This is equivalent to !!:^
- The last argument of the last command: !!:$
- The arguments 3 to 5 of the last command: !!:3-5
- All the arguments of the last command: !!:*
- This is equivalent to !!:^-$
It doesn't have to be of the last command, you can get the first argument of the last command that starts with "perl": !perl:1
On all these, you can apply filters:
- :e returns the extension of a file
- :r returns all but the extension
- :h returns the path part of a filename
For example:
- The extension of the filename that appears as the first argument in the previous line: !!:1:e
- The opposite, return all but the extension: !!:1:r
- Return the path of the filename that appears as the first argument in the previous line: !!:1:h
Substitution:
- Change “a” into “b” in the last line: ^a^b
- For all occurrences: ^a^b^g