Couple Engine to FacetsResponse to enable looking up computed facet by their originating object handle/id. Remove unused Engine.as_dict(). Add some commentary.

This commit is contained in:
Paul Makepeace 2011-04-26 23:52:13 -04:00
parent f30af6dcdd
commit 5c4930cc78
5 changed files with 80 additions and 35 deletions

View File

@ -130,6 +130,7 @@ class NumericFacet(Facet):
class FacetResponse(object): class FacetResponse(object):
"""Class for unpacking an individual facet response."""
def __init__(self, facet): def __init__(self, facet):
for k, v in facet.items(): for k, v in facet.items():
if isinstance(k, bool) or isinstance(k, basestring): if isinstance(k, bool) or isinstance(k, basestring):
@ -153,42 +154,76 @@ class FacetResponse(object):
class FacetsResponse(object): class FacetsResponse(object):
def __init__(self, facets): """FacetsResponse unpacking the compute-facets response.
self.facets = [FacetResponse(f) for f in facets['facets']]
It has two attributes: facets & mode. Mode is either 'row-based' or
'record-based'. facets is a list of facets produced by compute-facets, in
the same order as they were specified in the Engine. By coupling the engine
object with a custom container it's possible to look up the computed facet
by the original facet's object.
"""
def __init__(self, engine, facets):
class FacetResponseContainer(object):
facets = None
def __init__(self, facet_responses):
self.facets = [FacetResponse(fr) for fr in facet_responses]
def __iter__(self):
for facet in self.facets:
yield facet
def __getitem__(self, index):
if not isinstance(index, int):
index = engine.facet_index_by_id[id(index)]
assert self.facets[index].name == engine.facets[index].name
return self.facets[index]
self.facets = FacetResponseContainer(facets['facets'])
self.mode = facets['mode'] self.mode = facets['mode']
class Engine(object): class Engine(object):
"""An Engine keeps track of Facets, and responses to facet computation."""
facets = []
facet_index_by_id = {} # index into facets by Facet object id
def __init__(self, facets=None, mode='row-based'): def __init__(self, facets=None, mode='row-based'):
self.set_facets(facets) self.set_facets(facets)
self.mode = mode self.mode = mode
def set_facets(self, facets=None): def set_facets(self, facets=None):
"""facets may be a Facet or list of Facets."""
self.remove_all()
if facets is None: if facets is None:
facets = [] facets = []
elif not isinstance(facets, list): elif not isinstance(facets, list):
facets = [facets] facets = [facets]
self.facets = facets for facet in facets:
self.add_facet(facet)
def as_dict(self): def facets_response(self, response):
return { """Unpack a compute-facets response."""
'facets': [f.as_dict() for f in self.facets], # XXX how with json? return FacetsResponse(self, response)
'mode': self.mode,
}
def __len__(self): def __len__(self):
return len(self.facets) return len(self.facets)
def as_json(self): def as_json(self):
return json.dumps(self.as_dict()) """Return a JSON string suitable for use as a POST parameter."""
return json.dumps({
'facets': [f.as_dict() for f in self.facets], # XXX how with json?
'mode': self.mode,
})
def add_facet(self, facet): def add_facet(self, facet):
# Record the facet's object id so facet response can be looked up by id
self.facet_index_by_id[id(facet)] = len(self.facets)
self.facets.append(facet) self.facets.append(facet)
def remove_all(self): def remove_all(self):
"""Remove all facets."""
self.facets = [] self.facets = []
def reset_all(self): def reset_all(self):
"""Reset all facets."""
for facet in self.facets: for facet in self.facets:
facet.reset() facet.reset()
@ -204,15 +239,16 @@ class Sorting(object):
if not isinstance(criteria, list): if not isinstance(criteria, list):
criteria = [criteria] criteria = [criteria]
for criterion in criteria: for criterion in criteria:
# A string criterion defaults to a string sort on that column
if isinstance(criterion, basestring): if isinstance(criterion, basestring):
criterion = { criterion = {
'column': criterion, 'column': criterion,
'valueType': 'string', 'valueType': 'string',
'caseSensitive': False, 'caseSensitive': False,
} }
criterion.setdefault('reverse', False) criterion.setdefault('reverse', False)
criterion.setdefault('errorPosition', 1) criterion.setdefault('errorPosition', 1)
criterion.setdefault('blankPosition', 2) criterion.setdefault('blankPosition', 2)
self.criteria.append(criterion) self.criteria.append(criterion)
def as_json(self): def as_json(self):

