Monday, November 15, 2010

allCombinations: leaveOut

leaveOut

Ok, on my previous post I brought up a small Python module I was inspired to throw together while writing tests. It's still sitting in a gist, though I'll probably move it to a real repo before too long:

https://gist.github.com/674715

So I admit, as I was posting it, it occurred to me that to a large extent this stuff could be replaced with a nested for loop. For instance, this:

for lst in allCombinations([1, 2, oneOf(3,4), oneOf(5,6)]):

can be pulled off with:

for x in (3, 4):
for y in (5, 6):
lst = [1, 2, x, y]

Not a huge gain necessarily on my part. So as I was using it in my testing I realized I once again had engineered something for a tiny use that, neat as it is, could have been done much faster by brute force. But then, I realized another thing I could add that would make my code much more concise. I've added another keyword called "leaveOut". It lets you opt to not have the element show up at all. Here's an example:

allCombinations([1,2, oneOf(3, leaveOut), oneOf(4, leaveOut)])

This will return:

[ [1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2] ]
And of course, the "leaveOut" case will omit dictionary entries and object data members as well.

BTW

I should also mention another use case I thought of, "leaveOut" aside, that might be a real pain to do without an aide such as allCombinations, which is dynamically created structures, with an arbitrary amount of variables:

allCombinations( [ oneOf(1, 2) ] * x )

I've just generated all possible lists of either 1 or 2, of an arbitrary length, which can be set at runtime. Or how about something a bit more fun:

allCombinations( [ oneOf( *range(y) + [leaveOut] ) for y in range(x) ] )
Taking all combinations of lists of length x, where each element can equal any integer from zero to its index, and then adding combinations where items are omitted. Not horribly useful, but complicated.

To do these in a standard way you'd need x for loops, which you can't do directly. (I bet you could do it with recursion).

Fixes

I'll also mentione that I fixed a couple general errors. oneOf on Data members had a big bug. And now if you don't have oneOf in your structure, allCombinations just returns a list containing only the original structure, instead of looping to death.

Friday, November 12, 2010

allcombinations - generating combinations of python structures

Alright, on a whim I decided to make another tricky thing in Python. This one is less of a hack, and is more likely to be useful.

So let's say you want to do something with all combinations of... something.

[5, 6, oneOf(7,8,9), oneOf(10, 11, "shazaam")]

So you want to turn this structure into all the possibilities represented within:

[
[5, 6, 7, 10],
[5, 6, 7, 11],
[5, 6, 7, "shazaam"],
[5, 6, 8, 10],
[5, 6, 8, 11],
[5, 6, 8, "shazaam"],
[5, 6, 9, 10],
[5, 6, 9, 11],
[5, 6, 9, "shazaam"],
]

Well with allcombinations, you can do just that:

from allcombinations import allCombinations

allCombinations( [5, 6, oneOf(7,8,9), oneOf(10, 11, 12)] )

Here it is. The gist includes more complicated example.

Features
The oneOf should be able to reside almost anywhere in your expression. It can be in a list (as seen here), in a dict, or even in the attribute of an object. It can also reside in a list within a dict within an object's attribute, etc, as long as it's nowhere within an unsupported container type.

Limitations:
This will only work if oneOf resides in a list, dict, or an object's attributes. It shouldn't work anywhere within a set, or any other structure I can't think of. If you try it in something unsupported, the oneOf object should just stick around in all your combinations.

I'm probably actually going to use this, particularly (again) for testing. Anyone else think they'd find it useful? Should I package it?

Testing (Django) views with pyquery

PyQuery is basically what it sounds like. Using jQuery syntax, you can query and even manipulate XML files. Obviously we don't (yet!) have Python in the browser, so it's not useful in the same domain, but it can help out in dealing with XML in general, in the same way as, say, lxml, but without having to learn about things like ElementTree for simple cases. It's particularly good for XHTML because jQuery (and thus PyQuery) uses CSS syntax for class= and id=. Which brings me to how I'm using it:

from django.test.client import Client
from pyquery import PyQuery
from django.test.testcases import TestCase

...

class TestSomeViews(TestCase):

def testAView(self):

client = Client()

...

response = client.get("/someurl/")

self.assertTrue("expected text" in PyQuery(response.content)("#someid").html() )

(If you're unfamiliar with testing Django views, see this.)

For some basic tests, you can just search the entire response html, and not have to worry about where it shows up. But suppose you're searching for a username in a particular part of your response. You're pretty likely to find that username elsewhere on the page, so you have to select out the part of the file you expect it. I think this is much easier than using a regex.

So what this bit of PyQuery does is find the tag with the id of "someid" (presumably there's only only one, being an id), and returns the html within that tag. (If you search for a class that returns multiple tags, it seems that a simple call to .html() will only return the contents of the first one. This very well may match jQuery's behavior, I'm admittedly not that familiar, but just a head's up.) For more details look at the PyQuery API.