Merge lp:~stevenk/launchpad/bulk-eid-to-obj into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 16687
Proposed branch: lp:~stevenk/launchpad/bulk-eid-to-obj
Merge into: lp:launchpad
Diff against target: 154 lines (+61/-24)
3 files modified
lib/lp/services/auditor/client.py (+8/-4)
lib/lp/services/enterpriseid.py (+23/-10)
lib/lp/services/tests/test_enterpriseid.py (+30/-10)
To merge this branch: bzr merge lp:~stevenk/launchpad/bulk-eid-to-obj
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+171705@code.launchpad.net

Commit message

Destroy enterpriseid_to_object for being terrible, its replacement is enterpriseids_to_objects.

Description of the change

Destroy enterpriseid_to_object for being terrible and issuing one query per eid passed to it. Its replacement, enterpriseids_to_objects will make one query per type of object.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

20 + eids = []
21 + for entry in logs['log-entries']:
22 + eids.extend([entry['actor'], entry['object']])

This should probably be a set; there will likely be many dupes.

76 +def enterpriseids_to_objects(eids):
77 + """Given a list of SOA Enterprise IDs, return a dict that maps the ID to
78 + its concrete object."""

That's a prolix and non-compliant docstring. "Dereference multiple SOA Enterprise IDs.", maybe?

79 + map_id_to_obj = {}
80 + obj_id_to_eid = defaultdict(dict)

Aren't those "eid_to_obj" and "dbid_to_eid"? map_id_to_obj also isn't needed until right at the end; I'd define it there for readability.

89 + type_ids[scheme[1]].append(int(scheme[2]))
90 + obj_id_to_eid[scheme[1]][int(scheme[2])] = eid

These are redundant. type_ids[foo] is just obj_id_to_eid[foo].keys(). Dropping type_ids also has the side-effect of eliminating dupes.

92 + for kind in types:

This could just iterate over obj_id_to_eid instead, to get just the types relevant to this query.

137 + def test_enterpriseids_to_objects(self):

This probably wants to also test for multiple classes.