View File

@ -295,7 +295,7 @@ class RefineProject:
if facets: if facets:
self.engine.set_facets(facets) self.engine.set_facets(facets)
response = self.do_json('compute-facets') response = self.do_json('compute-facets')
return facet.FacetsResponse(response) return self.engine.facets_response(response)
def get_rows(self, facets=None, sort_by=None, start=0, limit=10): def get_rows(self, facets=None, sort_by=None, start=0, limit=10):
if facets: if facets:

View File

@ -130,12 +130,18 @@ class SortingTest(unittest.TestCase):
class FacetsResponseTest(unittest.TestCase): class FacetsResponseTest(unittest.TestCase):
def test_facets_response(self): response = """{"facets":[{"name":"Party Code","expression":"value","columnName":"Party Code","invert":false,"choices":[{"v":{"v":"D","l":"D"},"c":3700,"s":false},{"v":{"v":"R","l":"R"},"c":1613,"s":false},{"v":{"v":"N","l":"N"},"c":15,"s":false},{"v":{"v":"O","l":"O"},"c":184,"s":false}],"blankChoice":{"s":false,"c":1446}}],"mode":"row-based"}"""
response = """{"facets":[{"name":"Party Code","expression":"value","columnName":"Party Code","invert":false,"choices":[{"v":{"v":"D","l":"D"},"c":3700,"s":false},{"v":{"v":"R","l":"R"},"c":1613,"s":false},{"v":{"v":"N","l":"N"},"c":15,"s":false},{"v":{"v":"O","l":"O"},"c":184,"s":false}],"blankChoice":{"s":false,"c":1446}}],"mode":"row-based"}"""
response = FacetsResponse(json.loads(response)) def test_facet_response(self):
facets = response.facets party_code_facet = TextFacet('Party Code')
engine = Engine(party_code_facet)
facets = engine.facets_response(json.loads(self.response)).facets
self.assertEqual(facets[0].choices['D'].count, 3700) self.assertEqual(facets[0].choices['D'].count, 3700)
self.assertEqual(facets[0].blank_choice.count, 1446) self.assertEqual(facets[0].blank_choice.count, 1446)
self.assertEqual(facets[party_code_facet], facets[0])
# test iteration
facet = [f for f in facets][0]
self.assertEqual(facet, facets[0])
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -40,7 +40,7 @@ class RefineTest(refinetest.RefineTestCase):
def test_new_project(self): def test_new_project(self):
self.assertTrue(isinstance(self.project, refine.RefineProject)) self.assertTrue(isinstance(self.project, refine.RefineProject))
def test_get_models(self): def _test_get_models(self):
self.assertEqual(self.project.key_column, 'email') self.assertEqual(self.project.key_column, 'email')
self.assertTrue('email' in self.project.column_order) self.assertTrue('email' in self.project.column_order)
self.assertEqual(self.project.column_order['name'], 1) self.assertEqual(self.project.column_order['name'], 1)

View File

