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 for unpacking an individual facet response."""
def __init__(self, facet):
for k, v in facet.items():
if isinstance(k, bool) or isinstance(k, basestring):
@ -153,42 +154,76 @@ class FacetResponse(object):
class FacetsResponse(object):
def __init__(self, facets):
self.facets = [FacetResponse(f) for f in facets['facets']]
"""FacetsResponse unpacking the compute-facets response.
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']
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'):
self.set_facets(facets)
self.mode = mode
def set_facets(self, facets=None):
"""facets may be a Facet or list of Facets."""
self.remove_all()
if facets is None:
facets = []
elif not isinstance(facets, list):
facets = [facets]
self.facets = facets
for facet in facets:
self.add_facet(facet)
def as_dict(self):
return {
'facets': [f.as_dict() for f in self.facets], # XXX how with json?
'mode': self.mode,
}
def facets_response(self, response):
"""Unpack a compute-facets response."""
return FacetsResponse(self, response)
def __len__(self):
return len(self.facets)
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):
# 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)
def remove_all(self):
"""Remove all facets."""
self.facets = []
def reset_all(self):
"""Reset all facets."""
for facet in self.facets:
facet.reset()
@ -204,6 +239,7 @@ class Sorting(object):
if not isinstance(criteria, list):
criteria = [criteria]
for criterion in criteria:
# A string criterion defaults to a string sort on that column
if isinstance(criterion, basestring):
criterion = {
'column': criterion,

View File

@ -295,7 +295,7 @@ class RefineProject:
if facets:
self.engine.set_facets(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):
if facets:

View File

@ -130,12 +130,18 @@ class SortingTest(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 = FacetsResponse(json.loads(response))
facets = response.facets
def test_facet_response(self):
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].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__':

View File

@ -40,7 +40,7 @@ class RefineTest(refinetest.RefineTestCase):
def test_new_project(self):
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.assertTrue('email' in self.project.column_order)
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')
response = self.project.compute_facets(party_code_facet)
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.choices['D'].count, 3700)
self.assertEqual(pc.choices['N'].count, 15)
@ -50,7 +52,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
engine.add_facet(ethnicity_facet)
self.project.engine = engine
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['W'].count, 4469)
# {7}
@ -61,7 +63,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
self.assertEqual(indexes, [1, 2, 3, 4, 6, 12, 18, 26, 28, 32])
# {8}
response = self.project.compute_facets()
pc = response.facets[0]
pc = response.facets[party_code_facet]
self.assertEqual(pc.name, 'Party Code')
self.assertEqual(pc.choices['D'].count, 1179)
self.assertEqual(pc.choices['R'].count, 11)
@ -69,7 +71,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
# {9}
party_code_facet.include('R')
response = self.project.compute_facets()
e = response.facets[1]
e = response.facets[ethnicity_facet]
self.assertEqual(e.choices['B'].count, 11)
# {10}
party_code_facet.reset()
@ -90,7 +92,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
response = self.project.get_rows()
self.assertEqual(response.filtered, 1907)
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(ot.choices['Chief of Police'].count, 2)
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]')
self.project.engine.add_facet(phone_facet)
response = self.project.compute_facets()
p = response.facets[0]
p = response.facets[phone_facet]
self.assertEqual(p.expression, 'value[0, 3]')
self.assertEqual(p.choices['318'].count, 2331)
# {16}
@ -110,7 +112,7 @@ class TutorialTestFacets(refinetest.RefineTestCase):
expression='value.toDate().datePart("year")')
self.project.engine.add_facet(commissioned_date_facet)
response = self.project.compute_facets()
cd = response.facets[1]
cd = response.facets[commissioned_date_facet]
self.assertEqual(cd.error_count, 959)
self.assertEqual(cd.numeric_count, 5999)
# {17}
@ -118,10 +120,10 @@ class TutorialTestFacets(refinetest.RefineTestCase):
expression=r'value.match(/\D*(\d+)\w\w Rep.*/)[0].toNumber()')
self.project.engine.add_facet(office_description_facet)
response = self.project.compute_facets()
cd = response.facets[2]
self.assertEqual(cd.min, 0)
self.assertEqual(cd.max, 110)
self.assertEqual(cd.numeric_count, 548)
od = response.facets[office_description_facet]
self.assertEqual(od.min, 0)
self.assertEqual(od.max, 110)
self.assertEqual(od.numeric_count, 548)
class TutorialTestEditing(refinetest.RefineTestCase):
@ -139,25 +141,26 @@ class TutorialTestEditing(refinetest.RefineTestCase):
office_title_facet = facet.TextFacet('Office Title')
self.project.engine.add_facet(office_title_facet)
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.assertInResponse('6895')
response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 67)
self.assertEqual(len(response.facets[office_title_facet].choices), 67)
# {5}
self.project.edit('Office Title', 'Councilmen', 'Councilman')
self.assertInResponse('13')
response = self.project.compute_facets()
self.assertEqual(len(response.facets[0].choices), 66)
self.assertEqual(len(response.facets[office_title_facet].choices), 66)
# {6}
response = self.project.compute_clusters('Office Title')
self.assertTrue(not response)
# {7}
clusters = self.project.compute_clusters('Office Title', 'knn')
self.assertEqual(len(clusters), 7)
self.assertEqual(len(clusters[0]), 2)
self.assertEqual(clusters[0][0]['value'], 'RSCC Member')
self.assertEqual(clusters[0][0]['count'], 233)
first_cluster = clusters[0]
self.assertEqual(len(first_cluster), 2)
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
# of mass_edit, and it's also what the front end sends.
self.project.mass_edit('Office Title', [{
@ -166,7 +169,7 @@ class TutorialTestEditing(refinetest.RefineTestCase):
}])
self.assertInResponse('372')
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"
# Test doesn't strictly follow the tutorial as the "Browse this