review: Needs Fixing (code)
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/auditor/client.py'
2--- lib/lp/services/auditor/client.py 2013-06-18 03:57:26 +0000
3+++ lib/lp/services/auditor/client.py 2013-06-27 04:36:27 +0000
4@@ -12,7 +12,7 @@
5
6 from lp.services.config import config
7 from lp.services.enterpriseid import (
8- enterpriseid_to_object,
9+ enterpriseids_to_objects,
10 object_to_enterpriseid,
11 )
12
13@@ -42,7 +42,11 @@
14 logs = super(AuditorClient, self).receive(
15 obj, operation, actorobj, limit)
16 # Process the actors and objects back from enterprise ids.
17- for entry in logs['log-entries']:
18- entry['actor'] = enterpriseid_to_object(entry['actor'])
19- entry['object'] = enterpriseid_to_object(entry['object'])
20+ eids = set()
21+ for entry in logs['log-entries']:
22+ eids |= set([entry['actor'], entry['object']])
23+ map_eids_to_obj = enterpriseids_to_objects(eids)
24+ for entry in logs['log-entries']:
25+ entry['actor'] = map_eids_to_obj.get(entry['actor'], None)
26+ entry['object'] = map_eids_to_obj.get(entry['object'], None)
27 return logs['log-entries']
28
29=== modified file 'lib/lp/services/enterpriseid.py'
30--- lib/lp/services/enterpriseid.py 2012-07-26 22:12:53 +0000
31+++ lib/lp/services/enterpriseid.py 2013-06-27 04:36:27 +0000
32@@ -1,4 +1,4 @@
33-# Copyright 2012 Canonical Ltd. This software is licensed under the
34+# Copyright 2012-2013 Canonical Ltd. This software is licensed under the
35 # GNU Affero General Public License version 3 (see the file LICENSE).
36
37 """Enterprise ID utilities."""
38@@ -6,12 +6,14 @@
39 __metaclass__ = type
40 __all__ = [
41 'object_to_enterpriseid',
42- 'enterpriseid_to_object',
43+ 'enterpriseids_to_objects',
44 ]
45
46+from collections import defaultdict
47 import os
48
49 from lp.services.config import config
50+from lp.services.database.bulk import load
51
52
53 def object_to_enterpriseid(obj):
54@@ -25,17 +27,28 @@
55 return '%s:%s:%d' % (instance, otype, obj.id)
56
57
58-def enterpriseid_to_object(eid):
59- """Given an SOA Enterprise ID, return the object that it references."""
60+def _known_types():
61 # Circular imports.
62 from lp.registry.model.person import Person
63 from lp.soyuz.model.queue import PackageUpload
64- scheme = eid.split(':')
65- if not scheme[0].startswith('lp'):
66- raise TypeError
67- known_types = {
68+ return {
69 'PackageUpload': PackageUpload,
70 'Person': Person,
71 }
72- klass = known_types[scheme[1]]
73- return klass.get(scheme[2])
74+
75+
76+def enterpriseids_to_objects(eids):
77+ """Dereference multiple SOA Enterprise IDs."""
78+ dbid_to_eid = defaultdict(dict)
79+ for eid in eids:
80+ if not eid.startswith('lp'):
81+ raise TypeError
82+ instance, cls, id = eid.split(':')
83+ dbid_to_eid[cls][int(id)] = eid
84+ types = _known_types()
85+ eid_to_obj = {}
86+ for kind in dbid_to_eid:
87+ objs = load(types[kind], dbid_to_eid[kind].keys())
88+ for obj in objs:
89+ eid_to_obj[dbid_to_eid[kind][obj.id]] = obj
90+ return eid_to_obj
91
92=== modified file 'lib/lp/services/tests/test_enterpriseid.py'
93--- lib/lp/services/tests/test_enterpriseid.py 2012-03-05 02:24:17 +0000
94+++ lib/lp/services/tests/test_enterpriseid.py 2013-06-27 04:36:27 +0000
95@@ -1,20 +1,27 @@
96-# Copyright 2012 Canonical Ltd. This software is licensed under the
97+# Copyright 2012-2013 Canonical Ltd. This software is licensed under the
98 # GNU Affero General Public License version 3 (see the file LICENSE).
99
100 """Test Enterprise ID utilities."""
101
102 __metaclass__ = type
103
104+from testtools.matchers import Equals
105+
106+from lp.services.database.interfaces import IStore
107 from lp.services.enterpriseid import (
108- enterpriseid_to_object,
109+ enterpriseids_to_objects,
110 object_to_enterpriseid,
111 )
112-from lp.testing import TestCaseWithFactory
113-from lp.testing.layers import DatabaseFunctionalLayer
114+from lp.testing import (
115+ StormStatementRecorder,
116+ TestCaseWithFactory,
117+ )
118+from lp.testing.layers import LaunchpadFunctionalLayer
119+from lp.testing.matchers import HasQueryCount
120
121
122 class TestEnterpriseId(TestCaseWithFactory):
123- layer = DatabaseFunctionalLayer
124+ layer = LaunchpadFunctionalLayer
125
126 def test_object_to_enterpriseid(self):
127 person = self.factory.makePerson()
128@@ -22,8 +29,21 @@
129 expected = 'lp-development:Person:%d' % person.id
130 self.assertEqual(expected, eid)
131
132- def test_enterpriseid_to_object(self):
133- person = self.factory.makePerson()
134- eid = 'lp-development:Person:%d' % person.id
135- obj = enterpriseid_to_object(eid)
136- self.assertEqual(person, obj)
137+ def test_enterpriseids_to_objects(self):
138+ expected = {}
139+ for x in range(5):
140+ person = self.factory.makePerson()
141+ upload = self.factory.makePackageUpload()
142+ expected['lp-development:Person:%d' % person.id] = person
143+ expected['lp-development:PackageUpload:%d' % upload.id] = upload
144+ IStore(expected.values()[0].__class__).invalidate()
145+ with StormStatementRecorder() as recorder:
146+ objects = enterpriseids_to_objects(expected.keys())
147+ self.assertThat(recorder, HasQueryCount(Equals(2)))
148+ self.assertContentEqual(expected.keys(), objects.keys())
149+ self.assertContentEqual(expected.values(), objects.values())
150+
151+ def test_enterpriseids_to_objects_non_existent_id(self):
152+ objects = enterpriseids_to_objects(
153+ ['lp-development:PackageUpload:10000'])
154+ self.assertEqual({}, objects)