Permalink
Browse files

Add fieldlookup module

FieldLookup instances allow for string based lookups and comparisons against objects.

For example: fieldlookup_instance(some_obj, 'cat__color__iexact', 'blue', True) would yield a case insensitive comparison between instance.cat.color and the string 'blue').
  • Loading branch information...
1 parent 27c6df9 commit e3715ad5e117bf817a37474d72a57da38734f764 @thomasw committed Nov 24, 2012
Showing with 128 additions and 0 deletions.
  1. +60 −0 querylist/fieldlookup.py
  2. +68 −0 querylist/tests/fieldlookup_tests.py
View
60 querylist/fieldlookup.py
@@ -0,0 +1,60 @@
+class FieldLookup(object):
+ def __init__(self):
+ self.default_comparator = FieldLookup._exact
+ self.comparators = {
+ 'exact': FieldLookup._exact,
+ 'iexact': FieldLookup._iexact,
+ }
+
+ def __call__(self, instance, lookup, compare_value=None, compare=False):
+ """Return lookedup value or compares it against another.
+
+ Keyword arguments:
+ instance -- the instsance to preforma a lookup against
+ lookup -- a string representing a chain of lookups to preform
+ compare_value -- a value to compare the lookedup value against
+ compare -- bool describing whether to preform a lookup or a comparison
+
+ """
+ lookup_chain, comparator = self._parse_lookup_string(lookup)
+ value = self._resolve_lookup_chain(lookup_chain, instance)
+
+ if not compare:
+ return value
+
+ return comparator(value, compare_value)
+
+ def _parse_lookup_string(self, lookup_chain):
+ """Convert a lookup string to a lookup_chain, lookup method tuple."""
+ lookup_chain = lookup_chain.split('__')
+ comparator = self.default_comparator
+
+ # Only look for a lookup method if the lookup chain is larger than 1
+ if len(lookup_chain) <= 1:
+ return lookup_chain, comparator
+
+ # Get the correct lookup_method if the last value in the lookup
+ # chain is a lookup method specifier
+ if lookup_chain[-1] in self.comparators:
+ comparator = self.comparators.get(lookup_chain.pop(-1))
+
+ return lookup_chain, comparator
+
+ def _resolve_lookup_chain(self, chain, instance):
+ """Return the value of inst.chain[0].chain[1].chain[...].chain[n]."""
+ value = instance
+
+ for link in chain:
+ value = getattr(value, link)
+
+ return value
+
+ @staticmethod
+ def _exact(value1, value2):
+ """Compare two values."""
+ return value1 == value2
+
+ @staticmethod
+ def _iexact(value1, value2):
+ """Convert two values to lowercase and compare them."""
+ return value1.lower() == value2.lower()
View
68 querylist/tests/fieldlookup_tests.py
@@ -0,0 +1,68 @@
+import unittest2
+
+from querylist import BetterDict
+from querylist.fieldlookup import FieldLookup
+
+
+class FieldLookupTests(unittest2.TestCase):
+ def setUp(self):
+ self.fl = FieldLookup()
+ self.instance = BetterDict({
+ 'foo': 1,
+ 'bar': {
+ 'cat': True,
+ 'dog': False,
+ 'meh': {
+ 'bleh': [1, 2]
+ }
+ },
+ 'sauce': 'Mustard',
+ 'bleh': [1, 2, 3, 4]
+ })
+
+
+class FieldLookupParseLookupTests(FieldLookupTests):
+ """FieldLookups._parse_lookup_string"""
+ def test_returns_single_value_list_for_simple_lookups(self):
+ self.assertEquals(self.fl._parse_lookup_string('yay')[0], ['yay'])
+
+ def test_splits_relational_lookups_properly(self):
+ self.assertEquals(
+ self.fl._parse_lookup_string('yay__bar')[0], ['yay', 'bar'])
+
+ def test_defaults_to_exact_for_the_lookup_method(self):
+ self.assertEquals(
+ self.fl._parse_lookup_string('yay')[1], self.fl._exact)
+
+ def test_correctly_determines_the_lookup_method_if_not_the_default(self):
+ self.assertEquals(
+ self.fl._parse_lookup_string('yay__iexact')[1], self.fl._iexact)
+
+
+class FiedLookupResolveLookupChainTests(FieldLookupTests):
+ """FieldLookup._resolve_lookup_chain"""
+ def test_returns_the_correct_value_for_simple_lookup_chains(self):
+ self.assertEquals(
+ self.fl._resolve_lookup_chain(['foo'], self.instance), 1)
+
+ def test_returns_the_correct_value_for_multli_link_lookup_chains(self):
+ self.assertEquals(
+ self.fl._resolve_lookup_chain(['bar', 'dog'], self.instance),
+ False
+ )
+
+
+class FieldLookupCallTests(FieldLookupTests):
+ """FieldLookup()"""
+ def test_returns_looked_up_value_when_passed_a_lookup_and_instance(self):
+ self.assertEqual(
+ self.fl(self.instance, 'bar__meh__bleh'), [1, 2])
+
+ def test_returns_comparison_result_when_compare_is_set_to_true(self):
+ self.assertEqual(
+ self.fl(self.instance, 'foo', 1, True), True
+ )
+
+ def test_compares_based_on_comparator_specified_in_lookup_string(self):
+ self.assertEqual(
+ self.fl(self.instance, 'sauce__iexact', 'mustard', True), True)

0 comments on commit e3715ad

Please sign in to comment.