Merge lp:~cmiller/desktopcouch/excise-temporary-views into lp:desktopcouch

Proposed by Chad Miller
Status: Merged
Approved by: Joshua Blount
Approved revision: 25
Merged at revision: not available
Proposed branch: lp:~cmiller/desktopcouch/excise-temporary-views
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~cmiller/desktopcouch/excise-temporary-views
Reviewer Review Type Date Requested Status
Joshua Blount (community) Approve
Stuart Langridge (community) Approve
Review via email: mp+9471@code.launchpad.net

Commit message

Remove temporary-view function and add support for permanent views.

To post a comment you must log in.
25. By Chad Miller

Better wording in records docs.

Revision history for this message
Stuart Langridge (sil) wrote :

Looks good to me, and doctests pass afaict.

review: Approve
Revision history for this message
Joshua Blount (jblount) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'desktopcouch/records/doc/records.txt'
--- desktopcouch/records/doc/records.txt 2009-07-08 17:48:11 +0000
+++ desktopcouch/records/doc/records.txt 2009-07-30 16:13:07 +0000
@@ -29,11 +29,34 @@
29b29b
30>>>30>>>
3131
32You can use Couch's ad-hoc query functionality:32There is no ad-hoc query functionality.
3333
34>>> rows = db.query('''function(doc) { emit(doc._id, null) }''')34For views, you should specify a design document for most all calls.
35>>> print list(rows)[0].key == record_id35>>> design_doc = "application"
36
37To create a view:
38
39>>> map_js = """function(doc) { emit(doc._id, null) }"""
40>>> reduce_js = None
41>>> db.add_view("blueberries", map_js, reduce_js, design_doc)
42
43List views for a given design document:
44>>> db.list_views(design_doc)
45['blueberries']
46
47Test that a view exists:
48>>> db.view_exists("blueberries", design_doc)
36True49True
37>>>50
3851Execute a view. Results from execute_view() take list-like syntax to pick one
3952or more rows to retreive. Use index or slice notation.
53>>> result = db.execute_view("blueberries", design_doc)
54>>> for row in result["idfoo"]:
55... pass # all rows with id "idfoo". Unlike lists, may be more than one.
56
57Finally, remove a view. It returns a dict containing the deleted view data.
58>>> db.delete_view("blueberries", design_doc)
59{'map': 'function(doc) { emit(doc._id, null) }'}
60
61For most nonpredicate view operations, if the view you ask for does not exist,
62it will throw a KeyError exception.
4063
=== modified file 'desktopcouch/records/server.py'
--- desktopcouch/records/server.py 2009-07-28 04:12:18 +0000
+++ desktopcouch/records/server.py 2009-07-30 15:57:52 +0000
@@ -17,15 +17,21 @@
17# Authors: Eric Casteleijn <eric.casteleijn@canonical.com>17# Authors: Eric Casteleijn <eric.casteleijn@canonical.com>
18# Mark G. Saye <mark.saye@canonical.com>18# Mark G. Saye <mark.saye@canonical.com>
19# Stuart Langridge <stuart.langridge@canonical.com>19# Stuart Langridge <stuart.langridge@canonical.com>
20# Chad Miller <chad.miller@canonical.com>
2021
21"""The Desktop Couch Records API."""22"""The Desktop Couch Records API."""
2223
23from couchdb import Server24from couchdb import Server
24from couchdb.client import ResourceNotFound, ResourceConflict25from couchdb.client import ResourceNotFound, ResourceConflict
26from couchdb.design import ViewDefinition
25import desktopcouch27import desktopcouch
26from desktopcouch.records.record import Record28from desktopcouch.records.record import Record
2729
2830
31#DEFAULT_DESIGN_DOCUMENT = "design"
32DEFAULT_DESIGN_DOCUMENT = None # each view in its own eponymous design doc.
33
34
29class NoSuchDatabase(Exception):35class NoSuchDatabase(Exception):
30 "Exception for trying to use a non-existent database"36 "Exception for trying to use a non-existent database"
3137
@@ -57,9 +63,9 @@
57 self.db = self._server[database]63 self.db = self._server[database]
58 self.record_factory = record_factory or Record64 self.record_factory = record_factory or Record
5965
60 def query(self, map_fun, reduce_fun=None, language='javascript',66 def _temporary_query(self, map_fun, reduce_fun=None, language='javascript',
61 wrapper=None, **options):67 wrapper=None, **options):
62 """Pass-through to CouchDB query"""68 """Pass-through to CouchDB library. Deprecated."""
63 return self.db.query(map_fun, reduce_fun, language,69 return self.db.query(map_fun, reduce_fun, language,
64 wrapper, **options)70 wrapper, **options)
6571
@@ -112,11 +118,131 @@
112 self.db[record_id] = record118 self.db[record_id] = record
113119
114 def record_exists(self, record_id):120 def record_exists(self, record_id):
115 """Check if record with given id exists"""121 """Check if record with given id exists."""
116 if record_id not in self.db:122 if record_id not in self.db:
117 return False123 return False
118 record = self.db[record_id]124 record = self.db[record_id]
119 return 'deleted' not in record.get('application_annotations', {}).get(125 return 'deleted' not in record.get('application_annotations', {}).get(
120 'Ubuntu One', {}).get('private_application_annotations', {})126 'Ubuntu One', {}).get('private_application_annotations', {})
121127
122128 def delete_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT):
129 """Remove a view, given its name. Raises a KeyError on a unknown
130 view. Returns a dict of functions the deleted view defined."""
131 if design_doc is None:
132 design_doc = view_name
133
134 doc_id = "_design/%(design_doc)s" % locals()
135
136 # No atomic updates. Only read & mutate & write. Le sigh.
137 # First, get current contents.
138 view_container = self.db.get(doc_id, {'_id': doc_id })["views"]
139
140 deleted_data = view_container.pop(view_name) # Remove target
141
142 if len(view_container) > 0:
143 # Construct a new list of objects representing all views to have.
144 views = [
145 ViewDefinition(design_doc, k, v.get("map"), v.get("reduce"))
146 for k, v
147 in view_container.iteritems()
148 ]
149 # Push back a new batch of view. Pray to Eris that this doesn't
150 # clobber anything we want.
151
152 # sync_many does nothing if we pass an empty list. It even gets
153 # its design-document from the ViewDefinition items, and if there
154 # are no items, then it has no idea of a design document to
155 # update. This is a serious flaw. Thus, the "else" to follow.
156 ViewDefinition.sync_many(self.db, views, remove_missing=True)
157 else:
158 # There are no views left in this design document.
159
160 # Remove design document. This assumes there are only views in
161 # design documents. :(
162 del self.db[doc_id]
163
164 assert not self.view_exists(view_name, design_doc)
165
166 return deleted_data
167
168 def execute_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT):
169 """Execute view and return results."""
170 if design_doc is None:
171 design_doc = view_name
172
173 view_id_fmt = "_design/%(design_doc)s/_view/%(view_name)s"
174 return self.db.view(view_id_fmt % locals())
175
176 def add_view(self, view_name, map_js, reduce_js,
177 design_doc=DEFAULT_DESIGN_DOCUMENT):
178 """Create a view, given a name and the two parts (map and reduce).
179 Return the document id."""
180 if design_doc is None:
181 design_doc = view_name
182
183 view = ViewDefinition(design_doc, view_name, map_js, reduce_js)
184 view.sync(self.db)
185 assert self.view_exists(view_name, design_doc)
186
187 def view_exists(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT):
188 """Does a view with a given name, in a optional design document
189 exist?"""
190 if design_doc is None:
191 design_doc = view_name
192
193 doc_id = "_design/%(design_doc)s" % locals()
194
195 try:
196 view_container = self.db.get(doc_id, {'_id': doc_id })["views"]
197 return view_name in view_container
198 except KeyError:
199 return False
200
201 def list_views(self, design_doc):
202 """Return a list of view names for a given design document. There is
203 no error if the design document does not exist or if there are no views
204 in it."""
205 doc_id = "_design/%(design_doc)s" % locals()
206 try:
207 return list(self.db.get(doc_id, {'_id': doc_id })["views"])
208 except KeyError:
209 return []
210
211 def get_records_and_type(self, create_view=False,
212 design_doc=DEFAULT_DESIGN_DOCUMENT):
213 """A convenience function to get records. We optionally create a view
214 in the design document. C<create_view> may be True or False, and a
215 special value, None, is analogous to O_EXCL|O_CREAT .
216
217 Use the view to return *all* records. If there is no view to use or we
218 insist on creating a new view and cannot, raise KeyError .
219
220 Use index notation on the result to get rows with a particular record
221 type.
222 =>> results = get_records_and_type()
223 =>> for foo_document in results["foo"]:
224 ... print foo_document
225
226 Use slice notation to apply start-key and end-key options to the view.
227 =>> results = get_records_and_type()
228 =>> people = results[['Person']:['Person','ZZZZ']]
229 """
230 view_name = "get_records_and_type"
231 view_map_js = """function(doc) { emit(doc.record_type, doc) }"""
232
233 if design_doc is None:
234 design_doc = view_name
235
236 exists = self.view_exists(view_name, design_doc)
237
238 if exists:
239 if create_view is None:
240 raise KeyError("Exclusive creation failed.")
241 else:
242 if create_view == False:
243 raise KeyError("View doesn't already exist.")
244
245 if not exists:
246 self.add_view(view_name, view_map_js, None, design_doc)
247
248 return self.execute_view(view_name, design_doc)
123249
=== modified file 'desktopcouch/records/tests/test_server.py'
--- desktopcouch/records/tests/test_server.py 2009-07-28 03:43:02 +0000
+++ desktopcouch/records/tests/test_server.py 2009-07-30 15:57:52 +0000
@@ -93,3 +93,70 @@
93 self.assertEqual(11, working_copy['field1'])93 self.assertEqual(11, working_copy['field1'])
94 self.assertEqual(22, working_copy['field2'])94 self.assertEqual(22, working_copy['field2'])
95 self.assertEqual(3, working_copy['field3'])95 self.assertEqual(3, working_copy['field3'])
96
97 def test_view_add_and_delete(self):
98 design_doc = "design"
99 view1_name = "unit_tests_are_wonderful"
100 view2_name = "unit_tests_are_marvelous"
101 view3_name = "unit_tests_are_fantastic"
102
103 map_js = """function(doc) { emit(doc._id, null) }"""
104 reduce_js = """\
105 function (key, values, rereduce) {
106 return sum(values);
107 }"""
108
109 # add two and delete two.
110 self.assertRaises(KeyError, self.database.delete_view, view1_name, design_doc)
111 self.assertRaises(KeyError, self.database.delete_view, view2_name, design_doc)
112 self.database.add_view(view1_name, map_js, reduce_js, design_doc)
113 self.database.add_view(view2_name, map_js, reduce_js, design_doc)
114 self.database.delete_view(view1_name, design_doc)
115 self.assertRaises(KeyError, self.database.delete_view, view1_name, design_doc)
116 self.database.delete_view(view2_name, design_doc)
117 self.assertRaises(KeyError, self.database.delete_view, view2_name, design_doc)
118
119 def test_func_get_records_and_type(self):
120 record_ids_we_care_about = set()
121 good_record_type = "http://example.com/unittest/good"
122 other_record_type = "http://example.com/unittest/bad"
123
124 for i in range(7):
125 if i % 3 == 1:
126 record = Record({'record_number': i},
127 record_type=good_record_type)
128 record_ids_we_care_about.add(self.database.put_record(record))
129 else:
130 record = Record({'record_number': i},
131 record_type=other_record_type)
132 self.database.put_record(record)
133
134 results = self.database.get_records_and_type(create_view=True)
135
136 for row in results[good_record_type]: # index notation
137 self.assertTrue(row.id in record_ids_we_care_about)
138 record_ids_we_care_about.remove(row.id)
139
140 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")
141
142 def test_list_views(self):
143 design_doc = "d"
144 self.assertEqual(self.database.list_views(design_doc), [])
145
146 view_name = "unit_tests_are_fantastic"
147 map_js = """function(doc) { emit(doc._id, null) }"""
148 self.database.add_view(view_name, map_js, None, design_doc)
149
150 self.assertEqual(self.database.list_views(design_doc), [view_name])
151 self.database.delete_view(view_name, design_doc)
152
153 self.assertEqual(self.database.list_views(design_doc), [])
154
155 def test_get_view_by_type_new_but_already(self):
156 self.database.get_records_and_type(create_view=True)
157 self.database.get_records_and_type(create_view=True)
158 # No exceptions on second run? Yay.
159
160 def test_get_view_by_type_createxcl_fail(self):
161 self.database.get_records_and_type(create_view=True)
162 self.assertRaises(KeyError, self.database.get_records_and_type, create_view=None)

Subscribers

People subscribed via source and target branches