Merge lp:~thisfred/desktopcouch/fix-fieldmappings into lp:desktopcouch
- fix-fieldmappings
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Eric Casteleijn |
Approved revision: | 206 |
Merged at revision: | 205 |
Proposed branch: | lp:~thisfred/desktopcouch/fix-fieldmappings |
Merge into: | lp:desktopcouch |
Diff against target: |
1532 lines (+273/-188) 13 files modified
.bzrignore (+2/-0) bin/desktopcouch-pair (+41/-41) data/source_desktopcouch.py (+1/-1) desktopcouch/pair/couchdb_pairing/couchdb_io.py (+5/-5) desktopcouch/pair/tests/test_couchdb_io.py (+3/-3) desktopcouch/records/doc/field_registry.txt (+21/-16) desktopcouch/records/field_registry.py (+16/-9) desktopcouch/records/server.py (+16/-14) desktopcouch/records/server_base.py (+53/-32) desktopcouch/records/tests/test_field_registry.py (+50/-15) desktopcouch/records/tests/test_record.py (+20/-20) desktopcouch/records/tests/test_server.py (+44/-31) utilities/lint.sh (+1/-1) |
To merge this branch: | bzr merge lp:~thisfred/desktopcouch/fix-fieldmappings |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Miller (community) | Approve | ||
Vincenzo Di Somma (community) | Approve | ||
dobey (community) | Approve | ||
Review via email: mp+40573@code.launchpad.net |
Commit message
Fixes bug #416963: fieldregistry transformers now return results, and don't need a record or dictionary like object passed in.
Description of the change
Fixes bug #416963: fieldregistry transformers now return results, and don't need a record or dictionary like object passed in. (They will still accept one, to preserve backward compatibility, and to make it possible to initialize some date before the transformation.)
Eric Casteleijn (thisfred) wrote : | # |
Eric Casteleijn (thisfred) wrote : | # |
The meat of the real changes is in field_registry.py and its tests
dobey (dobey) : | # |
Vincenzo Di Somma (vds) : | # |
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
Attempt to merge into lp:desktopcouch failed due to conflicts:
text conflict in desktopcouch/
text conflict in desktopcouch/
text conflict in utilities/lint.sh
Chad Miller (cmiller) wrote : | # |
Approve revno 204.
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === added file '.bzrignore' | |||
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 | |||
3 | +++ .bzrignore 2010-11-12 14:40:00 +0000 | |||
4 | @@ -0,0 +1,2 @@ | |||
5 | 1 | _trial_temp | ||
6 | 2 | .coverage | ||
7 | 0 | 3 | ||
8 | === modified file 'bin/desktopcouch-pair' | |||
9 | --- bin/desktopcouch-pair 2010-04-05 21:17:42 +0000 | |||
10 | +++ bin/desktopcouch-pair 2010-11-12 14:40:00 +0000 | |||
11 | @@ -22,7 +22,7 @@ | |||
12 | 22 | 22 | ||
13 | 23 | A tool to set two local machines to replicate their couchdb instances to each | 23 | A tool to set two local machines to replicate their couchdb instances to each |
14 | 24 | other, or to set this machine to replicate to-and-from Ubuntu One (and perhaps | 24 | other, or to set this machine to replicate to-and-from Ubuntu One (and perhaps |
16 | 25 | other cloud services). | 25 | other cloud services). |
17 | 26 | 26 | ||
18 | 27 | Local-Pairing Authentication | 27 | Local-Pairing Authentication |
19 | 28 | ---------------------------- | 28 | ---------------------------- |
20 | @@ -37,7 +37,7 @@ | |||
21 | 37 | 37 | ||
22 | 38 | Alice then computes the SHA512 digest of Bob's secret and compares it to be | 38 | Alice then computes the SHA512 digest of Bob's secret and compares it to be |
23 | 39 | sure that the other machine is indeed the user's. Alice then concatenates | 39 | sure that the other machine is indeed the user's. Alice then concatenates |
25 | 40 | Bob's secret and the public seed, and sends the resulting hex digest back to | 40 | Bob's secret and the public seed, and sends the resulting hex digest back to |
26 | 41 | Bob to prove that she received the secret from the user. Alice sets herself to | 41 | Bob to prove that she received the secret from the user. Alice sets herself to |
27 | 42 | replicate to Bob. | 42 | replicate to Bob. |
28 | 43 | 43 | ||
29 | @@ -50,7 +50,7 @@ | |||
30 | 50 | import logging | 50 | import logging |
31 | 51 | import getpass | 51 | import getpass |
32 | 52 | import gettext | 52 | import gettext |
34 | 53 | # gettext implements "_" function. pylint: disable-msg=E0602 | 53 | # gettext implements "_" function. pylint: disable=E0602 |
35 | 54 | import random | 54 | import random |
36 | 55 | import cgi | 55 | import cgi |
37 | 56 | 56 | ||
38 | @@ -122,13 +122,13 @@ | |||
39 | 122 | 122 | ||
40 | 123 | We see listeners on the network and send invitations to pair with us. | 123 | We see listeners on the network and send invitations to pair with us. |
41 | 124 | We generate a secret message and a public seed. We get the SHA512 hex | 124 | We generate a secret message and a public seed. We get the SHA512 hex |
44 | 125 | digest of the secret message, append the public seed and send it to | 125 | digest of the secret message, append the public seed and send it to |
45 | 126 | Alice. We also display the cleartext of the secret message to the | 126 | Alice. We also display the cleartext of the secret message to the |
46 | 127 | screen, so that the user can take it to the machine he thinks is Alice. | 127 | screen, so that the user can take it to the machine he thinks is Alice. |
47 | 128 | 128 | ||
51 | 129 | Eventually, we receive a message back from Alice. We compute the | 129 | Eventually, we receive a message back from Alice. We compute the |
52 | 130 | secret message we started with concatenated with the public seed, and | 130 | secret message we started with concatenated with the public seed, and |
53 | 131 | if that matches Alice's message, then Alice must know the secret we | 131 | if that matches Alice's message, then Alice must know the secret we |
54 | 132 | displayed to the user. We then set outselves to replicate to Alice.""" | 132 | displayed to the user. We then set outselves to replicate to Alice.""" |
55 | 133 | 133 | ||
56 | 134 | def delete_event(self, widget, event, data=None): | 134 | def delete_event(self, widget, event, data=None): |
57 | @@ -167,7 +167,7 @@ | |||
58 | 167 | self.secret_message = secret_message | 167 | self.secret_message = secret_message |
59 | 168 | self.public_seed = generate_secret() | 168 | self.public_seed = generate_secret() |
60 | 169 | 169 | ||
62 | 170 | self.inviter = network_io.start_send_invitation(hostname, port, | 170 | self.inviter = network_io.start_send_invitation(hostname, port, |
63 | 171 | self.auth_completed, self.secret_message, self.public_seed, | 171 | self.auth_completed, self.secret_message, self.public_seed, |
64 | 172 | self.on_close, couchdb_io.get_my_host_unique_id(create=True)[0], | 172 | self.on_close, couchdb_io.get_my_host_unique_id(create=True)[0], |
65 | 173 | local_files.get_oauth_tokens()) | 173 | local_files.get_oauth_tokens()) |
66 | @@ -180,7 +180,7 @@ | |||
67 | 180 | """us, and to prove veracity of the invitation we\n""" + | 180 | """us, and to prove veracity of the invitation we\n""" + |
68 | 181 | """sent, it is waiting for you to tell it this secret:\n""" + | 181 | """sent, it is waiting for you to tell it this secret:\n""" + |
69 | 182 | """<span font-size="xx-large" color="blue" weight="bold">""" + | 182 | """<span font-size="xx-large" color="blue" weight="bold">""" + |
71 | 183 | """<tt>%s</tt></span> .""") % | 183 | """<tt>%s</tt></span> .""") % |
72 | 184 | (cgi.escape(service), cgi.escape(self.secret_message))) | 184 | (cgi.escape(service), cgi.escape(self.secret_message))) |
73 | 185 | text.set_justify(gtk.JUSTIFY_CENTER) | 185 | text.set_justify(gtk.JUSTIFY_CENTER) |
74 | 186 | top_vbox.pack_start(text, False, False, 10) | 186 | top_vbox.pack_start(text, False, False, 10) |
75 | @@ -197,7 +197,7 @@ | |||
76 | 197 | 197 | ||
77 | 198 | class AcceptInvitation: | 198 | class AcceptInvitation: |
78 | 199 | """We're part of 'Alice' in this module's story. | 199 | """We're part of 'Alice' in this module's story. |
80 | 200 | 200 | ||
81 | 201 | We've received an invitation. We now send the other end a secret key. The | 201 | We've received an invitation. We now send the other end a secret key. The |
82 | 202 | secret should make its way back to us via meatspace. We open a dialog | 202 | secret should make its way back to us via meatspace. We open a dialog |
83 | 203 | asking for that secret here, which we validate. | 203 | asking for that secret here, which we validate. |
84 | @@ -258,7 +258,7 @@ | |||
85 | 258 | 258 | ||
86 | 259 | self.result = gtk.Label("") | 259 | self.result = gtk.Label("") |
87 | 260 | self.result.show() | 260 | self.result.show() |
89 | 261 | self.entry_box.connect("changed", | 261 | self.entry_box.connect("changed", |
90 | 262 | lambda widget: self.result.set_text("")) | 262 | lambda widget: self.result.set_text("")) |
91 | 263 | top_vbox.pack_start(self.result, False, False, 0) | 263 | top_vbox.pack_start(self.result, False, False, 0) |
92 | 264 | 264 | ||
93 | @@ -300,7 +300,7 @@ | |||
94 | 300 | 300 | ||
95 | 301 | class Listening: | 301 | class Listening: |
96 | 302 | """We're part of 'Alice' in this module's story. | 302 | """We're part of 'Alice' in this module's story. |
98 | 303 | 303 | ||
99 | 304 | Window that starts listening for other machines to pick *us* to pair | 304 | Window that starts listening for other machines to pick *us* to pair |
100 | 305 | with. There must be at least one of these on the network for pairing to | 305 | with. There must be at least one of these on the network for pairing to |
101 | 306 | happen. We listen for a finite amount of time, and then stop.""" | 306 | happen. We listen for a finite amount of time, and then stop.""" |
102 | @@ -337,7 +337,7 @@ | |||
103 | 337 | we're listening.""" | 337 | we're listening.""" |
104 | 338 | 338 | ||
105 | 339 | self.listener = network_io.ListenForInvitations( | 339 | self.listener = network_io.ListenForInvitations( |
107 | 340 | self.receive_invitation_challenge, | 340 | self.receive_invitation_challenge, |
108 | 341 | lambda: self.window.destroy(), | 341 | lambda: self.window.destroy(), |
109 | 342 | couchdb_io.get_my_host_unique_id(create=True)[0], | 342 | couchdb_io.get_my_host_unique_id(create=True)[0], |
110 | 343 | local_files.get_oauth_tokens()) | 343 | local_files.get_oauth_tokens()) |
111 | @@ -407,16 +407,16 @@ | |||
112 | 407 | text = gtk.Label() | 407 | text = gtk.Label() |
113 | 408 | text.set_markup( | 408 | text.set_markup( |
114 | 409 | _("""We're listening for invitations! From another\n""" + | 409 | _("""We're listening for invitations! From another\n""" + |
120 | 410 | """machine on this local network, run this\n""" + | 410 | """machine on this local network, run this\n""" + |
121 | 411 | """same tool and find the machine called\n""" + | 411 | """same tool and find the machine called\n""" + |
122 | 412 | """<span font-size="xx-large" weight="bold"><tt>""" + | 412 | """<span font-size="xx-large" weight="bold"><tt>""" + |
123 | 413 | """%s-%s-%d</tt></span> .""") % | 413 | """%s-%s-%d</tt></span> .""") % |
124 | 414 | (cgi.escape(hostid), cgi.escape(userid), | 414 | (cgi.escape(hostid), cgi.escape(userid), |
125 | 415 | listen_port)) | 415 | listen_port)) |
126 | 416 | text.set_justify(gtk.JUSTIFY_CENTER) | 416 | text.set_justify(gtk.JUSTIFY_CENTER) |
127 | 417 | top_vbox.pack_start(text, False, False, 10) | 417 | top_vbox.pack_start(text, False, False, 10) |
128 | 418 | text.show() | 418 | text.show() |
130 | 419 | 419 | ||
131 | 420 | self.update_counter_view() | 420 | self.update_counter_view() |
132 | 421 | self.window.show_all() | 421 | self.window.show_all() |
133 | 422 | 422 | ||
134 | @@ -444,7 +444,7 @@ | |||
135 | 444 | 444 | ||
136 | 445 | self.update_counter_view() | 445 | self.update_counter_view() |
137 | 446 | return True | 446 | return True |
139 | 447 | 447 | ||
140 | 448 | 448 | ||
141 | 449 | class PickOrListen: | 449 | class PickOrListen: |
142 | 450 | """Main top-level window that represents the life of the application.""" | 450 | """Main top-level window that represents the life of the application.""" |
143 | @@ -459,7 +459,7 @@ | |||
144 | 459 | 459 | ||
145 | 460 | def create_pick_pane(self, container): | 460 | def create_pick_pane(self, container): |
146 | 461 | """Set up the pane that contains what's necessary to choose an | 461 | """Set up the pane that contains what's necessary to choose an |
148 | 462 | already-listening tool instance. This sets up a "Bob" in the | 462 | already-listening tool instance. This sets up a "Bob" in the |
149 | 463 | module's story.""" | 463 | module's story.""" |
150 | 464 | 464 | ||
151 | 465 | # positions: host id, descr, host, port, cloud_name | 465 | # positions: host id, descr, host, port, cloud_name |
152 | @@ -473,15 +473,15 @@ | |||
153 | 473 | srv = getattr(services, srv_name) | 473 | srv = getattr(services, srv_name) |
154 | 474 | try: | 474 | try: |
155 | 475 | if srv.is_active(): | 475 | if srv.is_active(): |
157 | 476 | all_paired_cloud_servers = [x.key for x in | 476 | all_paired_cloud_servers = [x.key for x in |
158 | 477 | couchdb_io.get_pairings()] | 477 | couchdb_io.get_pairings()] |
159 | 478 | if not srv_name in all_paired_cloud_servers: | 478 | if not srv_name in all_paired_cloud_servers: |
161 | 479 | self.listening_hosts.append(None, | 479 | self.listening_hosts.append(None, |
162 | 480 | [srv.name, srv.description, "", 0, srv_name]) | 480 | [srv.name, srv.description, "", 0, srv_name]) |
163 | 481 | except Exception, e: | 481 | except Exception, e: |
164 | 482 | self.logging.exception("service %r has errors", srv_name) | 482 | self.logging.exception("service %r has errors", srv_name) |
165 | 483 | 483 | ||
167 | 484 | self.inviting = None # pylint: disable-msg=W0201 | 484 | self.inviting = None # pylint: disable=W0201 |
168 | 485 | 485 | ||
169 | 486 | hostname_col = gtk.TreeViewColumn(_("hostname")) | 486 | hostname_col = gtk.TreeViewColumn(_("hostname")) |
170 | 487 | hostid_col = gtk.TreeViewColumn(_("service name")) | 487 | hostid_col = gtk.TreeViewColumn(_("service name")) |
171 | @@ -500,7 +500,7 @@ | |||
172 | 500 | tv.show() | 500 | tv.show() |
173 | 501 | 501 | ||
174 | 502 | def clicked(selection): | 502 | def clicked(selection): |
176 | 503 | """An item in the list of services was clicked, so now we go | 503 | """An item in the list of services was clicked, so now we go |
177 | 504 | about inviting it to pair with us.""" | 504 | about inviting it to pair with us.""" |
178 | 505 | 505 | ||
179 | 506 | model, iter = selection.get_selected() | 506 | model, iter = selection.get_selected() |
180 | @@ -511,7 +511,7 @@ | |||
181 | 511 | hostname = model.get_value(iter, 2) | 511 | hostname = model.get_value(iter, 2) |
182 | 512 | port = model.get_value(iter, 3) | 512 | port = model.get_value(iter, 3) |
183 | 513 | service_name = model.get_value(iter, 4) | 513 | service_name = model.get_value(iter, 4) |
185 | 514 | 514 | ||
186 | 515 | if service_name: | 515 | if service_name: |
187 | 516 | # Pairing with a cloud service, which doesn't do key exchange | 516 | # Pairing with a cloud service, which doesn't do key exchange |
188 | 517 | pair_with_cloud_service(service_name, self.window) | 517 | pair_with_cloud_service(service_name, self.window) |
189 | @@ -519,7 +519,7 @@ | |||
190 | 519 | self.listening_hosts.remove(iter) | 519 | self.listening_hosts.remove(iter) |
191 | 520 | # add to already-paired list | 520 | # add to already-paired list |
192 | 521 | srv = getattr(services, service_name) | 521 | srv = getattr(services, service_name) |
194 | 522 | self.already_paired_hosts.append(None, | 522 | self.already_paired_hosts.append(None, |
195 | 523 | [service, _("paired just now"), hostname, port, service_name, None]) | 523 | [service, _("paired just now"), hostname, port, service_name, None]) |
196 | 524 | return | 524 | return |
197 | 525 | 525 | ||
198 | @@ -548,7 +548,7 @@ | |||
199 | 548 | self.listening_hosts.append(None, [name, description, host, port, None]) | 548 | self.listening_hosts.append(None, [name, description, host, port, None]) |
200 | 549 | 549 | ||
201 | 550 | def remove_service_from_list(name): | 550 | def remove_service_from_list(name): |
203 | 551 | """When a zeroconf service disappears, this finds it in the | 551 | """When a zeroconf service disappears, this finds it in the |
204 | 552 | listing and removes it as an option for picking.""" | 552 | listing and removes it as an option for picking.""" |
205 | 553 | 553 | ||
206 | 554 | it = self.listening_hosts.get_iter_first() | 554 | it = self.listening_hosts.get_iter_first() |
207 | @@ -604,7 +604,7 @@ | |||
208 | 604 | already_paired_record.value.get("ctime", | 604 | already_paired_record.value.get("ctime", |
209 | 605 | _("unknown date")) | 605 | _("unknown date")) |
210 | 606 | try: | 606 | try: |
212 | 607 | self.already_paired_hosts.append(None, | 607 | self.already_paired_hosts.append(None, |
213 | 608 | [srv.name, nice_description, "", 0, srv_name, pid]) | 608 | [srv.name, nice_description, "", 0, srv_name, pid]) |
214 | 609 | except Exception, e: | 609 | except Exception, e: |
215 | 610 | logging.error("Service %s had an error", srv_name, e) | 610 | logging.error("Service %s had an error", srv_name, e) |
216 | @@ -613,7 +613,7 @@ | |||
217 | 613 | nice_description = _("paired ") + \ | 613 | nice_description = _("paired ") + \ |
218 | 614 | already_paired_record.value.get("ctime", | 614 | already_paired_record.value.get("ctime", |
219 | 615 | _("unknown date")) | 615 | _("unknown date")) |
221 | 616 | self.already_paired_hosts.append(None, | 616 | self.already_paired_hosts.append(None, |
222 | 617 | [hostname, nice_description, None, 0, None, pid]) | 617 | [hostname, nice_description, None, 0, None, pid]) |
223 | 618 | else: | 618 | else: |
224 | 619 | logging.error("unknown pairing record %s", | 619 | logging.error("unknown pairing record %s", |
225 | @@ -635,7 +635,7 @@ | |||
226 | 635 | tv.show() | 635 | tv.show() |
227 | 636 | 636 | ||
228 | 637 | def clicked(selection): | 637 | def clicked(selection): |
230 | 638 | """An item in the list of services was clicked, so now we go | 638 | """An item in the list of services was clicked, so now we go |
231 | 639 | about inviting it to pair with us.""" | 639 | about inviting it to pair with us.""" |
232 | 640 | 640 | ||
233 | 641 | model, iter = selection.get_selected() | 641 | model, iter = selection.get_selected() |
234 | @@ -646,17 +646,17 @@ | |||
235 | 646 | port = model.get_value(iter, 3) | 646 | port = model.get_value(iter, 3) |
236 | 647 | service_name = model.get_value(iter, 4) | 647 | service_name = model.get_value(iter, 4) |
237 | 648 | pid = model.get_value(iter, 5) | 648 | pid = model.get_value(iter, 5) |
239 | 649 | 649 | ||
240 | 650 | if service_name: | 650 | if service_name: |
241 | 651 | # delete record | 651 | # delete record |
242 | 652 | for record in couchdb_io.get_pairings(): | 652 | for record in couchdb_io.get_pairings(): |
243 | 653 | couchdb_io.remove_pairing(record.id, True) | 653 | couchdb_io.remove_pairing(record.id, True) |
245 | 654 | 654 | ||
246 | 655 | # remove from already-paired list | 655 | # remove from already-paired list |
247 | 656 | self.already_paired_hosts.remove(iter) | 656 | self.already_paired_hosts.remove(iter) |
248 | 657 | # add to listening list | 657 | # add to listening list |
249 | 658 | srv = getattr(services, service_name) | 658 | srv = getattr(services, service_name) |
251 | 659 | self.listening_hosts.append(None, [service, srv.description, | 659 | self.listening_hosts.append(None, [service, srv.description, |
252 | 660 | hostname, port, service_name]) | 660 | hostname, port, service_name]) |
253 | 661 | return | 661 | return |
254 | 662 | 662 | ||
255 | @@ -665,7 +665,7 @@ | |||
256 | 665 | if record.value["pairing_identifier"] == pid: | 665 | if record.value["pairing_identifier"] == pid: |
257 | 666 | couchdb_io.remove_pairing(record.id, False) | 666 | couchdb_io.remove_pairing(record.id, False) |
258 | 667 | break | 667 | break |
260 | 668 | 668 | ||
261 | 669 | # remove from already-paired list | 669 | # remove from already-paired list |
262 | 670 | self.already_paired_hosts.remove(iter) | 670 | self.already_paired_hosts.remove(iter) |
263 | 671 | # do not add to listening list -- if it's listening then zeroconf | 671 | # do not add to listening list -- if it's listening then zeroconf |
264 | @@ -699,7 +699,7 @@ | |||
265 | 699 | 699 | ||
266 | 700 | def create_single_listen_pane(self, container): | 700 | def create_single_listen_pane(self, container): |
267 | 701 | """This sets up an "Alice" from the module's story. | 701 | """This sets up an "Alice" from the module's story. |
269 | 702 | 702 | ||
270 | 703 | This assumes we're pairing a single, known local CouchDB instance, | 703 | This assumes we're pairing a single, known local CouchDB instance, |
271 | 704 | instead of generic instances that we'd need more information to talk | 704 | instead of generic instances that we'd need more information to talk |
272 | 705 | about. Instead of using this function, one might use another that | 705 | about. Instead of using this function, one might use another that |
273 | @@ -749,7 +749,7 @@ | |||
274 | 749 | #some_row_in_list.connect("clicked", self.listen, target_db_info) | 749 | #some_row_in_list.connect("clicked", self.listen, target_db_info) |
275 | 750 | 750 | ||
276 | 751 | def __init__(self): | 751 | def __init__(self): |
278 | 752 | 752 | ||
279 | 753 | 753 | ||
280 | 754 | self.logging = logging.getLogger(self.__class__.__name__) | 754 | self.logging = logging.getLogger(self.__class__.__name__) |
281 | 755 | 755 | ||
282 | @@ -802,8 +802,8 @@ | |||
283 | 802 | fail_note.run() | 802 | fail_note.run() |
284 | 803 | fail_note.destroy() | 803 | fail_note.destroy() |
285 | 804 | return | 804 | return |
288 | 805 | 805 | ||
289 | 806 | success_note = gtk.Dialog(title=_("Paired with %(hostname)s") % locals(), | 806 | success_note = gtk.Dialog(title=_("Paired with %(hostname)s") % locals(), |
290 | 807 | parent=parent, | 807 | parent=parent, |
291 | 808 | flags=gtk.DIALOG_DESTROY_WITH_PARENT, | 808 | flags=gtk.DIALOG_DESTROY_WITH_PARENT, |
292 | 809 | buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,)) | 809 | buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,)) |
293 | @@ -838,7 +838,7 @@ | |||
294 | 838 | fail_note.run() | 838 | fail_note.run() |
295 | 839 | fail_note.destroy() | 839 | fail_note.destroy() |
296 | 840 | return | 840 | return |
298 | 841 | 841 | ||
299 | 842 | success_note = gtk.MessageDialog( | 842 | success_note = gtk.MessageDialog( |
300 | 843 | parent=parent, | 843 | parent=parent, |
301 | 844 | flags=gtk.DIALOG_DESTROY_WITH_PARENT, | 844 | flags=gtk.DIALOG_DESTROY_WITH_PARENT, |
302 | 845 | 845 | ||
303 | === modified file 'data/source_desktopcouch.py' | |||
304 | --- data/source_desktopcouch.py 2010-02-03 19:39:17 +0000 | |||
305 | +++ data/source_desktopcouch.py 2010-11-12 14:40:00 +0000 | |||
306 | @@ -14,7 +14,7 @@ | |||
307 | 14 | # You should have received a copy of the GNU General Public License along | 14 | # You should have received a copy of the GNU General Public License along |
308 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
309 | 16 | """Stub for Apport""" | 16 | """Stub for Apport""" |
311 | 17 | # pylint: disable-msg=F0401,C0103 | 17 | # pylint: disable=F0401,C0103 |
312 | 18 | # shut up about apport. We know. We aren't going to backport it for pqm | 18 | # shut up about apport. We know. We aren't going to backport it for pqm |
313 | 19 | import apport | 19 | import apport |
314 | 20 | from apport.hookutils import attach_file_if_exists, packaging | 20 | from apport.hookutils import attach_file_if_exists, packaging |
315 | 21 | 21 | ||
316 | === modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py' | |||
317 | --- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-09 17:37:32 +0000 | |||
318 | +++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-12 14:40:00 +0000 | |||
319 | @@ -55,7 +55,7 @@ | |||
320 | 55 | protocol = "https" if has_ssl else "http" | 55 | protocol = "https" if has_ssl else "http" |
321 | 56 | if auth_pair: | 56 | if auth_pair: |
322 | 57 | auth = (":".join( | 57 | auth = (":".join( |
324 | 58 | map(urllib.quote, auth_pair)) + "@") # pylint: disable-msg=W0141 | 58 | map(urllib.quote, auth_pair)) + "@") # pylint: disable=W0141 |
325 | 59 | else: | 59 | else: |
326 | 60 | auth = "" | 60 | auth = "" |
327 | 61 | if (protocol, port) in (("http", 80), ("https", 443)): | 61 | if (protocol, port) in (("http", 80), ("https", 443)): |
328 | @@ -108,7 +108,7 @@ | |||
329 | 108 | push_to_server=True, server=hostname, ctx=ctx) | 108 | push_to_server=True, server=hostname, ctx=ctx) |
330 | 109 | 109 | ||
331 | 110 | 110 | ||
333 | 111 | def get_static_paired_hosts(uri=None, # pylint: disable-msg=R0914 | 111 | def get_static_paired_hosts(uri=None, # pylint: disable=R0914 |
334 | 112 | ctx=local_files.DEFAULT_CONTEXT, port=None): | 112 | ctx=local_files.DEFAULT_CONTEXT, port=None): |
335 | 113 | """Retreive a list of static hosts' information in the form of | 113 | """Retreive a list of static hosts' information in the form of |
336 | 114 | (ID, service name, to_push, to_pull) .""" | 114 | (ID, service name, to_push, to_pull) .""" |
337 | @@ -251,7 +251,7 @@ | |||
338 | 251 | oauth_tokens=oauth_tokens) | 251 | oauth_tokens=oauth_tokens) |
339 | 252 | 252 | ||
340 | 253 | 253 | ||
342 | 254 | def replicate(source_database, target_database, # pylint: disable-msg=R0914 | 254 | def replicate(source_database, target_database, # pylint: disable=R0914 |
343 | 255 | target_host=None, target_port=None, source_host=None, | 255 | target_host=None, target_port=None, source_host=None, |
344 | 256 | source_port=None, source_ssl=False, target_ssl=False, | 256 | source_port=None, source_ssl=False, target_ssl=False, |
345 | 257 | source_oauth=None, target_oauth=None, local_uri=None): | 257 | source_oauth=None, target_oauth=None, local_uri=None): |
346 | @@ -268,7 +268,7 @@ | |||
347 | 268 | else: | 268 | else: |
348 | 269 | server.CouchDatabase(target_database, create=True, uri=local_uri) | 269 | server.CouchDatabase(target_database, create=True, uri=local_uri) |
349 | 270 | logging.debug("db exists, and we're ready to replicate") | 270 | logging.debug("db exists, and we're ready to replicate") |
351 | 271 | except: # pylint: disable-msg=W0702 | 271 | except: # pylint: disable=W0702 |
352 | 272 | logging.exception( | 272 | logging.exception( |
353 | 273 | "can't create/verify %r %s:%d oauth=%s", target_database, | 273 | "can't create/verify %r %s:%d oauth=%s", target_database, |
354 | 274 | target_host, target_port, obsfuscate(target_oauth)) | 274 | target_host, target_port, obsfuscate(target_oauth)) |
355 | @@ -309,7 +309,7 @@ | |||
356 | 309 | content=record) | 309 | content=record) |
357 | 310 | logging.debug( | 310 | logging.debug( |
358 | 311 | "replicate result: %r %r", obsfuscate(resp), obsfuscate(data)) | 311 | "replicate result: %r %r", obsfuscate(resp), obsfuscate(data)) |
360 | 312 | except: # pylint: disable-msg=W0702 | 312 | except: # pylint: disable=W0702 |
361 | 313 | logging.exception("can't replicate %r %r <== %r", source_database, | 313 | logging.exception("can't replicate %r %r <== %r", source_database, |
362 | 314 | local_uri, obsfuscate(record)) | 314 | local_uri, obsfuscate(record)) |
363 | 315 | 315 | ||
364 | 316 | 316 | ||
365 | === modified file 'desktopcouch/pair/tests/test_couchdb_io.py' | |||
366 | --- desktopcouch/pair/tests/test_couchdb_io.py 2010-11-09 17:37:32 +0000 | |||
367 | +++ desktopcouch/pair/tests/test_couchdb_io.py 2010-11-12 14:40:00 +0000 | |||
368 | @@ -67,8 +67,8 @@ | |||
369 | 67 | 67 | ||
370 | 68 | def tearDown(self): | 68 | def tearDown(self): |
371 | 69 | """tear down each test""" | 69 | """tear down each test""" |
374 | 70 | del self.mgt_database._server['management'] # pylint: disable-msg=W0212 | 70 | del self.mgt_database._server['management'] # pylint: disable=W0212 |
375 | 71 | del self.mgt_database._server['foo'] # pylint: disable-msg=W0212 | 71 | del self.mgt_database._server['foo'] # pylint: disable=W0212 |
376 | 72 | 72 | ||
377 | 73 | def test_obsfuscation(self): | 73 | def test_obsfuscation(self): |
378 | 74 | """Test the obfuscation of sensitive data.""" | 74 | """Test the obfuscation of sensitive data.""" |
379 | @@ -98,7 +98,7 @@ | |||
380 | 98 | self.assertEqual(couchdb_io.obsfuscate({1: {}}), {1: {}}) | 98 | self.assertEqual(couchdb_io.obsfuscate({1: {}}), {1: {}}) |
381 | 99 | self.assertEqual(couchdb_io.obsfuscate({1: 1}), {1: 1}) | 99 | self.assertEqual(couchdb_io.obsfuscate({1: 1}), {1: 1}) |
382 | 100 | 100 | ||
384 | 101 | def test_put_static_paired_service(self): # pylint: disable-msg=R0201 | 101 | def test_put_static_paired_service(self): # pylint: disable=R0201 |
385 | 102 | """Test putting a static paired service.""" | 102 | """Test putting a static paired service.""" |
386 | 103 | service_name = "dummyfortest" | 103 | service_name = "dummyfortest" |
387 | 104 | oauth_data = { | 104 | oauth_data = { |
388 | 105 | 105 | ||
389 | === modified file 'desktopcouch/records/doc/field_registry.txt' | |||
390 | --- desktopcouch/records/doc/field_registry.txt 2009-10-05 13:36:43 +0000 | |||
391 | +++ desktopcouch/records/doc/field_registry.txt 2010-11-12 14:40:00 +0000 | |||
392 | @@ -9,7 +9,16 @@ | |||
393 | 9 | >>> from desktopcouch.records.record import Record | 9 | >>> from desktopcouch.records.record import Record |
394 | 10 | 10 | ||
395 | 11 | Say we have a very simple audiofile record type that defines 'artist' | 11 | Say we have a very simple audiofile record type that defines 'artist' |
397 | 12 | and 'title' string fields. Now also say we have an application that | 12 | and 'title' string fields. |
398 | 13 | |||
399 | 14 | >>> class AudioFile(Record): | ||
400 | 15 | ... """An audio file desktopcouch record.""" | ||
401 | 16 | ... | ||
402 | 17 | ... def __init__(self, data=None): | ||
403 | 18 | ... super(AudioFile, self).__init__( | ||
404 | 19 | ... record_type='http://example.org/record_types/audio_file', data=data) | ||
405 | 20 | |||
406 | 21 | Now also say we have an application that | ||
407 | 13 | wants to interact with records of this type called 'My Awesome Music | 22 | wants to interact with records of this type called 'My Awesome Music |
408 | 14 | Player' or MAMP. The developers of MAMP use a data structure that has | 23 | Player' or MAMP. The developers of MAMP use a data structure that has |
409 | 15 | the same fields, but uses slightly different names for them: | 24 | the same fields, but uses slightly different names for them: |
410 | @@ -23,7 +32,8 @@ | |||
411 | 23 | 32 | ||
412 | 24 | and instantiate a Transformer object: | 33 | and instantiate a Transformer object: |
413 | 25 | 34 | ||
415 | 26 | >>> my_transformer = Transformer('My Awesome Music Player', my_registry) | 35 | >>> my_transformer = Transformer( |
416 | 36 | ... 'My Awesome Music Player', my_registry, record_class=AudioFile) | ||
417 | 27 | 37 | ||
418 | 28 | If MAMP has the following song object (a plain dictionary): | 38 | If MAMP has the following song object (a plain dictionary): |
419 | 29 | 39 | ||
420 | @@ -36,8 +46,7 @@ | |||
421 | 36 | object: | 46 | object: |
422 | 37 | 47 | ||
423 | 38 | >>> AUDIO_FILE_RECORD_TYPE = 'http://example.org/record_types/audio_file' | 48 | >>> AUDIO_FILE_RECORD_TYPE = 'http://example.org/record_types/audio_file' |
426 | 39 | >>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE) | 49 | >>> new_record = my_transformer.from_app(my_song) |
425 | 40 | >>> my_transformer.from_app(my_song, new_record) | ||
427 | 41 | 50 | ||
428 | 42 | Now we can look at the underlying data: | 51 | Now we can look at the underlying data: |
429 | 43 | 52 | ||
430 | @@ -63,8 +72,7 @@ | |||
431 | 63 | this data. Let's see what happens if we run the transformation with | 72 | this data. Let's see what happens if we run the transformation with |
432 | 64 | this field present, but undefined in the field registry: | 73 | this field present, but undefined in the field registry: |
433 | 65 | 74 | ||
436 | 66 | >>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE) | 75 | >>> new_record = my_transformer.from_app(my_song) |
435 | 67 | >>> my_transformer.from_app(my_song, new_record) | ||
437 | 68 | 76 | ||
438 | 69 | >>> new_record._data #doctest: +NORMALIZE_WHITESPACE | 77 | >>> new_record._data #doctest: +NORMALIZE_WHITESPACE |
439 | 70 | {'record_type': 'http://example.org/record_types/audio_file', | 78 | {'record_type': 'http://example.org/record_types/audio_file', |
440 | @@ -111,9 +119,9 @@ | |||
441 | 111 | ... default_values={'description': 'subject'}), | 119 | ... default_values={'description': 'subject'}), |
442 | 112 | ... } | 120 | ... } |
443 | 113 | 121 | ||
447 | 114 | >>> my_transformer = Transformer('My Awesome Music Player', my_registry) | 122 | >>> my_transformer = Transformer( |
448 | 115 | >>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE) | 123 | ... 'My Awesome Music Player', my_registry, record_class=AudioFile) |
449 | 116 | >>> my_transformer.from_app(my_song, new_record) | 124 | >>> new_record = my_transformer.from_app(my_song) |
450 | 117 | 125 | ||
451 | 118 | Since _data will now contain lots of uuids to keep references intact, | 126 | Since _data will now contain lots of uuids to keep references intact, |
452 | 119 | it's less readable, and a less clear example, so I'll show you what | 127 | it's less readable, and a less clear example, so I'll show you what |
453 | @@ -141,8 +149,7 @@ | |||
454 | 141 | 149 | ||
455 | 142 | and now look at transforming in the other direction: | 150 | and now look at transforming in the other direction: |
456 | 143 | 151 | ||
459 | 144 | >>> new_song = {} | 152 | >>> new_song = my_transformer.to_app(new_record) |
458 | 145 | >>> my_transformer.to_app(new_record, new_song) | ||
460 | 146 | >>> new_song #doctest: +NORMALIZE_WHITESPACE | 153 | >>> new_song #doctest: +NORMALIZE_WHITESPACE |
461 | 147 | {'tag_title': 'shaking it', | 154 | {'tag_title': 'shaking it', |
462 | 148 | 'tag_subject': 'talking', | 155 | 'tag_subject': 'talking', |
463 | @@ -193,7 +200,8 @@ | |||
464 | 193 | ... 'songtitle': SimpleFieldMapping('title'), | 200 | ... 'songtitle': SimpleFieldMapping('title'), |
465 | 194 | ... 'stars': StarIntMapping('score'), | 201 | ... 'stars': StarIntMapping('score'), |
466 | 195 | ... } | 202 | ... } |
468 | 196 | >>> my_transformer = Transformer('My Awesome Music Player', my_registry) | 203 | >>> my_transformer = Transformer( |
469 | 204 | ... 'My Awesome Music Player', my_registry, record_class=AudioFile) | ||
470 | 197 | 205 | ||
471 | 198 | Create a song with a rating: | 206 | Create a song with a rating: |
472 | 199 | 207 | ||
473 | @@ -204,10 +212,7 @@ | |||
474 | 204 | ... 'number_of_times_played_in_mamp': 23 | 212 | ... 'number_of_times_played_in_mamp': 23 |
475 | 205 | ... } | 213 | ... } |
476 | 206 | 214 | ||
479 | 207 | >>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE) | 215 | >>> new_record = my_transformer.from_app(my_song) |
478 | 208 | >>> my_transformer.from_app(my_song, new_record) | ||
480 | 209 | >>> new_record['score'] | 216 | >>> new_record['score'] |
481 | 210 | 100 | 217 | 100 |
482 | 211 | 218 | ||
483 | 212 | And, I don't know if you've ever heard the song in question, but that | ||
484 | 213 | is in fact correct! ;) | ||
485 | 214 | 219 | ||
486 | === modified file 'desktopcouch/records/field_registry.py' | |||
487 | --- desktopcouch/records/field_registry.py 2010-11-02 23:10:39 +0000 | |||
488 | +++ desktopcouch/records/field_registry.py 2010-11-12 14:40:00 +0000 | |||
489 | @@ -21,7 +21,7 @@ | |||
490 | 21 | 21 | ||
491 | 22 | import copy | 22 | import copy |
492 | 23 | 23 | ||
494 | 24 | from desktopcouch.records.record import MergeableList | 24 | from desktopcouch.records.record import MergeableList |
495 | 25 | ANNOTATION_NAMESPACE = 'application_annotations' | 25 | ANNOTATION_NAMESPACE = 'application_annotations' |
496 | 26 | 26 | ||
497 | 27 | 27 | ||
498 | @@ -30,12 +30,15 @@ | |||
499 | 30 | data. | 30 | data. |
500 | 31 | """ | 31 | """ |
501 | 32 | 32 | ||
503 | 33 | def __init__(self, app_name, field_registry): | 33 | def __init__(self, app_name, field_registry, record_class=None): |
504 | 34 | self.app_name = app_name | 34 | self.app_name = app_name |
505 | 35 | self.field_registry = field_registry | 35 | self.field_registry = field_registry |
506 | 36 | self.record_class = record_class | ||
507 | 36 | 37 | ||
509 | 37 | def from_app(self, data, record): | 38 | def from_app(self, data, record=None): |
510 | 38 | """Transform from application data to record data.""" | 39 | """Transform from application data to record data.""" |
511 | 40 | if record is None: | ||
512 | 41 | record = self.record_class() | ||
513 | 39 | for key, val in data.items(): | 42 | for key, val in data.items(): |
514 | 40 | if key in self.field_registry: | 43 | if key in self.field_registry: |
515 | 41 | self.field_registry[key].setValue(record, val) | 44 | self.field_registry[key].setValue(record, val) |
516 | @@ -43,15 +46,19 @@ | |||
517 | 43 | record.application_annotations.setdefault( | 46 | record.application_annotations.setdefault( |
518 | 44 | self.app_name, {}).setdefault( | 47 | self.app_name, {}).setdefault( |
519 | 45 | 'application_fields', {})[key] = val | 48 | 'application_fields', {})[key] = val |
520 | 49 | return record | ||
521 | 46 | 50 | ||
523 | 47 | def to_app(self, record, data): | 51 | def to_app(self, record, data=None): |
524 | 48 | """Transform from record data to application data.""" | 52 | """Transform from record data to application data.""" |
525 | 53 | if data is None: | ||
526 | 54 | data = {} | ||
527 | 49 | annotations = record.application_annotations.get(self.app_name, {}).get( | 55 | annotations = record.application_annotations.get(self.app_name, {}).get( |
528 | 50 | 'application_fields', {}) | 56 | 'application_fields', {}) |
529 | 51 | for key, value in annotations.items(): | 57 | for key, value in annotations.items(): |
530 | 52 | data[key] = value | 58 | data[key] = value |
531 | 53 | for key in self.field_registry: | 59 | for key in self.field_registry: |
532 | 54 | data[key] = self.field_registry[key].getValue(record) | 60 | data[key] = self.field_registry[key].getValue(record) |
533 | 61 | return data | ||
534 | 55 | 62 | ||
535 | 56 | 63 | ||
536 | 57 | class SimpleFieldMapping(object): | 64 | class SimpleFieldMapping(object): |
537 | @@ -81,8 +88,8 @@ | |||
538 | 81 | class MergeableListFieldMapping(object): | 88 | class MergeableListFieldMapping(object): |
539 | 82 | """Mapping between MergeableLists and application fields.""" | 89 | """Mapping between MergeableLists and application fields.""" |
540 | 83 | 90 | ||
543 | 84 | def __init__( | 91 | def __init__(self, app_name, uuid_field, root_list, field_name, |
544 | 85 | self, app_name, uuid_field, root_list, field_name, default_values=None): | 92 | default_values=None): |
545 | 86 | """initialize the default values""" | 93 | """initialize the default values""" |
546 | 87 | self._app_name = app_name | 94 | self._app_name = app_name |
547 | 88 | self._uuid_field = uuid_field | 95 | self._uuid_field = uuid_field |
548 | @@ -119,8 +126,10 @@ | |||
549 | 119 | uuid_key = self._uuidLookup(record) | 126 | uuid_key = self._uuidLookup(record) |
550 | 120 | if not uuid_key: | 127 | if not uuid_key: |
551 | 121 | return | 128 | return |
552 | 129 | # pylint: disable=W0212 | ||
553 | 122 | if self._field_name in root_list._data.get(uuid_key, []): | 130 | if self._field_name in root_list._data.get(uuid_key, []): |
555 | 123 | del root_list._data[uuid_key][self._field_name] | 131 | del root_list._data[uuid_key][self._field_name] |
556 | 132 | # pylint: enable=W0212 | ||
557 | 124 | 133 | ||
558 | 125 | def setValue(self, record, value): | 134 | def setValue(self, record, value): |
559 | 126 | """set the value for the registered field""" | 135 | """set the value for the registered field""" |
560 | @@ -155,5 +164,3 @@ | |||
561 | 155 | application_annotations[self._uuid_field] = uuid_key | 164 | application_annotations[self._uuid_field] = uuid_key |
562 | 156 | return | 165 | return |
563 | 157 | record_dict[self._field_name] = value | 166 | record_dict[self._field_name] = value |
564 | 158 | |||
565 | 159 | |||
566 | 160 | 167 | ||
567 | === modified file 'desktopcouch/records/server.py' | |||
568 | --- desktopcouch/records/server.py 2010-11-04 11:01:31 +0000 | |||
569 | +++ desktopcouch/records/server.py 2010-11-12 14:40:00 +0000 | |||
570 | @@ -18,10 +18,11 @@ | |||
571 | 18 | # Mark G. Saye <mark.saye@canonical.com> | 18 | # Mark G. Saye <mark.saye@canonical.com> |
572 | 19 | # Stuart Langridge <stuart.langridge@canonical.com> | 19 | # Stuart Langridge <stuart.langridge@canonical.com> |
573 | 20 | # Chad Miller <chad.miller@canonical.com> | 20 | # Chad Miller <chad.miller@canonical.com> |
575 | 21 | 21 | ||
576 | 22 | """The Desktop Couch Records API.""" | 22 | """The Desktop Couch Records API.""" |
577 | 23 | 23 | ||
579 | 24 | import copy, uuid | 24 | import copy |
580 | 25 | import uuid | ||
581 | 25 | 26 | ||
582 | 26 | from couchdb import Server | 27 | from couchdb import Server |
583 | 27 | from couchdb.client import Resource | 28 | from couchdb.client import Resource |
584 | @@ -32,9 +33,10 @@ | |||
585 | 32 | 33 | ||
586 | 33 | DCTRASH = 'dctrash' | 34 | DCTRASH = 'dctrash' |
587 | 34 | 35 | ||
588 | 36 | |||
589 | 35 | class OAuthCapableServer(Server): | 37 | class OAuthCapableServer(Server): |
590 | 36 | """Subclass Server to provide oauth magic""" | 38 | """Subclass Server to provide oauth magic""" |
592 | 37 | # pylint: disable-msg=W0231 | 39 | # pylint: disable=W0231 |
593 | 38 | # __init__ method from base class is not called | 40 | # __init__ method from base class is not called |
594 | 39 | def __init__(self, uri, oauth_tokens=None, ctx=None): | 41 | def __init__(self, uri, oauth_tokens=None, ctx=None): |
595 | 40 | """Subclass of couchdb.client.Server which creates a custom | 42 | """Subclass of couchdb.client.Server which creates a custom |
596 | @@ -47,17 +49,17 @@ | |||
597 | 47 | if oauth_tokens is None: | 49 | if oauth_tokens is None: |
598 | 48 | oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx) | 50 | oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx) |
599 | 49 | (consumer_key, consumer_secret, token, token_secret) = ( | 51 | (consumer_key, consumer_secret, token, token_secret) = ( |
601 | 50 | oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], | 52 | oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], |
602 | 51 | oauth_tokens["token"], oauth_tokens["token_secret"]) | 53 | oauth_tokens["token"], oauth_tokens["token_secret"]) |
603 | 52 | http.add_oauth_tokens( | 54 | http.add_oauth_tokens( |
604 | 53 | consumer_key, consumer_secret, token, token_secret) | 55 | consumer_key, consumer_secret, token, token_secret) |
605 | 54 | self.resource = Resource(http, uri) | 56 | self.resource = Resource(http, uri) |
609 | 55 | # pylint: enable-msg=W0231 | 57 | # pylint: enable=W0231 |
610 | 56 | 58 | ||
611 | 57 | 59 | ||
612 | 58 | class CouchDatabase(server_base.CouchDatabaseBase): | 60 | class CouchDatabase(server_base.CouchDatabaseBase): |
613 | 59 | """An small records specific abstraction over a couch db database.""" | 61 | """An small records specific abstraction over a couch db database.""" |
615 | 60 | 62 | ||
616 | 61 | def __init__(self, database, uri=None, record_factory=None, create=False, | 63 | def __init__(self, database, uri=None, record_factory=None, create=False, |
617 | 62 | server_class=OAuthCapableServer, oauth_tokens=None, | 64 | server_class=OAuthCapableServer, oauth_tokens=None, |
618 | 63 | ctx=desktopcouch.local_files.DEFAULT_CONTEXT): | 65 | ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
619 | @@ -67,7 +69,7 @@ | |||
620 | 67 | database, uri, record_factory=record_factory, create=create, | 69 | database, uri, record_factory=record_factory, create=create, |
621 | 68 | server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx) | 70 | server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx) |
622 | 69 | 71 | ||
624 | 70 | # pylint: disable-msg=W0212 | 72 | # pylint: disable=W0212 |
625 | 71 | #Access to a protected member | 73 | #Access to a protected member |
626 | 72 | def delete_record(self, record_id): | 74 | def delete_record(self, record_id): |
627 | 73 | """Delete record with given id""" | 75 | """Delete record with given id""" |
628 | @@ -79,9 +81,9 @@ | |||
629 | 79 | create=True, | 81 | create=True, |
630 | 80 | **self._server_class_extras) | 82 | **self._server_class_extras) |
631 | 81 | new_record.record_id = str(uuid.uuid4()) | 83 | new_record.record_id = str(uuid.uuid4()) |
633 | 82 | del new_record._data['_rev'] | 84 | del new_record._data['_rev'] |
634 | 83 | try: | 85 | try: |
636 | 84 | del new_record._data['_attachments'] | 86 | del new_record._data['_attachments'] |
637 | 85 | except KeyError: | 87 | except KeyError: |
638 | 86 | pass | 88 | pass |
639 | 87 | new_record.application_annotations['desktopcouch'] = { | 89 | new_record.application_annotations['desktopcouch'] = { |
640 | @@ -90,9 +92,9 @@ | |||
641 | 90 | 'original_id': record_id}} | 92 | 'original_id': record_id}} |
642 | 91 | del self.db[record_id] | 93 | del self.db[record_id] |
643 | 92 | return dctrash.put_record(new_record) | 94 | return dctrash.put_record(new_record) |
645 | 93 | # pylint: enable-msg=W0212 | 95 | # pylint: enable=W0212 |
646 | 94 | 96 | ||
648 | 95 | # pylint: disable-msg=W0221 | 97 | # pylint: disable=W0221 |
649 | 96 | # Arguments number differs from overridden method | 98 | # Arguments number differs from overridden method |
650 | 97 | def _reconnect(self): | 99 | def _reconnect(self): |
651 | 98 | if not self.server_uri: | 100 | if not self.server_uri: |
652 | @@ -101,4 +103,4 @@ | |||
653 | 101 | else: | 103 | else: |
654 | 102 | uri = self.server_uri | 104 | uri = self.server_uri |
655 | 103 | super(CouchDatabase, self)._reconnect(uri=uri) | 105 | super(CouchDatabase, self)._reconnect(uri=uri) |
657 | 104 | # pylint: enable-msg=W0221 | 106 | # pylint: enable=W0221 |
658 | 105 | 107 | ||
659 | === modified file 'desktopcouch/records/server_base.py' | |||
660 | --- desktopcouch/records/server_base.py 2010-11-03 22:57:06 +0000 | |||
661 | +++ desktopcouch/records/server_base.py 2010-11-12 14:40:00 +0000 | |||
662 | @@ -22,31 +22,37 @@ | |||
663 | 22 | 22 | ||
664 | 23 | """The Desktop Couch Records API.""" | 23 | """The Desktop Couch Records API.""" |
665 | 24 | 24 | ||
667 | 25 | import httplib2, urlparse, cgi, copy, warnings | 25 | import cgi |
668 | 26 | import copy | ||
669 | 27 | import httplib2 | ||
670 | 28 | import urlparse | ||
671 | 29 | import warnings | ||
672 | 30 | |||
673 | 26 | from time import time | 31 | from time import time |
674 | 27 | 32 | ||
675 | 28 | # please keep desktopcouch python 2.5 compatible for now | 33 | # please keep desktopcouch python 2.5 compatible for now |
676 | 29 | 34 | ||
677 | 30 | # pylint can't deal with failing imports even when they're handled | 35 | # pylint can't deal with failing imports even when they're handled |
679 | 31 | # pylint: disable-msg=F0401 | 36 | # pylint: disable=F0401 |
680 | 32 | try: | 37 | try: |
681 | 33 | # Python 2.5 | 38 | # Python 2.5 |
682 | 34 | import simplejson as json | 39 | import simplejson as json |
683 | 35 | except ImportError: | 40 | except ImportError: |
684 | 36 | # Python 2.6+ | 41 | # Python 2.6+ |
685 | 37 | import json | 42 | import json |
687 | 38 | # pylint: enable-msg=F0401 | 43 | # pylint: enable=F0401 |
688 | 39 | 44 | ||
689 | 40 | from oauth import oauth | 45 | from oauth import oauth |
690 | 41 | 46 | ||
691 | 42 | from couchdb import Server | 47 | from couchdb import Server |
692 | 43 | from couchdb.client import ResourceNotFound, ResourceConflict, uri as couchdburi | 48 | from couchdb.client import ResourceNotFound, ResourceConflict, uri as couchdburi |
693 | 44 | from couchdb.design import ViewDefinition | 49 | from couchdb.design import ViewDefinition |
695 | 45 | from record import Record | 50 | from desktopcouch.records.record import Record |
696 | 46 | import logging | 51 | import logging |
697 | 47 | 52 | ||
698 | 48 | DEFAULT_DESIGN_DOCUMENT = None # each view in its own eponymous design doc. | 53 | DEFAULT_DESIGN_DOCUMENT = None # each view in its own eponymous design doc. |
699 | 49 | 54 | ||
700 | 55 | |||
701 | 50 | def get_changes(self, changes_since): | 56 | def get_changes(self, changes_since): |
702 | 51 | """This method is used to monkey patch the database to provide a | 57 | """This method is used to monkey patch the database to provide a |
703 | 52 | get_changes method""" | 58 | get_changes method""" |
704 | @@ -58,6 +64,7 @@ | |||
705 | 58 | resp, data = self.resource.http.request(uri, "GET", "", {}) | 64 | resp, data = self.resource.http.request(uri, "GET", "", {}) |
706 | 59 | return resp, data | 65 | return resp, data |
707 | 60 | 66 | ||
708 | 67 | |||
709 | 61 | def transform_to_records(view_results): | 68 | def transform_to_records(view_results): |
710 | 62 | """Transform view resulst into Record objects.""" | 69 | """Transform view resulst into Record objects.""" |
711 | 63 | for result in view_results: | 70 | for result in view_results: |
712 | @@ -67,10 +74,10 @@ | |||
713 | 67 | class FieldsConflict(Exception): | 74 | class FieldsConflict(Exception): |
714 | 68 | """Raised in case of an unrecoverable couchdb conflict.""" | 75 | """Raised in case of an unrecoverable couchdb conflict.""" |
715 | 69 | 76 | ||
717 | 70 | #pylint: disable-msg=W0231 | 77 | #pylint: disable=W0231 |
718 | 71 | def __init__(self, conflicts): | 78 | def __init__(self, conflicts): |
719 | 72 | self.conflicts = conflicts | 79 | self.conflicts = conflicts |
721 | 73 | #pylint: enable-msg=W0231 | 80 | #pylint: enable=W0231 |
722 | 74 | 81 | ||
723 | 75 | def __str__(self): | 82 | def __str__(self): |
724 | 76 | return "<CouchDB Conflict Error: %s>" % self.conflicts | 83 | return "<CouchDB Conflict Error: %s>" % self.conflicts |
725 | @@ -97,6 +104,7 @@ | |||
726 | 97 | httplib2.Authentication.__init__(self, None, host, request_uri, | 104 | httplib2.Authentication.__init__(self, None, host, request_uri, |
727 | 98 | headers, response, content, http) | 105 | headers, response, content, http) |
728 | 99 | 106 | ||
729 | 107 | # pylint: disable=R0914 | ||
730 | 100 | def request(self, method, request_uri, headers, content): | 108 | def request(self, method, request_uri, headers, content): |
731 | 101 | """Modify the request headers to add the appropriate | 109 | """Modify the request headers to add the appropriate |
732 | 102 | Authorization header.""" | 110 | Authorization header.""" |
733 | @@ -106,35 +114,43 @@ | |||
734 | 106 | self.oauth_data['token_secret']) | 114 | self.oauth_data['token_secret']) |
735 | 107 | sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1 | 115 | sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1 |
736 | 108 | full_http_url = "%s://%s%s" % (self.scheme, self.host, request_uri) | 116 | full_http_url = "%s://%s%s" % (self.scheme, self.host, request_uri) |
739 | 109 | schema, netloc, path, params, query, fragment = \ | 117 | # pylint: disable=W0612 |
740 | 110 | urlparse.urlparse(full_http_url) | 118 | # better than using dummy variables, in case we want to use |
741 | 119 | # any of them later on | ||
742 | 120 | schema, netloc, path, params, query, fragment = urlparse.urlparse( | ||
743 | 121 | full_http_url) | ||
744 | 122 | # pylint: enable=W0612 | ||
745 | 111 | querystr_as_dict = dict(cgi.parse_qsl(query)) | 123 | querystr_as_dict = dict(cgi.parse_qsl(query)) |
746 | 112 | req = oauth.OAuthRequest.from_consumer_and_token( | 124 | req = oauth.OAuthRequest.from_consumer_and_token( |
747 | 113 | consumer, | 125 | consumer, |
748 | 114 | access_token, | 126 | access_token, |
753 | 115 | http_method = method, | 127 | http_method=method, |
754 | 116 | http_url = full_http_url, | 128 | http_url=full_http_url, |
755 | 117 | parameters = querystr_as_dict | 129 | parameters=querystr_as_dict) |
752 | 118 | ) | ||
756 | 119 | req.sign_request(sig_method(), consumer, access_token) | 130 | req.sign_request(sig_method(), consumer, access_token) |
757 | 131 | # pylint: disable=W0212 | ||
758 | 120 | headers.update(httplib2._normalize_headers(req.to_header())) | 132 | headers.update(httplib2._normalize_headers(req.to_header())) |
759 | 133 | # pylint: enable=W0212 | ||
760 | 134 | # pylint: enable=R0914 | ||
761 | 121 | 135 | ||
762 | 122 | 136 | ||
763 | 123 | class OAuthCapableHttp(httplib2.Http): | 137 | class OAuthCapableHttp(httplib2.Http): |
764 | 124 | """Subclass of httplib2.Http which specifically uses our OAuth | 138 | """Subclass of httplib2.Http which specifically uses our OAuth |
765 | 125 | Authentication subclass (because httplib2 doesn't know about it)""" | 139 | Authentication subclass (because httplib2 doesn't know about it)""" |
767 | 126 | def __init__(self, scheme="http", cache=None, timeout=None, proxy_info=None): | 140 | def __init__(self, scheme="http", cache=None, timeout=None, |
768 | 141 | proxy_info=None): | ||
769 | 127 | self.__scheme = scheme | 142 | self.__scheme = scheme |
770 | 143 | self.oauth_data = None | ||
771 | 128 | super(OAuthCapableHttp, self).__init__(cache, timeout, proxy_info) | 144 | super(OAuthCapableHttp, self).__init__(cache, timeout, proxy_info) |
772 | 129 | 145 | ||
773 | 130 | def add_oauth_tokens(self, consumer_key, consumer_secret, | 146 | def add_oauth_tokens(self, consumer_key, consumer_secret, |
774 | 131 | token, token_secret): | 147 | token, token_secret): |
775 | 148 | """Add the OAuth tokens to the Http object.""" | ||
776 | 132 | self.oauth_data = { | 149 | self.oauth_data = { |
777 | 133 | "consumer_key": consumer_key, | 150 | "consumer_key": consumer_key, |
778 | 134 | "consumer_secret": consumer_secret, | 151 | "consumer_secret": consumer_secret, |
779 | 135 | "token": token, | 152 | "token": token, |
782 | 136 | "token_secret": token_secret | 153 | "token_secret": token_secret} |
781 | 137 | } | ||
783 | 138 | 154 | ||
784 | 139 | def _auth_from_challenge(self, host, request_uri, headers, response, | 155 | def _auth_from_challenge(self, host, request_uri, headers, response, |
785 | 140 | content): | 156 | content): |
786 | @@ -144,6 +160,7 @@ | |||
787 | 144 | yield OAuthAuthentication(self.oauth_data, host, request_uri, headers, | 160 | yield OAuthAuthentication(self.oauth_data, host, request_uri, headers, |
788 | 145 | response, content, self, self.__scheme) | 161 | response, content, self, self.__scheme) |
789 | 146 | 162 | ||
790 | 163 | |||
791 | 147 | def row_is_deleted(row): | 164 | def row_is_deleted(row): |
792 | 148 | """Test if a row is marked as deleted. Smart views 'maps' should not | 165 | """Test if a row is marked as deleted. Smart views 'maps' should not |
793 | 149 | return rows that are marked as deleted, so this function is not often | 166 | return rows that are marked as deleted, so this function is not often |
794 | @@ -174,10 +191,12 @@ | |||
795 | 174 | 191 | ||
796 | 175 | @staticmethod | 192 | @staticmethod |
797 | 176 | def _is_reconnection_fail(ex): | 193 | def _is_reconnection_fail(ex): |
798 | 194 | """Check whether this is the bug in httplib.""" | ||
799 | 177 | return isinstance(ex, AttributeError) and \ | 195 | return isinstance(ex, AttributeError) and \ |
800 | 178 | ex.args == ("'NoneType' object has no attribute 'makefile'",) | 196 | ex.args == ("'NoneType' object has no attribute 'makefile'",) |
801 | 179 | 197 | ||
802 | 180 | def _reconnect(self, uri=None): | 198 | def _reconnect(self, uri=None): |
803 | 199 | """Reconnect after losing connection.""" | ||
804 | 181 | logging.info("Connecting to %s.", | 200 | logging.info("Connecting to %s.", |
805 | 182 | self.server_uri or "discovered local port") | 201 | self.server_uri or "discovered local port") |
806 | 183 | self._server = self._server_class(uri or self.server_uri, | 202 | self._server = self._server_class(uri or self.server_uri, |
807 | @@ -211,8 +230,9 @@ | |||
808 | 211 | """Closure storing the database for lower levels to use when needed. | 230 | """Closure storing the database for lower levels to use when needed. |
809 | 212 | """ | 231 | """ |
810 | 213 | def getter(): | 232 | def getter(): |
813 | 214 | return source_db.get_attachment(document_id, attachment_name), \ | 233 | """Get the attachment and content type.""" |
814 | 215 | content_type | 234 | return source_db.get_attachment( |
815 | 235 | document_id, attachment_name), content_type | ||
816 | 216 | return getter | 236 | return getter |
817 | 217 | 237 | ||
818 | 218 | try: | 238 | try: |
819 | @@ -240,7 +260,7 @@ | |||
820 | 240 | from uuid import uuid4 | 260 | from uuid import uuid4 |
821 | 241 | record.record_id = uuid4().hex | 261 | record.record_id = uuid4().hex |
822 | 242 | try: | 262 | try: |
824 | 243 | self.db[record.record_id] = record._data | 263 | self.db[record.record_id] = record._data # pylint: disable=W0212 |
825 | 244 | except ResourceConflict: | 264 | except ResourceConflict: |
826 | 245 | if record.record_revision is None and not row_is_deleted(record): | 265 | if record.record_revision is None and not row_is_deleted(record): |
827 | 246 | old_record = self.db[record.record_id] | 266 | old_record = self.db[record.record_id] |
828 | @@ -249,14 +269,16 @@ | |||
829 | 249 | # but we have marked deleted internally. Instead of | 269 | # but we have marked deleted internally. Instead of |
830 | 250 | # complaining, pull up the previous revision ID and | 270 | # complaining, pull up the previous revision ID and |
831 | 251 | # add that to the user's record, and re-send it. | 271 | # add that to the user's record, and re-send it. |
832 | 272 | # pylint: disable=W0212 | ||
833 | 252 | record._set_record_revision(old_record.rev) | 273 | record._set_record_revision(old_record.rev) |
834 | 253 | |||
835 | 254 | self.db[record.record_id] = record._data | 274 | self.db[record.record_id] = record._data |
836 | 275 | # pylint: enable=W0212 | ||
837 | 255 | else: | 276 | else: |
838 | 256 | raise | 277 | raise |
839 | 257 | else: | 278 | else: |
840 | 258 | raise | 279 | raise |
841 | 259 | 280 | ||
842 | 281 | # pylint: disable=W0212 | ||
843 | 260 | for attachment_name in getattr(record, "_detached", []): | 282 | for attachment_name in getattr(record, "_detached", []): |
844 | 261 | self.db.delete_attachment(record._data, attachment_name) | 283 | self.db.delete_attachment(record._data, attachment_name) |
845 | 262 | 284 | ||
846 | @@ -266,6 +288,7 @@ | |||
847 | 266 | data, | 288 | data, |
848 | 267 | attachment_name, | 289 | attachment_name, |
849 | 268 | content_type) | 290 | content_type) |
850 | 291 | # pylint: enable=W0212 | ||
851 | 269 | 292 | ||
852 | 270 | return record.record_id | 293 | return record.record_id |
853 | 271 | 294 | ||
854 | @@ -282,6 +305,7 @@ | |||
855 | 282 | # although with a single record we need to test for the | 305 | # although with a single record we need to test for the |
856 | 283 | # revisison, with a batch we do not, but we have to make sure | 306 | # revisison, with a batch we do not, but we have to make sure |
857 | 284 | # that we did not get an error | 307 | # that we did not get an error |
858 | 308 | # pylint: disable=W0212 | ||
859 | 285 | batch_put_result = self.db.update([record._data for record in batch]) | 309 | batch_put_result = self.db.update([record._data for record in batch]) |
860 | 286 | for current_tuple in batch_put_result: | 310 | for current_tuple in batch_put_result: |
861 | 287 | success, docid, rev_or_exc = current_tuple | 311 | success, docid, rev_or_exc = current_tuple |
862 | @@ -289,15 +313,14 @@ | |||
863 | 289 | record = records_hash[docid] | 313 | record = records_hash[docid] |
864 | 290 | # set the new rev | 314 | # set the new rev |
865 | 291 | record._data["_rev"] = rev_or_exc | 315 | record._data["_rev"] = rev_or_exc |
866 | 292 | |||
867 | 293 | for attachment_name in getattr(record, "_detached", []): | 316 | for attachment_name in getattr(record, "_detached", []): |
868 | 294 | self.db.delete_attachment(record._data, attachment_name) | 317 | self.db.delete_attachment(record._data, attachment_name) |
869 | 295 | |||
870 | 296 | for attachment_name in record.list_attachments(): | 318 | for attachment_name in record.list_attachments(): |
871 | 297 | data, content_type = record.attachment_data(attachment_name) | 319 | data, content_type = record.attachment_data(attachment_name) |
872 | 298 | self.db.put_attachment( | 320 | self.db.put_attachment( |
874 | 299 | {"_id":record.record_id, "_rev":record["_rev"]}, | 321 | {"_id": record.record_id, "_rev": record["_rev"]}, |
875 | 300 | data, attachment_name, content_type) | 322 | data, attachment_name, content_type) |
876 | 323 | # pylint: enable=W0212 | ||
877 | 301 | # all success record have the blobs added we return result of | 324 | # all success record have the blobs added we return result of |
878 | 302 | # update | 325 | # update |
879 | 303 | return batch_put_result | 326 | return batch_put_result |
880 | @@ -322,7 +345,7 @@ | |||
881 | 322 | if cached_record is None: | 345 | if cached_record is None: |
882 | 323 | cached_record = self.db[record_id] | 346 | cached_record = self.db[record_id] |
883 | 324 | if isinstance(cached_record, Record): | 347 | if isinstance(cached_record, Record): |
885 | 325 | cached_record = cached_record._data | 348 | cached_record = cached_record._data # pylint: disable=W0212 |
886 | 326 | record = copy.deepcopy(cached_record) | 349 | record = copy.deepcopy(cached_record) |
887 | 327 | # Loop until either failure or success has been determined | 350 | # Loop until either failure or success has been determined |
888 | 328 | while True: | 351 | while True: |
889 | @@ -404,7 +427,7 @@ | |||
890 | 404 | # No atomic updates. Only read & mutate & write. Le sigh. | 427 | # No atomic updates. Only read & mutate & write. Le sigh. |
891 | 405 | # First, get current contents. | 428 | # First, get current contents. |
892 | 406 | try: | 429 | try: |
894 | 407 | view_container = self.db[doc_id]["views"] | 430 | view_container = self.db[doc_id]["views"] |
895 | 408 | except (KeyError, ResourceNotFound): | 431 | except (KeyError, ResourceNotFound): |
896 | 409 | raise KeyError | 432 | raise KeyError |
897 | 410 | 433 | ||
898 | @@ -414,9 +437,7 @@ | |||
899 | 414 | # Construct a new list of objects representing all views to have. | 437 | # Construct a new list of objects representing all views to have. |
900 | 415 | views = [ | 438 | views = [ |
901 | 416 | ViewDefinition(design_doc, k, v.get("map"), v.get("reduce")) | 439 | ViewDefinition(design_doc, k, v.get("map"), v.get("reduce")) |
905 | 417 | for k, v | 440 | for k, v in view_container.iteritems()] |
903 | 418 | in view_container.iteritems() | ||
904 | 419 | ] | ||
906 | 420 | # Push back a new batch of view. Pray to Eris that this doesn't | 441 | # Push back a new batch of view. Pray to Eris that this doesn't |
907 | 421 | # clobber anything we want. | 442 | # clobber anything we want. |
908 | 422 | 443 | ||
909 | @@ -446,9 +467,8 @@ | |||
910 | 446 | view_id_fmt = "_design/%(design_doc)s/_view/%(view_name)s" | 467 | view_id_fmt = "_design/%(design_doc)s/_view/%(view_name)s" |
911 | 447 | return self.db.view(view_id_fmt % locals(), **params) | 468 | return self.db.view(view_id_fmt % locals(), **params) |
912 | 448 | 469 | ||
913 | 449 | |||
914 | 450 | def add_view(self, view_name, map_js, reduce_js, | 470 | def add_view(self, view_name, map_js, reduce_js, |
916 | 451 | design_doc=DEFAULT_DESIGN_DOCUMENT): | 471 | design_doc=DEFAULT_DESIGN_DOCUMENT): |
917 | 452 | """Create a view, given a name and the two parts (map and reduce). | 472 | """Create a view, given a name and the two parts (map and reduce). |
918 | 453 | Return the document id.""" | 473 | Return the document id.""" |
919 | 454 | if design_doc is None: | 474 | if design_doc is None: |
920 | @@ -476,7 +496,7 @@ | |||
921 | 476 | """Return a list of view names for a given design document. There is | 496 | """Return a list of view names for a given design document. There is |
922 | 477 | no error if the design document does not exist or if there are no views | 497 | no error if the design document does not exist or if there are no views |
923 | 478 | in it.""" | 498 | in it.""" |
925 | 479 | doc_id = "_design/%(design_doc)s" % locals() | 499 | doc_id = "_design/%s" % design_doc |
926 | 480 | try: | 500 | try: |
927 | 481 | return list(self.db[doc_id]["views"]) | 501 | return list(self.db[doc_id]["views"]) |
928 | 482 | except (KeyError, ResourceNotFound): | 502 | except (KeyError, ResourceNotFound): |
929 | @@ -542,12 +562,14 @@ | |||
930 | 542 | return viewdata[record_type] | 562 | return viewdata[record_type] |
931 | 543 | 563 | ||
932 | 544 | def get_view_results_as_records(self, view_name, record_type=None): | 564 | def get_view_results_as_records(self, view_name, record_type=None): |
933 | 565 | """Get the results of a view as a list of Record objects.""" | ||
934 | 545 | view_results = self.execute_view(view_name, view_name) | 566 | view_results = self.execute_view(view_name, view_name) |
935 | 546 | if record_type: | 567 | if record_type: |
936 | 547 | view_results = view_results[record_type] | 568 | view_results = view_results[record_type] |
937 | 548 | return [record for record in transform_to_records(view_results)] | 569 | return [record for record in transform_to_records(view_results)] |
938 | 549 | 570 | ||
939 | 550 | def get_all_records(self, record_type=None): | 571 | def get_all_records(self, record_type=None): |
940 | 572 | """Get all records from the database, optionally by record type.""" | ||
941 | 551 | view_name = "get_records_and_type" | 573 | view_name = "get_records_and_type" |
942 | 552 | try: | 574 | try: |
943 | 553 | return self.get_view_results_as_records( | 575 | return self.get_view_results_as_records( |
944 | @@ -568,7 +590,6 @@ | |||
945 | 568 | return self.get_view_results_as_records( | 590 | return self.get_view_results_as_records( |
946 | 569 | view_name, record_type=record_type) | 591 | view_name, record_type=record_type) |
947 | 570 | 592 | ||
948 | 571 | |||
949 | 572 | def get_changes(self, niceness=10): | 593 | def get_changes(self, niceness=10): |
950 | 573 | """Get a list of database changes. This is the sister function of | 594 | """Get a list of database changes. This is the sister function of |
951 | 574 | report_changes that returns a list instead of calling a function for | 595 | report_changes that returns a list instead of calling a function for |
952 | @@ -610,7 +631,7 @@ | |||
953 | 610 | raise IOError( | 631 | raise IOError( |
954 | 611 | "HTTP response code %s.\n%s" % (resp["status"], data)) | 632 | "HTTP response code %s.\n%s" % (resp["status"], data)) |
955 | 612 | structure = json.loads(data) | 633 | structure = json.loads(data) |
957 | 613 | for change in structure.get("results"): | 634 | for change in structure.get("results"): # pylint: disable=E1103 |
958 | 614 | # kw-args can't have unicode keys | 635 | # kw-args can't have unicode keys |
959 | 615 | change_encoded_keys = dict( | 636 | change_encoded_keys = dict( |
960 | 616 | (k.encode("utf8"), v) for k, v in change.iteritems()) | 637 | (k.encode("utf8"), v) for k, v in change.iteritems()) |
961 | 617 | 638 | ||
962 | === modified file 'desktopcouch/records/tests/test_field_registry.py' | |||
963 | --- desktopcouch/records/tests/test_field_registry.py 2010-11-11 19:01:41 +0000 | |||
964 | +++ desktopcouch/records/tests/test_field_registry.py 2010-11-12 14:40:00 +0000 | |||
965 | @@ -17,7 +17,10 @@ | |||
966 | 17 | 17 | ||
967 | 18 | """Test cases for field mapping""" | 18 | """Test cases for field mapping""" |
968 | 19 | 19 | ||
970 | 20 | import copy, doctest, os | 20 | import copy |
971 | 21 | import doctest | ||
972 | 22 | import os | ||
973 | 23 | |||
974 | 21 | from testtools import TestCase | 24 | from testtools import TestCase |
975 | 22 | 25 | ||
976 | 23 | import desktopcouch | 26 | import desktopcouch |
977 | @@ -34,25 +37,35 @@ | |||
978 | 34 | 'not_the_field': 'different value'}, | 37 | 'not_the_field': 'different value'}, |
979 | 35 | 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3': { | 38 | 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3': { |
980 | 36 | 'the field': 'another value'}}, | 39 | 'the field': 'another value'}}, |
983 | 37 | 'application_annotations': {'Test App': {'private_application_annotations':{ | 40 | 'application_annotations': {'Test App': { |
984 | 38 | 'test_field': 'e47455fb-da05-481e-a2c7-88f14d5cc163'}}}} | 41 | 'private_application_annotations': { |
985 | 42 | 'test_field': 'e47455fb-da05-481e-a2c7-88f14d5cc163'}}}} | ||
986 | 39 | 43 | ||
987 | 40 | APP_RECORD = { | 44 | APP_RECORD = { |
988 | 41 | 'record_type': 'http://example.com/test', | 45 | 'record_type': 'http://example.com/test', |
989 | 42 | 'simpleField': 23, | 46 | 'simpleField': 23, |
991 | 43 | 'strawberryField': 'the value',} | 47 | 'strawberryField': 'the value'} |
992 | 44 | 48 | ||
993 | 45 | FIELD_REGISTRY = { | 49 | FIELD_REGISTRY = { |
994 | 46 | 'simpleField': SimpleFieldMapping('simple_field'), | 50 | 'simpleField': SimpleFieldMapping('simple_field'), |
995 | 47 | 'strawberryField': MergeableListFieldMapping( | 51 | 'strawberryField': MergeableListFieldMapping( |
997 | 48 | 'Test App', 'test_field', 'test_fields', 'the_field'),} | 52 | 'Test App', 'test_field', 'test_fields', 'the_field')} |
998 | 53 | |||
999 | 54 | |||
1000 | 55 | class TestRecord(Record): | ||
1001 | 56 | """A test record type.""" | ||
1002 | 57 | |||
1003 | 58 | def __init__(self, data=None): | ||
1004 | 59 | super(TestRecord, self).__init__( | ||
1005 | 60 | data=data, record_type='http://example.com/test') | ||
1006 | 49 | 61 | ||
1007 | 50 | 62 | ||
1008 | 51 | class AppTransformer(Transformer): | 63 | class AppTransformer(Transformer): |
1009 | 52 | """A test transformer class.""" | 64 | """A test transformer class.""" |
1010 | 53 | 65 | ||
1011 | 54 | def __init__(self): | 66 | def __init__(self): |
1013 | 55 | super(AppTransformer, self).__init__('Test App', FIELD_REGISTRY) | 67 | super(AppTransformer, self).__init__( |
1014 | 68 | 'Test App', FIELD_REGISTRY, record_class=TestRecord) | ||
1015 | 56 | 69 | ||
1016 | 57 | 70 | ||
1017 | 58 | class TestFieldMapping(TestCase): | 71 | class TestFieldMapping(TestCase): |
1018 | @@ -83,16 +96,16 @@ | |||
1019 | 83 | 96 | ||
1020 | 84 | def test_mergeable_list_field_mapping1(self): | 97 | def test_mergeable_list_field_mapping1(self): |
1021 | 85 | """Test the MergeableListFieldMapping object.""" | 98 | """Test the MergeableListFieldMapping object.""" |
1023 | 86 | # pylint: disable-msg=W0212 | 99 | # pylint: disable=W0212 |
1024 | 87 | record = Record(self.test_record) | 100 | record = Record(self.test_record) |
1025 | 88 | mapping = MergeableListFieldMapping( | 101 | mapping = MergeableListFieldMapping( |
1026 | 89 | 'Test App', 'test_field', 'test_fields', 'the_field') | 102 | 'Test App', 'test_field', 'test_fields', 'the_field') |
1027 | 90 | self.assertEqual('the value', mapping.getValue(record)) | 103 | self.assertEqual('the value', mapping.getValue(record)) |
1029 | 91 | del record._data['test_fields'][ | 104 | del record._data['test_fields'][ # pylint: disable=W0212 |
1030 | 92 | 'e47455fb-da05-481e-a2c7-88f14d5cc163'] | 105 | 'e47455fb-da05-481e-a2c7-88f14d5cc163'] |
1031 | 93 | mapping.deleteValue(record) | 106 | mapping.deleteValue(record) |
1032 | 94 | self.assertEqual(None, mapping.getValue(record)) | 107 | self.assertEqual(None, mapping.getValue(record)) |
1034 | 95 | # pylint: enable-msg=W0212 | 108 | # pylint: enable=W0212 |
1035 | 96 | 109 | ||
1036 | 97 | def test_mergeable_list_field_mapping_empty_field(self): | 110 | def test_mergeable_list_field_mapping_empty_field(self): |
1037 | 98 | """Test setting empty values in the MergeableListFieldMapping object.""" | 111 | """Test setting empty values in the MergeableListFieldMapping object.""" |
1038 | @@ -112,21 +125,44 @@ | |||
1039 | 112 | self.transformer = AppTransformer() | 125 | self.transformer = AppTransformer() |
1040 | 113 | 126 | ||
1041 | 114 | def test_from_app(self): | 127 | def test_from_app(self): |
1044 | 115 | """Test transformation from app to Ubuntu One.""" | 128 | """Test transformation from app to desktopcouch.""" |
1045 | 116 | # pylint: disable-msg=W0212 | 129 | record = self.transformer.from_app(APP_RECORD) |
1046 | 130 | underlying = record._data # pylint: disable=W0212 | ||
1047 | 131 | self.assertEqual(23, record['simple_field']) | ||
1048 | 132 | the_uuid = record.application_annotations['Test App']\ | ||
1049 | 133 | ['private_application_annotations']['test_field'] | ||
1050 | 134 | self.assertEqual( | ||
1051 | 135 | {'the_field': 'the value'}, | ||
1052 | 136 | underlying['test_fields'][the_uuid]) | ||
1053 | 137 | |||
1054 | 138 | def test_from_app_with_record(self): | ||
1055 | 139 | """Test transformation from app to desktopcouch when passing in | ||
1056 | 140 | an existing record. | ||
1057 | 141 | |||
1058 | 142 | """ | ||
1059 | 117 | record = Record(record_type="http://example.com/test") | 143 | record = Record(record_type="http://example.com/test") |
1060 | 118 | self.transformer.from_app(APP_RECORD, record) | 144 | self.transformer.from_app(APP_RECORD, record) |
1062 | 119 | underlying = record._data | 145 | underlying = record._data # pylint: disable=W0212 |
1063 | 120 | self.assertEqual(23, record['simple_field']) | 146 | self.assertEqual(23, record['simple_field']) |
1064 | 121 | the_uuid = record.application_annotations['Test App']\ | 147 | the_uuid = record.application_annotations['Test App']\ |
1065 | 122 | ['private_application_annotations']['test_field'] | 148 | ['private_application_annotations']['test_field'] |
1066 | 123 | self.assertEqual( | 149 | self.assertEqual( |
1067 | 124 | {'the_field': 'the value'}, | 150 | {'the_field': 'the value'}, |
1068 | 125 | underlying['test_fields'][the_uuid]) | 151 | underlying['test_fields'][the_uuid]) |
1070 | 126 | # pylint: enable-msg=W0212 | 152 | # pylint: enable=W0212 |
1071 | 127 | 153 | ||
1072 | 128 | def test_to_app(self): | 154 | def test_to_app(self): |
1074 | 129 | """Test transformation to app from Ubuntu One.""" | 155 | """Test transformation to app from desktopcouch.""" |
1075 | 156 | record = Record(TEST_RECORD) | ||
1076 | 157 | data = self.transformer.to_app(record) | ||
1077 | 158 | self.assertEqual( | ||
1078 | 159 | {'simpleField': 23, 'strawberryField': 'the value'}, data) | ||
1079 | 160 | |||
1080 | 161 | def test_to_app_with_dictionary(self): | ||
1081 | 162 | """Test transformation to app from desktopcouch when passing | ||
1082 | 163 | in an existing dictionary. | ||
1083 | 164 | |||
1084 | 165 | """ | ||
1085 | 130 | record = Record(TEST_RECORD) | 166 | record = Record(TEST_RECORD) |
1086 | 131 | data = {} | 167 | data = {} |
1087 | 132 | self.transformer.to_app(record, data) | 168 | self.transformer.to_app(record, data) |
1088 | @@ -145,5 +181,4 @@ | |||
1089 | 145 | '../desktopcouch/records/doc/field_registry.txt' | 181 | '../desktopcouch/records/doc/field_registry.txt' |
1090 | 146 | results = doctest.testfile(field_registry_tests_path, | 182 | results = doctest.testfile(field_registry_tests_path, |
1091 | 147 | module_relative=False) | 183 | module_relative=False) |
1092 | 148 | |||
1093 | 149 | self.assertEqual(0, results.failed) | 184 | self.assertEqual(0, results.failed) |
1094 | 150 | 185 | ||
1095 | === modified file 'desktopcouch/records/tests/test_record.py' | |||
1096 | --- desktopcouch/records/tests/test_record.py 2010-11-11 19:01:41 +0000 | |||
1097 | +++ desktopcouch/records/tests/test_record.py 2010-11-12 14:40:00 +0000 | |||
1098 | @@ -19,11 +19,12 @@ | |||
1099 | 19 | 19 | ||
1100 | 20 | """Tests for the RecordDict object on which the Contacts API is built.""" | 20 | """Tests for the RecordDict object on which the Contacts API is built.""" |
1101 | 21 | 21 | ||
1103 | 22 | import doctest, os | 22 | import doctest |
1104 | 23 | import os | ||
1105 | 23 | from testtools import TestCase | 24 | from testtools import TestCase |
1106 | 24 | 25 | ||
1107 | 25 | # pylint does not like relative imports from containing packages | 26 | # pylint does not like relative imports from containing packages |
1109 | 26 | # pylint: disable-msg=F0401 | 27 | # pylint: disable=F0401 |
1110 | 27 | import desktopcouch | 28 | import desktopcouch |
1111 | 28 | from desktopcouch.records.server import CouchDatabase | 29 | from desktopcouch.records.server import CouchDatabase |
1112 | 29 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, | 30 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, |
1113 | @@ -34,7 +35,7 @@ | |||
1114 | 34 | class TestRecords(TestCase): | 35 | class TestRecords(TestCase): |
1115 | 35 | """Test the record functionality""" | 36 | """Test the record functionality""" |
1116 | 36 | 37 | ||
1118 | 37 | # pylint: disable-msg=C0103 | 38 | # pylint: disable=C0103 |
1119 | 38 | def setUp(self): | 39 | def setUp(self): |
1120 | 39 | """Test setup.""" | 40 | """Test setup.""" |
1121 | 40 | super(TestRecords, self).setUp() | 41 | super(TestRecords, self).setUp() |
1122 | @@ -47,7 +48,7 @@ | |||
1123 | 47 | "subfield_uuid": { | 48 | "subfield_uuid": { |
1124 | 48 | "e47455fb-da05-481e-a2c7-88f14d5cc163": { | 49 | "e47455fb-da05-481e-a2c7-88f14d5cc163": { |
1125 | 49 | "field11": "value11", | 50 | "field11": "value11", |
1127 | 50 | "field12": "value12",}, | 51 | "field12": "value12"}, |
1128 | 51 | "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": { | 52 | "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": { |
1129 | 52 | "field21": "value21", | 53 | "field21": "value21", |
1130 | 53 | "field22": "value22"}}, | 54 | "field22": "value22"}}, |
1131 | @@ -56,12 +57,13 @@ | |||
1132 | 56 | "record_type": "http://fnord.org/smorgasbord", | 57 | "record_type": "http://fnord.org/smorgasbord", |
1133 | 57 | } | 58 | } |
1134 | 58 | self.record = Record(self.dict) | 59 | self.record = Record(self.dict) |
1136 | 59 | # pylint: enable-msg=C0103 | 60 | # pylint: enable=C0103 |
1137 | 60 | 61 | ||
1138 | 61 | def test_revision(self): | 62 | def test_revision(self): |
1139 | 62 | """Test document always has a revision field and that the revision | 63 | """Test document always has a revision field and that the revision |
1140 | 63 | changes when the document is updated""" | 64 | changes when the document is updated""" |
1141 | 64 | self.assertEquals(self.record.record_revision, None) | 65 | self.assertEquals(self.record.record_revision, None) |
1142 | 66 | |||
1143 | 65 | def set_rev(rec): | 67 | def set_rev(rec): |
1144 | 66 | """Set revision.""" | 68 | """Set revision.""" |
1145 | 67 | rec.record_revision = "1" | 69 | rec.record_revision = "1" |
1146 | @@ -126,8 +128,7 @@ | |||
1147 | 126 | ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'], | 128 | ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'], |
1148 | 127 | sorted(self.record.keys())) | 129 | sorted(self.record.keys())) |
1149 | 128 | self.assertIn("a", self.record) | 130 | self.assertIn("a", self.record) |
1152 | 129 | self.assertTrue(self.record.has_key("a")) | 131 | self.assertNotIn('f', self.record) |
1151 | 130 | self.assertFalse(self.record.has_key("f")) | ||
1153 | 131 | self.assertNotIn("_id", self.record) # is internal. play dumb. | 132 | self.assertNotIn("_id", self.record) # is internal. play dumb. |
1154 | 132 | 133 | ||
1155 | 133 | def test_application_annotations(self): | 134 | def test_application_annotations(self): |
1156 | @@ -249,7 +250,7 @@ | |||
1157 | 249 | value = [1, 2, 3, 4, 5] | 250 | value = [1, 2, 3, 4, 5] |
1158 | 250 | self.record["subfield_uuid"] = value | 251 | self.record["subfield_uuid"] = value |
1159 | 251 | self.assertRaises(IndexError, self.record["subfield_uuid"].pop, | 252 | self.assertRaises(IndexError, self.record["subfield_uuid"].pop, |
1161 | 252 | len(value)*2) | 253 | len(value) * 2) |
1162 | 253 | 254 | ||
1163 | 254 | def test_mergeable_list_pop_last(self): | 255 | def test_mergeable_list_pop_last(self): |
1164 | 255 | """Test that exception is raised when poping last item""" | 256 | """Test that exception is raised when poping last item""" |
1165 | @@ -272,9 +273,9 @@ | |||
1166 | 272 | 273 | ||
1167 | 273 | def test_dictionary_access_to_mergeable_list(self): | 274 | def test_dictionary_access_to_mergeable_list(self): |
1168 | 274 | """Test that appropriate errors are raised.""" | 275 | """Test that appropriate errors are raised.""" |
1170 | 275 | # pylint: disable-msg=W0212 | 276 | # pylint: disable=W0212 |
1171 | 276 | keys = self.record["subfield_uuid"]._data.keys() | 277 | keys = self.record["subfield_uuid"]._data.keys() |
1173 | 277 | # pylint: enable-msg=W0212 | 278 | # pylint: enable=W0212 |
1174 | 278 | self.assertRaises( | 279 | self.assertRaises( |
1175 | 279 | TypeError, | 280 | TypeError, |
1176 | 280 | self.record["subfield_uuid"].__getitem__, keys[0]) | 281 | self.record["subfield_uuid"].__getitem__, keys[0]) |
1177 | @@ -298,9 +299,9 @@ | |||
1178 | 298 | 299 | ||
1179 | 299 | def test_uuid_like_keys(self): | 300 | def test_uuid_like_keys(self): |
1180 | 300 | """Test that appropriate errors are raised.""" | 301 | """Test that appropriate errors are raised.""" |
1182 | 301 | # pylint: disable-msg=W0212 | 302 | # pylint: disable=W0212 |
1183 | 302 | keys = self.record["subfield_uuid"]._data.keys() | 303 | keys = self.record["subfield_uuid"]._data.keys() |
1185 | 303 | # pylint: enable-msg=W0212 | 304 | # pylint: enable=W0212 |
1186 | 304 | self.assertRaises( | 305 | self.assertRaises( |
1187 | 305 | IllegalKeyException, | 306 | IllegalKeyException, |
1188 | 306 | self.record["subfield"].__setitem__, keys[0], 'stuff') | 307 | self.record["subfield"].__setitem__, keys[0], 'stuff') |
1189 | @@ -313,7 +314,7 @@ | |||
1190 | 313 | def test_run_doctests(self): | 314 | def test_run_doctests(self): |
1191 | 314 | """Run all doc tests from here to set the proper context (ctx)""" | 315 | """Run all doc tests from here to set the proper context (ctx)""" |
1192 | 315 | ctx = test_environment.test_context | 316 | ctx = test_environment.test_context |
1194 | 316 | globs = { "db": CouchDatabase('testing', create=True, ctx=ctx) } | 317 | globs = {"db": CouchDatabase('testing', create=True, ctx=ctx)} |
1195 | 317 | 318 | ||
1196 | 318 | records_tests_path = os.path.dirname( | 319 | records_tests_path = os.path.dirname( |
1197 | 319 | desktopcouch.__file__) + '/records/doc/records.txt' | 320 | desktopcouch.__file__) + '/records/doc/records.txt' |
1198 | @@ -333,25 +334,24 @@ | |||
1199 | 333 | results = doctest.testfile( | 334 | results = doctest.testfile( |
1200 | 334 | '../desktopcouch/records/doc/an_example_application.txt', | 335 | '../desktopcouch/records/doc/an_example_application.txt', |
1201 | 335 | module_relative=False) | 336 | module_relative=False) |
1202 | 336 | |||
1203 | 337 | self.assertEqual(0, results.failed) | 337 | self.assertEqual(0, results.failed) |
1204 | 338 | 338 | ||
1205 | 339 | def test_record_id(self): | 339 | def test_record_id(self): |
1206 | 340 | """Test all passible way to assign a record id""" | 340 | """Test all passible way to assign a record id""" |
1208 | 341 | data = {"_id":"recordid"} | 341 | data = {"_id": "recordid"} |
1209 | 342 | record = Record(data, record_type="url") | 342 | record = Record(data, record_type="url") |
1210 | 343 | self.assertEqual(data["_id"], record.record_id) | 343 | self.assertEqual(data["_id"], record.record_id) |
1211 | 344 | data = {} | 344 | data = {} |
1212 | 345 | record_id = "recordid" | 345 | record_id = "recordid" |
1213 | 346 | record = Record(data, record_type="url", record_id=record_id) | 346 | record = Record(data, record_type="url", record_id=record_id) |
1214 | 347 | self.assertEqual(record_id, record.record_id) | 347 | self.assertEqual(record_id, record.record_id) |
1216 | 348 | data = {"_id":"differentid"} | 348 | data = {"_id": "differentid"} |
1217 | 349 | self.assertRaises(ValueError, | 349 | self.assertRaises(ValueError, |
1218 | 350 | Record, data, record_id=record_id, record_type="url") | 350 | Record, data, record_id=record_id, record_type="url") |
1219 | 351 | 351 | ||
1220 | 352 | def test_record_type_version(self): | 352 | def test_record_type_version(self): |
1221 | 353 | """Test record type version support""" | 353 | """Test record type version support""" |
1223 | 354 | data = {"_id":"recordid"} | 354 | data = {"_id": "recordid"} |
1224 | 355 | record1 = Record(data, record_type="url") | 355 | record1 = Record(data, record_type="url") |
1225 | 356 | self.assertIs(None, record1.record_type_version) | 356 | self.assertIs(None, record1.record_type_version) |
1226 | 357 | record2 = Record(data, record_type="url", record_type_version=1) | 357 | record2 = Record(data, record_type="url", record_type_version=1) |
1227 | @@ -361,7 +361,7 @@ | |||
1228 | 361 | class TestRecordFactory(TestCase): | 361 | class TestRecordFactory(TestCase): |
1229 | 362 | """Test Record/Mergeable List factories.""" | 362 | """Test Record/Mergeable List factories.""" |
1230 | 363 | 363 | ||
1232 | 364 | # pylint: disable-msg=C0103 | 364 | # pylint: disable=C0103 |
1233 | 365 | def setUp(self): | 365 | def setUp(self): |
1234 | 366 | """Test setup.""" | 366 | """Test setup.""" |
1235 | 367 | super(TestRecordFactory, self).setUp() | 367 | super(TestRecordFactory, self).setUp() |
1236 | @@ -370,7 +370,7 @@ | |||
1237 | 370 | "b": "B", | 370 | "b": "B", |
1238 | 371 | "subfield": { | 371 | "subfield": { |
1239 | 372 | "field11s": "value11s", | 372 | "field11s": "value11s", |
1241 | 373 | "field12s": "value12s",}, | 373 | "field12s": "value12s"}, |
1242 | 374 | "subfield_uuid": | 374 | "subfield_uuid": |
1243 | 375 | [ | 375 | [ |
1244 | 376 | {"field11": "value11", | 376 | {"field11": "value11", |
1245 | @@ -379,7 +379,7 @@ | |||
1246 | 379 | "field22": "value22"}], | 379 | "field22": "value22"}], |
1247 | 380 | "record_type": "http://fnord.org/smorgasbord", | 380 | "record_type": "http://fnord.org/smorgasbord", |
1248 | 381 | } | 381 | } |
1250 | 382 | # pylint: enable-msg=C0103 | 382 | # pylint: enable=C0103 |
1251 | 383 | 383 | ||
1252 | 384 | def test_build(self): | 384 | def test_build(self): |
1253 | 385 | """Test RecordDict/MergeableList factory method.""" | 385 | """Test RecordDict/MergeableList factory method.""" |
1254 | 386 | 386 | ||
1255 | === modified file 'desktopcouch/records/tests/test_server.py' | |||
1256 | --- desktopcouch/records/tests/test_server.py 2010-11-03 22:57:06 +0000 | |||
1257 | +++ desktopcouch/records/tests/test_server.py 2010-11-12 14:40:00 +0000 | |||
1258 | @@ -32,27 +32,23 @@ | |||
1259 | 32 | from desktopcouch.platform import find_pid | 32 | from desktopcouch.platform import find_pid |
1260 | 33 | 33 | ||
1261 | 34 | # pylint can't deal with failing imports even when they're handled | 34 | # pylint can't deal with failing imports even when they're handled |
1263 | 35 | # pylint: disable-msg=F0401 | 35 | # pylint: disable=F0401 |
1264 | 36 | try: | 36 | try: |
1265 | 37 | from io import StringIO | 37 | from io import StringIO |
1266 | 38 | except ImportError: | 38 | except ImportError: |
1267 | 39 | from cStringIO import StringIO as StringIO | 39 | from cStringIO import StringIO as StringIO |
1269 | 40 | # pylint: enable-msg=F0401 | 40 | # pylint: enable=F0401 |
1270 | 41 | 41 | ||
1271 | 42 | DCTRASH = 'dctrash' | 42 | DCTRASH = 'dctrash' |
1272 | 43 | 43 | ||
1273 | 44 | FAKE_RECORD_TYPE = "http://example.org/test" | ||
1274 | 45 | |||
1275 | 46 | js = """ | ||
1276 | 47 | function(doc) { | ||
1277 | 48 | if (doc.record_type == '%s') { | ||
1278 | 49 | emit(doc._id, null); | ||
1279 | 50 | } | ||
1280 | 51 | }""" % FAKE_RECORD_TYPE | ||
1281 | 52 | |||
1282 | 53 | 44 | ||
1283 | 54 | def get_test_context(): | 45 | def get_test_context(): |
1285 | 55 | return test_environment.test_context | 46 | """Return test context.""" |
1286 | 47 | return test_environment.test_context | ||
1287 | 48 | |||
1288 | 49 | # pylint: disable=W0212 | ||
1289 | 50 | # I don't care about private members in tests. | ||
1290 | 51 | |||
1291 | 56 | 52 | ||
1292 | 57 | class TestCouchDatabaseDeprecated(testtools.TestCase): | 53 | class TestCouchDatabaseDeprecated(testtools.TestCase): |
1293 | 58 | """Test specific for CouchDatabase""" | 54 | """Test specific for CouchDatabase""" |
1294 | @@ -84,9 +80,11 @@ | |||
1295 | 84 | super(TestCouchDatabaseDeprecated, self).tearDown() | 80 | super(TestCouchDatabaseDeprecated, self).tearDown() |
1296 | 85 | 81 | ||
1297 | 86 | def maybe_die(self): | 82 | def maybe_die(self): |
1298 | 83 | """Method that could kill couchdb. Or could it? Or COULD it?""" | ||
1299 | 87 | pass | 84 | pass |
1300 | 88 | 85 | ||
1302 | 89 | def wait_until_server_dead(self, pid=None): | 86 | def wait_until_server_dead(self, pid=None): # pylint: disable=R0201 |
1303 | 87 | """Wait until the server is no longer breathing.""" | ||
1304 | 90 | if pid is not None: | 88 | if pid is not None: |
1305 | 91 | pid = find_pid( | 89 | pid = find_pid( |
1306 | 92 | start_if_not_running=False, ctx=get_test_context()) | 90 | start_if_not_running=False, ctx=get_test_context()) |
1307 | @@ -102,11 +100,12 @@ | |||
1308 | 102 | def test_get_records_by_record_type_save_view(self): | 100 | def test_get_records_by_record_type_save_view(self): |
1309 | 103 | """Test getting mutliple records by type""" | 101 | """Test getting mutliple records by type""" |
1310 | 104 | records = self.database.get_records( | 102 | records = self.database.get_records( |
1312 | 105 | record_type="test.com",create_view=True) | 103 | record_type="test.com", create_view=True) |
1313 | 106 | self.maybe_die() # should be able to survive couchdb death | 104 | self.maybe_die() # should be able to survive couchdb death |
1314 | 107 | self.assertEqual(3, len(records)) | 105 | self.assertEqual(3, len(records)) |
1315 | 108 | 106 | ||
1316 | 109 | def test_func_get_records(self): | 107 | def test_func_get_records(self): |
1317 | 108 | """Functional test of get_records.""" | ||
1318 | 110 | record_ids_we_care_about = set() | 109 | record_ids_we_care_about = set() |
1319 | 111 | good_record_type = "http://example.com/unittest/good" | 110 | good_record_type = "http://example.com/unittest/good" |
1320 | 112 | other_record_type = "http://example.com/unittest/bad" | 111 | other_record_type = "http://example.com/unittest/bad" |
1321 | @@ -145,18 +144,19 @@ | |||
1322 | 145 | design_doc="mustNotExist", create_view=False) | 144 | design_doc="mustNotExist", create_view=False) |
1323 | 146 | 145 | ||
1324 | 147 | def test_get_view_by_type_new_but_already(self): | 146 | def test_get_view_by_type_new_but_already(self): |
1325 | 147 | """Test calling the view.""" | ||
1326 | 148 | self.database.get_records(create_view=True) | 148 | self.database.get_records(create_view=True) |
1327 | 149 | self.maybe_die() # should be able to survive couchdb death | 149 | self.maybe_die() # should be able to survive couchdb death |
1328 | 150 | self.database.get_records(create_view=True) | 150 | self.database.get_records(create_view=True) |
1329 | 151 | # No exceptions on second run? Yay. | 151 | # No exceptions on second run? Yay. |
1330 | 152 | 152 | ||
1331 | 153 | def test_get_view_by_type_createxcl_fail(self): | 153 | def test_get_view_by_type_createxcl_fail(self): |
1332 | 154 | """Test that calling the view without creating it fails.""" | ||
1333 | 154 | self.database.get_records(create_view=True) | 155 | self.database.get_records(create_view=True) |
1334 | 155 | self.maybe_die() # should be able to survive couchdb death | 156 | self.maybe_die() # should be able to survive couchdb death |
1335 | 156 | self.assertRaises(KeyError, self.database.get_records, create_view=None) | 157 | self.assertRaises(KeyError, self.database.get_records, create_view=None) |
1336 | 157 | 158 | ||
1337 | 158 | 159 | ||
1338 | 159 | |||
1339 | 160 | class TestCouchDatabase(testtools.TestCase): | 160 | class TestCouchDatabase(testtools.TestCase): |
1340 | 161 | """tests specific for CouchDatabase""" | 161 | """tests specific for CouchDatabase""" |
1341 | 162 | 162 | ||
1342 | @@ -187,9 +187,11 @@ | |||
1343 | 187 | super(TestCouchDatabase, self).tearDown() | 187 | super(TestCouchDatabase, self).tearDown() |
1344 | 188 | 188 | ||
1345 | 189 | def maybe_die(self): | 189 | def maybe_die(self): |
1346 | 190 | """Method that could kill couchdb. Or could it? Or COULD it?""" | ||
1347 | 190 | pass | 191 | pass |
1348 | 191 | 192 | ||
1350 | 192 | def wait_until_server_dead(self, pid=None): | 193 | def wait_until_server_dead(self, pid=None): # pylint: disable=R0201 |
1351 | 194 | """Wait until the server is no longer breathing.""" | ||
1352 | 193 | if pid is not None: | 195 | if pid is not None: |
1353 | 194 | pid = find_pid( | 196 | pid = find_pid( |
1354 | 195 | start_if_not_running=False, ctx=get_test_context()) | 197 | start_if_not_running=False, ctx=get_test_context()) |
1355 | @@ -203,6 +205,7 @@ | |||
1356 | 203 | break | 205 | break |
1357 | 204 | 206 | ||
1358 | 205 | def test_database_not_exists(self): | 207 | def test_database_not_exists(self): |
1359 | 208 | """Test that the database does not exist.""" | ||
1360 | 206 | self.assertRaises( | 209 | self.assertRaises( |
1361 | 207 | NoSuchDatabase, CouchDatabase, "this-must-not-exist", create=False) | 210 | NoSuchDatabase, CouchDatabase, "this-must-not-exist", create=False) |
1362 | 208 | 211 | ||
1363 | @@ -251,7 +254,7 @@ | |||
1364 | 251 | # put the batch and ensure that the records have been added | 254 | # put the batch and ensure that the records have been added |
1365 | 252 | batch_result = self.database.put_records_batch(batch) | 255 | batch_result = self.database.put_records_batch(batch) |
1366 | 253 | for current_tuple in batch_result: | 256 | for current_tuple in batch_result: |
1368 | 254 | success, docid, rev_or_exc = current_tuple | 257 | success, docid, rev_or_exc = current_tuple # pylint: disable=W0612 |
1369 | 255 | if success: | 258 | if success: |
1370 | 256 | self.assertTrue(self.database._server[self.dbname][docid]) | 259 | self.assertTrue(self.database._server[self.dbname][docid]) |
1371 | 257 | else: | 260 | else: |
1372 | @@ -299,15 +302,6 @@ | |||
1373 | 299 | 302 | ||
1374 | 300 | self.database.delete_record(new_record_id) | 303 | self.database.delete_record(new_record_id) |
1375 | 301 | 304 | ||
1376 | 302 | def test_get_deleted_record(self): | ||
1377 | 303 | """Test (not) getting a deleted record.""" | ||
1378 | 304 | record = Record({'record_number': 0}, record_type="http://example.com/") | ||
1379 | 305 | record_id = self.database.put_record(record) | ||
1380 | 306 | self.database.delete_record(record_id) | ||
1381 | 307 | self.maybe_die() # should be able to survive couchdb death | ||
1382 | 308 | retrieved_record = self.database.get_record(record_id) | ||
1383 | 309 | self.assertEqual(None, retrieved_record) | ||
1384 | 310 | |||
1385 | 311 | def test_record_exists(self): | 305 | def test_record_exists(self): |
1386 | 312 | """Test checking whether a record exists.""" | 306 | """Test checking whether a record exists.""" |
1387 | 313 | record = Record({'record_number': 0}, record_type="http://example.com/") | 307 | record = Record({'record_number': 0}, record_type="http://example.com/") |
1388 | @@ -338,6 +332,7 @@ | |||
1389 | 338 | self.assertEqual(3, working_copy['field3']) | 332 | self.assertEqual(3, working_copy['field3']) |
1390 | 339 | 333 | ||
1391 | 340 | def test_view_add_and_delete(self): | 334 | def test_view_add_and_delete(self): |
1392 | 335 | """Test adding and deleting a view.""" | ||
1393 | 341 | design_doc = "design" | 336 | design_doc = "design" |
1394 | 342 | view1_name = "unit_tests_are_wonderful" | 337 | view1_name = "unit_tests_are_wonderful" |
1395 | 343 | view2_name = "unit_tests_are_marvelous" | 338 | view2_name = "unit_tests_are_marvelous" |
1396 | @@ -365,6 +360,7 @@ | |||
1397 | 365 | KeyError, self.database.delete_view, view2_name, design_doc) | 360 | KeyError, self.database.delete_view, view2_name, design_doc) |
1398 | 366 | 361 | ||
1399 | 367 | def test_func_get_all_records(self): | 362 | def test_func_get_all_records(self): |
1400 | 363 | """Functional test of get_all_records.""" | ||
1401 | 368 | record_ids_we_care_about = set() | 364 | record_ids_we_care_about = set() |
1402 | 369 | good_record_type = "http://example.com/unittest/good" | 365 | good_record_type = "http://example.com/unittest/good" |
1403 | 370 | other_record_type = "http://example.com/unittest/bad" | 366 | other_record_type = "http://example.com/unittest/bad" |
1404 | @@ -401,6 +397,7 @@ | |||
1405 | 401 | self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero") | 397 | self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero") |
1406 | 402 | 398 | ||
1407 | 403 | def test_list_views(self): | 399 | def test_list_views(self): |
1408 | 400 | """Test the list views.""" | ||
1409 | 404 | design_doc = "d" | 401 | design_doc = "d" |
1410 | 405 | self.assertEqual(self.database.list_views(design_doc), []) | 402 | self.assertEqual(self.database.list_views(design_doc), []) |
1411 | 406 | 403 | ||
1412 | @@ -415,12 +412,14 @@ | |||
1413 | 415 | self.assertEqual(self.database.list_views(design_doc), []) | 412 | self.assertEqual(self.database.list_views(design_doc), []) |
1414 | 416 | 413 | ||
1415 | 417 | def test_get_view_by_type_new_but_already(self): | 414 | def test_get_view_by_type_new_but_already(self): |
1416 | 415 | """Test calling the view.""" | ||
1417 | 418 | self.database.get_all_records() | 416 | self.database.get_all_records() |
1418 | 419 | self.maybe_die() # should be able to survive couchdb death | 417 | self.maybe_die() # should be able to survive couchdb death |
1419 | 420 | self.database.get_all_records() | 418 | self.database.get_all_records() |
1420 | 421 | # No exceptions on second run? Yay. | 419 | # No exceptions on second run? Yay. |
1421 | 422 | 420 | ||
1422 | 423 | def test_get_changes(self): | 421 | def test_get_changes(self): |
1423 | 422 | """Test get_changes.""" | ||
1424 | 424 | self.test_put_record() | 423 | self.test_put_record() |
1425 | 425 | self.test_update_fields() | 424 | self.test_update_fields() |
1426 | 426 | self.test_delete_record() | 425 | self.test_delete_record() |
1427 | @@ -435,7 +434,10 @@ | |||
1428 | 435 | self.failUnless("id" in change) | 434 | self.failUnless("id" in change) |
1429 | 436 | 435 | ||
1430 | 437 | def test_report_changes_polite(self): | 436 | def test_report_changes_polite(self): |
1431 | 437 | """Test reporting changes.""" | ||
1432 | 438 | |||
1433 | 438 | def rep(**kwargs): | 439 | def rep(**kwargs): |
1434 | 440 | """Check the keyword arguments for changes and id.""" | ||
1435 | 439 | self.failUnless("changes" in kwargs) | 441 | self.failUnless("changes" in kwargs) |
1436 | 440 | self.failUnless("id" in kwargs) | 442 | self.failUnless("id" in kwargs) |
1437 | 441 | 443 | ||
1438 | @@ -453,7 +455,10 @@ | |||
1439 | 453 | self.assertEqual(0, count) | 455 | self.assertEqual(0, count) |
1440 | 454 | 456 | ||
1441 | 455 | def test_report_changes_exceptions(self): | 457 | def test_report_changes_exceptions(self): |
1442 | 458 | """Test reporting changes in the face of exceptions.""" | ||
1443 | 459 | |||
1444 | 456 | def rep(**kwargs): | 460 | def rep(**kwargs): |
1445 | 461 | """Check the keyword arguments for changes and id.""" | ||
1446 | 457 | self.failUnless("changes" in kwargs) | 462 | self.failUnless("changes" in kwargs) |
1447 | 458 | self.failUnless("id" in kwargs) | 463 | self.failUnless("id" in kwargs) |
1448 | 459 | 464 | ||
1449 | @@ -470,7 +475,7 @@ | |||
1450 | 470 | 475 | ||
1451 | 471 | # Exceptions in our callbacks do not consume an event. | 476 | # Exceptions in our callbacks do not consume an event. |
1452 | 472 | self.assertRaises( | 477 | self.assertRaises( |
1454 | 473 | ZeroDivisionError, self.database.report_changes, lambda **kw: 1/0) | 478 | ZeroDivisionError, self.database.report_changes, lambda **kw: 1 / 0) |
1455 | 474 | 479 | ||
1456 | 475 | # Ensure pos'n is same. | 480 | # Ensure pos'n is same. |
1457 | 476 | self.assertEqual(saved_position, self.database._changes_since) | 481 | self.assertEqual(saved_position, self.database._changes_since) |
1458 | @@ -488,7 +493,10 @@ | |||
1459 | 488 | self.assertEqual(saved_position + 1, self.database._changes_since) | 493 | self.assertEqual(saved_position + 1, self.database._changes_since) |
1460 | 489 | 494 | ||
1461 | 490 | def test_report_changes_all_ops_give_known_keys(self): | 495 | def test_report_changes_all_ops_give_known_keys(self): |
1462 | 496 | """Test reporting changes results have the right keys.""" | ||
1463 | 497 | |||
1464 | 491 | def rep(**kwargs): | 498 | def rep(**kwargs): |
1465 | 499 | """Check the keyword arguments for changes and id.""" | ||
1466 | 492 | self.failUnless("changes" in kwargs) | 500 | self.failUnless("changes" in kwargs) |
1467 | 493 | self.failUnless("id" in kwargs) | 501 | self.failUnless("id" in kwargs) |
1468 | 494 | 502 | ||
1469 | @@ -498,7 +506,10 @@ | |||
1470 | 498 | self.database.report_changes(rep) | 506 | self.database.report_changes(rep) |
1471 | 499 | 507 | ||
1472 | 500 | def test_report_changes_nochanges(self): | 508 | def test_report_changes_nochanges(self): |
1473 | 509 | """Test report changes when there are none.""" | ||
1474 | 510 | |||
1475 | 501 | def rep(**kwargs): | 511 | def rep(**kwargs): |
1476 | 512 | """Check the keyword arguments for changes and id.""" | ||
1477 | 502 | self.failUnless("changes" in kwargs) | 513 | self.failUnless("changes" in kwargs) |
1478 | 503 | self.failUnless("id" in kwargs) | 514 | self.failUnless("id" in kwargs) |
1479 | 504 | 515 | ||
1480 | @@ -515,6 +526,7 @@ | |||
1481 | 515 | self.assertEqual(saved_position, self.database._changes_since) | 526 | self.assertEqual(saved_position, self.database._changes_since) |
1482 | 516 | 527 | ||
1483 | 517 | def test_attachments(self): | 528 | def test_attachments(self): |
1484 | 529 | """Test attachments.""" | ||
1485 | 518 | content = StringIO("0123456789\n==========\n\n" * 5) | 530 | content = StringIO("0123456789\n==========\n\n" * 5) |
1486 | 519 | 531 | ||
1487 | 520 | constructed_record = Record( | 532 | constructed_record = Record( |
1488 | @@ -529,12 +541,12 @@ | |||
1489 | 529 | constructed_record.attach("string", "another document", "text/plain") | 541 | constructed_record.attach("string", "another document", "text/plain") |
1490 | 530 | 542 | ||
1491 | 531 | self.maybe_die() # should be able to survive couchdb death | 543 | self.maybe_die() # should be able to survive couchdb death |
1493 | 532 | constructed_record.attach("XXXXXXXXX", "never used", "text/plain") | 544 | constructed_record.attach( |
1494 | 545 | "SOMETHINGSOMETHING", "never used", "text/plain") | ||
1495 | 533 | constructed_record.detach("never used") # detach works before commit. | 546 | constructed_record.detach("never used") # detach works before commit. |
1496 | 534 | 547 | ||
1497 | 535 | # We can read from a document that we constructed. | 548 | # We can read from a document that we constructed. |
1500 | 536 | out_file, out_content_type = \ | 549 | _, out_content_type = constructed_record.attachment_data("nu/mbe/rs") |
1499 | 537 | constructed_record.attachment_data("nu/mbe/rs") | ||
1501 | 538 | self.assertEqual(out_content_type, "text/plain") | 550 | self.assertEqual(out_content_type, "text/plain") |
1502 | 539 | 551 | ||
1503 | 540 | # One can not put another document of the same name. | 552 | # One can not put another document of the same name. |
1504 | @@ -604,6 +616,7 @@ | |||
1505 | 604 | self.database.delete_record(record_id) | 616 | self.database.delete_record(record_id) |
1506 | 605 | 617 | ||
1507 | 606 | def test_view_fetch(self): | 618 | def test_view_fetch(self): |
1508 | 619 | """Test view fetch.""" | ||
1509 | 607 | design_doc = "test_view_fetch" | 620 | design_doc = "test_view_fetch" |
1510 | 608 | view1_name = "unit_tests_are_great_yeah" | 621 | view1_name = "unit_tests_are_great_yeah" |
1511 | 609 | 622 | ||
1512 | @@ -641,7 +654,7 @@ | |||
1513 | 641 | non_working_copy['field3'] = 3 | 654 | non_working_copy['field3'] = 3 |
1514 | 642 | self.database.put_record(non_working_copy) | 655 | self.database.put_record(non_working_copy) |
1515 | 643 | self.database.update_fields( | 656 | self.database.update_fields( |
1517 | 644 | record_id, {'field1': 11,('nested', 'sub2'): 's2-changed'}, | 657 | record_id, {'field1': 11, ('nested', 'sub2'): 's2-changed'}, |
1518 | 645 | cached_record=record) | 658 | cached_record=record) |
1519 | 646 | working_copy = self.database.get_record(record_id) | 659 | working_copy = self.database.get_record(record_id) |
1520 | 647 | self.assertEqual(0, working_copy['record_number']) | 660 | self.assertEqual(0, working_copy['record_number']) |
1521 | 648 | 661 | ||
1522 | === modified file 'utilities/lint.sh' | |||
1523 | --- utilities/lint.sh 2010-11-11 19:01:41 +0000 | |||
1524 | +++ utilities/lint.sh 2010-11-12 14:40:00 +0000 | |||
1525 | @@ -37,7 +37,7 @@ | |||
1526 | 37 | fi | 37 | fi |
1527 | 38 | 38 | ||
1528 | 39 | export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH" | 39 | export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH" |
1530 | 40 | pylint="`which pylint` -r n -i y --variable-rgx=[a-z_][a-z0-9_]{0,30} --method-rgx=[a-z_][a-z0-9_]{2,} -d I0011,R0904,R0913" | 40 | pylint="`which pylint` -r n -i y --variable-rgx=[a-z_][a-z0-9_]{0,30} --attr-rgx=[a-z_][a-z0-9_]{1,30} --method-rgx=[a-z_][a-z0-9_]{2,} -d I0011,R0902,R0904,R0913,W0142" |
1531 | 41 | 41 | ||
1532 | 42 | pylint_notices=`$pylint $pyfiles` | 42 | pylint_notices=`$pylint $pyfiles` |
1533 | 43 | 43 |
90% of the diff comes from delinting, you'll be happy to know.