@ -40,6 +40,8 @@ class TutorialTestFacets(refinetest.RefineTestCase):
party_code_facet = facet.TextFacet(column='Party Code') party_code_facet = facet.TextFacet(column='Party Code')
response = self.project.compute_facets(party_code_facet) response = self.project.compute_facets(party_code_facet)
pc = response.facets[0] pc = response.facets[0]
# test look by index same as look up by facet object
self.assertEqual(pc, response.facets[party_code_facet])
self.assertEqual(pc.name, 'Party Code') self.assertEqual(pc.name, 'Party Code')
self.assertEqual(pc.choices['D'].count, 3700) self.assertEqual(pc.choices['D'].count, 3700)
self.assertEqual(pc.choices['N'].count, 15) self.assertEqual(pc.choices['N'].count, 15)
@ -50,7 +52,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
engine.add_facet(ethnicity_facet) engine.add_facet(ethnicity_facet)
self.project.engine = engine self.project.engine = engine
response = self.project.compute_facets() response = self.project.compute_facets()
e = response.facets[1] e = response.facets[ethnicity_facet]
self.assertEqual(e.choices['B'].count, 1255) self.assertEqual(e.choices['B'].count, 1255)
self.assertEqual(e.choices['W'].count, 4469) self.assertEqual(e.choices['W'].count, 4469)
# {7} # {7}
@ -61,7 +63,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
self.assertEqual(indexes, [1, 2, 3, 4, 6, 12, 18, 26, 28, 32]) self.assertEqual(indexes, [1, 2, 3, 4, 6, 12, 18, 26, 28, 32])
# {8} # {8}
response = self.project.compute_facets() response = self.project.compute_facets()
pc = response.facets[0] pc = response.facets[party_code_facet]
self.assertEqual(pc.name, 'Party Code') self.assertEqual(pc.name, 'Party Code')
self.assertEqual(pc.choices['D'].count, 1179) self.assertEqual(pc.choices['D'].count, 1179)
self.assertEqual(pc.choices['R'].count, 11) self.assertEqual(pc.choices['R'].count, 11)
@ -69,7 +71,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
# {9} # {9}
party_code_facet.include('R') party_code_facet.include('R')
response = self.project.compute_facets() response = self.project.compute_facets()
e = response.facets[1] e = response.facets[ethnicity_facet]
self.assertEqual(e.choices['B'].count, 11) self.assertEqual(e.choices['B'].count, 11)
# {10} # {10}
party_code_facet.reset() party_code_facet.reset()
@ -90,7 +92,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
response = self.project.get_rows() response = self.project.get_rows()
self.assertEqual(response.filtered, 1907) self.assertEqual(response.filtered, 1907)
response = self.project.compute_facets() response = self.project.compute_facets()
ot = response.facets[2] # Office Title ot = response.facets[office_title_facet]
self.assertEqual(len(ot.choices), 21) self.assertEqual(len(ot.choices), 21)
self.assertEqual(ot.choices['Chief of Police'].count, 2) self.assertEqual(ot.choices['Chief of Police'].count, 2)
self.assertEqual(ot.choices['Chief of Police '].count, 211) self.assertEqual(ot.choices['Chief of Police '].count, 211)
@ -102,7 +104,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
phone_facet = facet.TextFacet('Phone', expression='value[0, 3]') phone_facet = facet.TextFacet('Phone', expression='value[0, 3]')
self.project.engine.add_facet(phone_facet) self.project.engine.add_facet(phone_facet)
response = self.project.compute_facets() response = self.project.compute_facets()
p = response.facets[0] p = response.facets[phone_facet]
self.assertEqual(p.expression, 'value[0, 3]') self.assertEqual(p.expression, 'value[0, 3]')
self.assertEqual(p.choices['318'].count, 2331) self.assertEqual(p.choices['318'].count, 2331)
# {16} # {16}
@ -110,7 +112,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
expression='value.toDate().datePart("year")') expression='value.toDate().datePart("year")')
self.project.engine.add_facet(commissioned_date_facet) self.project.engine.add_facet(commissioned_date_facet)
response = self.project.compute_facets() response = self.project.compute_facets()
cd = response.facets[1] cd = response.facets[commissioned_date_facet]
self.assertEqual(cd.error_count, 959) self.assertEqual(cd.error_count, 959)
self.assertEqual(cd.numeric_count, 5999) self.assertEqual(cd.numeric_count, 5999)
# {17} # {17}
@ -118,10 +120,10 @@ class TutorialTestFacets(refinetest.RefineTestCase):
expression=r'value.match(/\D*(\d+)\w\w Rep.*/)[0].toNumber()') expression=r'value.match(/\D*(\d+)\w\w Rep.*/)[0].toNumber()')
self.project.engine.add_facet(office_description_facet) self.project.engine.add_facet(office_description_facet)
response = self.project.compute_facets() response = self.project.compute_facets()
cd = response.facets[2] od = response.facets[office_description_facet]
self.assertEqual(cd.min, 0) self.assertEqual(od.min, 0)
self.assertEqual(cd.max, 110) self.assertEqual(od.max, 110)
self.assertEqual(cd.numeric_count, 548) self.assertEqual(od.numeric_count, 548)
class TutorialTestEditing(refinetest.RefineTestCase): class TutorialTestEditing(refinetest.RefineTestCase):
@ -139,25 +141,26 @@ class TutorialTestEditing(refinetest.RefineTestCase):
office_title_facet = facet.TextFacet('Office Title') office_title_facet = facet.TextFacet('Office Title')
self.project.engine.add_facet(office_title_facet) self.project.engine.add_facet(office_title_facet)
response = self.project.compute_facets() response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 76) self.assertEqual(len(response.facets[office_title_facet].choices), 76)
self.project.text_transform('Office Title', 'value.trim()') self.project.text_transform('Office Title', 'value.trim()')
self.assertInResponse('6895') self.assertInResponse('6895')
response = self.project.compute_facets() response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 67) self.assertEqual(len(response.facets[office_title_facet].choices), 67)
# {5} # {5}
self.project.edit('Office Title', 'Councilmen', 'Councilman') self.project.edit('Office Title', 'Councilmen', 'Councilman')
self.assertInResponse('13') self.assertInResponse('13')
response = self.project.compute_facets() response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 66) self.assertEqual(len(response.facets[office_title_facet].choices), 66)
# {6} # {6}
response = self.project.compute_clusters('Office Title') response = self.project.compute_clusters('Office Title')
self.assertTrue(not response) self.assertTrue(not response)
# {7} # {7}
clusters = self.project.compute_clusters('Office Title', 'knn') clusters = self.project.compute_clusters('Office Title', 'knn')
self.assertEqual(len(clusters), 7) self.assertEqual(len(clusters), 7)
self.assertEqual(len(clusters[0]), 2) first_cluster = clusters[0]
self.assertEqual(clusters[0][0]['value'], 'RSCC Member') self.assertEqual(len(first_cluster), 2)
self.assertEqual(clusters[0][0]['count'], 233) self.assertEqual(first_cluster[0]['value'], 'RSCC Member')
self.assertEqual(first_cluster[0]['count'], 233)
# Not strictly necessary to repeat 'Council Member' but a test # Not strictly necessary to repeat 'Council Member' but a test
# of mass_edit, and it's also what the front end sends. # of mass_edit, and it's also what the front end sends.
self.project.mass_edit('Office Title', [{ self.project.mass_edit('Office Title', [{
@ -166,7 +169,7 @@ class TutorialTestEditing(refinetest.RefineTestCase):
}]) }])
self.assertInResponse('372') self.assertInResponse('372')
response = self.project.compute_facets() response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 65) self.assertEqual(len(response.facets[office_title_facet].choices), 65)
# Section "4. Row and Column Editing, Batched Row Deletion" # Section "4. Row and Column Editing, Batched Row Deletion"
# Test doesn't strictly follow the tutorial as the "Browse this # Test doesn't strictly follow the tutorial as the "Browse this