Merge lp:~thisfred/desktopcouch/fix-fieldmappings into lp:desktopcouch

Proposed by Eric Casteleijn
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
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.)

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

90% of the diff comes from delinting, you'll be happy to know.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

The meat of the real changes is in field_registry.py and its tests

Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Vincenzo Di Somma (vds) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

Attempt to merge into lp:desktopcouch failed due to conflicts:

text conflict in desktopcouch/records/tests/test_field_registry.py
text conflict in desktopcouch/records/tests/test_record.py
text conflict in utilities/lint.sh

Revision history for this message
Chad Miller (cmiller) wrote :

Approve revno 204.

review: Approve
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2010-11-12 14:40:00 +0000
@@ -0,0 +1,2 @@
1_trial_temp
2.coverage
03
=== modified file 'bin/desktopcouch-pair'
--- bin/desktopcouch-pair 2010-04-05 21:17:42 +0000
+++ bin/desktopcouch-pair 2010-11-12 14:40:00 +0000
@@ -22,7 +22,7 @@
2222
23A tool to set two local machines to replicate their couchdb instances to each23A tool to set two local machines to replicate their couchdb instances to each
24other, or to set this machine to replicate to-and-from Ubuntu One (and perhaps24other, or to set this machine to replicate to-and-from Ubuntu One (and perhaps
25other cloud services). 25other cloud services).
2626
27Local-Pairing Authentication27Local-Pairing Authentication
28----------------------------28----------------------------
@@ -37,7 +37,7 @@
3737
38Alice then computes the SHA512 digest of Bob's secret and compares it to be38Alice then computes the SHA512 digest of Bob's secret and compares it to be
39sure that the other machine is indeed the user's. Alice then concatenates39sure that the other machine is indeed the user's. Alice then concatenates
40Bob's secret and the public seed, and sends the resulting hex digest back to 40Bob's secret and the public seed, and sends the resulting hex digest back to
41Bob to prove that she received the secret from the user. Alice sets herself to41Bob to prove that she received the secret from the user. Alice sets herself to
42replicate to Bob.42replicate to Bob.
4343
@@ -50,7 +50,7 @@
50import logging50import logging
51import getpass51import getpass
52import gettext52import gettext
53# gettext implements "_" function. pylint: disable-msg=E060253# gettext implements "_" function. pylint: disable=E0602
54import random54import random
55import cgi55import cgi
5656
@@ -122,13 +122,13 @@
122122
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.
124 We generate a secret message and a public seed. We get the SHA512 hex124 We generate a secret message and a public seed. We get the SHA512 hex
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
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
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.
128128
129 Eventually, we receive a message back from Alice. We compute the 129 Eventually, we receive a message back from Alice. We compute the
130 secret message we started with concatenated with the public seed, and 130 secret message we started with concatenated with the public seed, and
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
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."""
133133
134 def delete_event(self, widget, event, data=None):134 def delete_event(self, widget, event, data=None):
@@ -167,7 +167,7 @@
167 self.secret_message = secret_message167 self.secret_message = secret_message
168 self.public_seed = generate_secret()168 self.public_seed = generate_secret()
169169
170 self.inviter = network_io.start_send_invitation(hostname, port, 170 self.inviter = network_io.start_send_invitation(hostname, port,
171 self.auth_completed, self.secret_message, self.public_seed,171 self.auth_completed, self.secret_message, self.public_seed,
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],
173 local_files.get_oauth_tokens())173 local_files.get_oauth_tokens())
@@ -180,7 +180,7 @@
180 """us, and to prove veracity of the invitation we\n""" +180 """us, and to prove veracity of the invitation we\n""" +
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""" +
182 """<span font-size="xx-large" color="blue" weight="bold">""" +182 """<span font-size="xx-large" color="blue" weight="bold">""" +
183 """<tt>%s</tt></span> .""") % 183 """<tt>%s</tt></span> .""") %
184 (cgi.escape(service), cgi.escape(self.secret_message)))184 (cgi.escape(service), cgi.escape(self.secret_message)))
185 text.set_justify(gtk.JUSTIFY_CENTER)185 text.set_justify(gtk.JUSTIFY_CENTER)
186 top_vbox.pack_start(text, False, False, 10)186 top_vbox.pack_start(text, False, False, 10)
@@ -197,7 +197,7 @@
197197
198class AcceptInvitation:198class AcceptInvitation:
199 """We're part of 'Alice' in this module's story.199 """We're part of 'Alice' in this module's story.
200 200
201 We've received an invitation. We now send the other end a secret key. The201 We've received an invitation. We now send the other end a secret key. The
202 secret should make its way back to us via meatspace. We open a dialog202 secret should make its way back to us via meatspace. We open a dialog
203 asking for that secret here, which we validate.203 asking for that secret here, which we validate.
@@ -258,7 +258,7 @@
258258
259 self.result = gtk.Label("")259 self.result = gtk.Label("")
260 self.result.show()260 self.result.show()
261 self.entry_box.connect("changed", 261 self.entry_box.connect("changed",
262 lambda widget: self.result.set_text(""))262 lambda widget: self.result.set_text(""))
263 top_vbox.pack_start(self.result, False, False, 0)263 top_vbox.pack_start(self.result, False, False, 0)
264264
@@ -300,7 +300,7 @@
300300
301class Listening:301class Listening:
302 """We're part of 'Alice' in this module's story.302 """We're part of 'Alice' in this module's story.
303 303
304 Window that starts listening for other machines to pick *us* to pair304 Window that starts listening for other machines to pick *us* to pair
305 with. There must be at least one of these on the network for pairing to305 with. There must be at least one of these on the network for pairing to
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."""
@@ -337,7 +337,7 @@
337 we're listening."""337 we're listening."""
338338
339 self.listener = network_io.ListenForInvitations(339 self.listener = network_io.ListenForInvitations(
340 self.receive_invitation_challenge, 340 self.receive_invitation_challenge,
341 lambda: self.window.destroy(),341 lambda: self.window.destroy(),
342 couchdb_io.get_my_host_unique_id(create=True)[0],342 couchdb_io.get_my_host_unique_id(create=True)[0],
343 local_files.get_oauth_tokens())343 local_files.get_oauth_tokens())
@@ -407,16 +407,16 @@
407 text = gtk.Label()407 text = gtk.Label()
408 text.set_markup(408 text.set_markup(
409 _("""We're listening for invitations! From another\n""" +409 _("""We're listening for invitations! From another\n""" +
410 """machine on this local network, run this\n""" + 410 """machine on this local network, run this\n""" +
411 """same tool and find the machine called\n""" + 411 """same tool and find the machine called\n""" +
412 """<span font-size="xx-large" weight="bold"><tt>""" + 412 """<span font-size="xx-large" weight="bold"><tt>""" +
413 """%s-%s-%d</tt></span> .""") % 413 """%s-%s-%d</tt></span> .""") %
414 (cgi.escape(hostid), cgi.escape(userid), 414 (cgi.escape(hostid), cgi.escape(userid),
415 listen_port))415 listen_port))
416 text.set_justify(gtk.JUSTIFY_CENTER)416 text.set_justify(gtk.JUSTIFY_CENTER)
417 top_vbox.pack_start(text, False, False, 10)417 top_vbox.pack_start(text, False, False, 10)
418 text.show()418 text.show()
419 419
420 self.update_counter_view()420 self.update_counter_view()
421 self.window.show_all()421 self.window.show_all()
422422
@@ -444,7 +444,7 @@
444444
445 self.update_counter_view()445 self.update_counter_view()
446 return True446 return True
447 447
448448
449class PickOrListen:449class PickOrListen:
450 """Main top-level window that represents the life of the application."""450 """Main top-level window that represents the life of the application."""
@@ -459,7 +459,7 @@
459459
460 def create_pick_pane(self, container):460 def create_pick_pane(self, container):
461 """Set up the pane that contains what's necessary to choose an461 """Set up the pane that contains what's necessary to choose an
462 already-listening tool instance. This sets up a "Bob" in the 462 already-listening tool instance. This sets up a "Bob" in the
463 module's story."""463 module's story."""
464464
465 # positions: host id, descr, host, port, cloud_name465 # positions: host id, descr, host, port, cloud_name
@@ -473,15 +473,15 @@
473 srv = getattr(services, srv_name)473 srv = getattr(services, srv_name)
474 try:474 try:
475 if srv.is_active():475 if srv.is_active():
476 all_paired_cloud_servers = [x.key for x in 476 all_paired_cloud_servers = [x.key for x in
477 couchdb_io.get_pairings()]477 couchdb_io.get_pairings()]
478 if not srv_name in all_paired_cloud_servers:478 if not srv_name in all_paired_cloud_servers:
479 self.listening_hosts.append(None, 479 self.listening_hosts.append(None,
480 [srv.name, srv.description, "", 0, srv_name])480 [srv.name, srv.description, "", 0, srv_name])
481 except Exception, e:481 except Exception, e:
482 self.logging.exception("service %r has errors", srv_name)482 self.logging.exception("service %r has errors", srv_name)
483483
484 self.inviting = None # pylint: disable-msg=W0201484 self.inviting = None # pylint: disable=W0201
485485
486 hostname_col = gtk.TreeViewColumn(_("hostname"))486 hostname_col = gtk.TreeViewColumn(_("hostname"))
487 hostid_col = gtk.TreeViewColumn(_("service name"))487 hostid_col = gtk.TreeViewColumn(_("service name"))
@@ -500,7 +500,7 @@
500 tv.show()500 tv.show()
501501
502 def clicked(selection):502 def clicked(selection):
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
504 about inviting it to pair with us."""504 about inviting it to pair with us."""
505505
506 model, iter = selection.get_selected()506 model, iter = selection.get_selected()
@@ -511,7 +511,7 @@
511 hostname = model.get_value(iter, 2)511 hostname = model.get_value(iter, 2)
512 port = model.get_value(iter, 3)512 port = model.get_value(iter, 3)
513 service_name = model.get_value(iter, 4)513 service_name = model.get_value(iter, 4)
514 514
515 if service_name:515 if service_name:
516 # Pairing with a cloud service, which doesn't do key exchange516 # Pairing with a cloud service, which doesn't do key exchange
517 pair_with_cloud_service(service_name, self.window)517 pair_with_cloud_service(service_name, self.window)
@@ -519,7 +519,7 @@
519 self.listening_hosts.remove(iter)519 self.listening_hosts.remove(iter)
520 # add to already-paired list520 # add to already-paired list
521 srv = getattr(services, service_name)521 srv = getattr(services, service_name)
522 self.already_paired_hosts.append(None, 522 self.already_paired_hosts.append(None,
523 [service, _("paired just now"), hostname, port, service_name, None])523 [service, _("paired just now"), hostname, port, service_name, None])
524 return524 return
525525
@@ -548,7 +548,7 @@
548 self.listening_hosts.append(None, [name, description, host, port, None])548 self.listening_hosts.append(None, [name, description, host, port, None])
549549
550 def remove_service_from_list(name):550 def remove_service_from_list(name):
551 """When a zeroconf service disappears, this finds it in the 551 """When a zeroconf service disappears, this finds it in the
552 listing and removes it as an option for picking."""552 listing and removes it as an option for picking."""
553553
554 it = self.listening_hosts.get_iter_first()554 it = self.listening_hosts.get_iter_first()
@@ -604,7 +604,7 @@
604 already_paired_record.value.get("ctime",604 already_paired_record.value.get("ctime",
605 _("unknown date"))605 _("unknown date"))
606 try:606 try:
607 self.already_paired_hosts.append(None, 607 self.already_paired_hosts.append(None,
608 [srv.name, nice_description, "", 0, srv_name, pid])608 [srv.name, nice_description, "", 0, srv_name, pid])
609 except Exception, e:609 except Exception, e:
610 logging.error("Service %s had an error", srv_name, e)610 logging.error("Service %s had an error", srv_name, e)
@@ -613,7 +613,7 @@
613 nice_description = _("paired ") + \613 nice_description = _("paired ") + \
614 already_paired_record.value.get("ctime",614 already_paired_record.value.get("ctime",
615 _("unknown date"))615 _("unknown date"))
616 self.already_paired_hosts.append(None, 616 self.already_paired_hosts.append(None,
617 [hostname, nice_description, None, 0, None, pid])617 [hostname, nice_description, None, 0, None, pid])
618 else:618 else:
619 logging.error("unknown pairing record %s",619 logging.error("unknown pairing record %s",
@@ -635,7 +635,7 @@
635 tv.show()635 tv.show()
636636
637 def clicked(selection):637 def clicked(selection):
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
639 about inviting it to pair with us."""639 about inviting it to pair with us."""
640640
641 model, iter = selection.get_selected()641 model, iter = selection.get_selected()
@@ -646,17 +646,17 @@
646 port = model.get_value(iter, 3)646 port = model.get_value(iter, 3)
647 service_name = model.get_value(iter, 4)647 service_name = model.get_value(iter, 4)
648 pid = model.get_value(iter, 5)648 pid = model.get_value(iter, 5)
649 649
650 if service_name:650 if service_name:
651 # delete record651 # delete record
652 for record in couchdb_io.get_pairings():652 for record in couchdb_io.get_pairings():
653 couchdb_io.remove_pairing(record.id, True)653 couchdb_io.remove_pairing(record.id, True)
654 654
655 # remove from already-paired list655 # remove from already-paired list
656 self.already_paired_hosts.remove(iter)656 self.already_paired_hosts.remove(iter)
657 # add to listening list657 # add to listening list
658 srv = getattr(services, service_name)658 srv = getattr(services, service_name)
659 self.listening_hosts.append(None, [service, srv.description, 659 self.listening_hosts.append(None, [service, srv.description,
660 hostname, port, service_name])660 hostname, port, service_name])
661 return661 return
662662
@@ -665,7 +665,7 @@
665 if record.value["pairing_identifier"] == pid:665 if record.value["pairing_identifier"] == pid:
666 couchdb_io.remove_pairing(record.id, False)666 couchdb_io.remove_pairing(record.id, False)
667 break667 break
668 668
669 # remove from already-paired list669 # remove from already-paired list
670 self.already_paired_hosts.remove(iter)670 self.already_paired_hosts.remove(iter)
671 # do not add to listening list -- if it's listening then zeroconf671 # do not add to listening list -- if it's listening then zeroconf
@@ -699,7 +699,7 @@
699699
700 def create_single_listen_pane(self, container):700 def create_single_listen_pane(self, container):
701 """This sets up an "Alice" from the module's story.701 """This sets up an "Alice" from the module's story.
702 702
703 This assumes we're pairing a single, known local CouchDB instance,703 This assumes we're pairing a single, known local CouchDB instance,
704 instead of generic instances that we'd need more information to talk704 instead of generic instances that we'd need more information to talk
705 about. Instead of using this function, one might use another that705 about. Instead of using this function, one might use another that
@@ -749,7 +749,7 @@
749 #some_row_in_list.connect("clicked", self.listen, target_db_info)749 #some_row_in_list.connect("clicked", self.listen, target_db_info)
750750
751 def __init__(self):751 def __init__(self):
752 752
753753
754 self.logging = logging.getLogger(self.__class__.__name__)754 self.logging = logging.getLogger(self.__class__.__name__)
755755
@@ -802,8 +802,8 @@
802 fail_note.run()802 fail_note.run()
803 fail_note.destroy()803 fail_note.destroy()
804 return804 return
805 805
806 success_note = gtk.Dialog(title=_("Paired with %(hostname)s") % locals(), 806 success_note = gtk.Dialog(title=_("Paired with %(hostname)s") % locals(),
807 parent=parent,807 parent=parent,
808 flags=gtk.DIALOG_DESTROY_WITH_PARENT,808 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
809 buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,))809 buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,))
@@ -838,7 +838,7 @@
838 fail_note.run()838 fail_note.run()
839 fail_note.destroy()839 fail_note.destroy()
840 return840 return
841 841
842 success_note = gtk.MessageDialog(842 success_note = gtk.MessageDialog(
843 parent=parent,843 parent=parent,
844 flags=gtk.DIALOG_DESTROY_WITH_PARENT,844 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
845845
=== modified file 'data/source_desktopcouch.py'
--- data/source_desktopcouch.py 2010-02-03 19:39:17 +0000
+++ data/source_desktopcouch.py 2010-11-12 14:40:00 +0000
@@ -14,7 +14,7 @@
14# You should have received a copy of the GNU General Public License along14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.15# with this program. If not, see <http://www.gnu.org/licenses/>.
16"""Stub for Apport"""16"""Stub for Apport"""
17# pylint: disable-msg=F0401,C010317# pylint: disable=F0401,C0103
18# shut up about apport. We know. We aren't going to backport it for pqm18# shut up about apport. We know. We aren't going to backport it for pqm
19import apport19import apport
20from apport.hookutils import attach_file_if_exists, packaging20from apport.hookutils import attach_file_if_exists, packaging
2121
=== modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py'
--- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-09 17:37:32 +0000
+++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-12 14:40:00 +0000
@@ -55,7 +55,7 @@
55 protocol = "https" if has_ssl else "http"55 protocol = "https" if has_ssl else "http"
56 if auth_pair:56 if auth_pair:
57 auth = (":".join(57 auth = (":".join(
58 map(urllib.quote, auth_pair)) + "@") # pylint: disable-msg=W014158 map(urllib.quote, auth_pair)) + "@") # pylint: disable=W0141
59 else:59 else:
60 auth = ""60 auth = ""
61 if (protocol, port) in (("http", 80), ("https", 443)):61 if (protocol, port) in (("http", 80), ("https", 443)):
@@ -108,7 +108,7 @@
108 push_to_server=True, server=hostname, ctx=ctx)108 push_to_server=True, server=hostname, ctx=ctx)
109109
110110
111def get_static_paired_hosts(uri=None, # pylint: disable-msg=R0914111def get_static_paired_hosts(uri=None, # pylint: disable=R0914
112 ctx=local_files.DEFAULT_CONTEXT, port=None):112 ctx=local_files.DEFAULT_CONTEXT, port=None):
113 """Retreive a list of static hosts' information in the form of113 """Retreive a list of static hosts' information in the form of
114 (ID, service name, to_push, to_pull) ."""114 (ID, service name, to_push, to_pull) ."""
@@ -251,7 +251,7 @@
251 oauth_tokens=oauth_tokens)251 oauth_tokens=oauth_tokens)
252252
253253
254def replicate(source_database, target_database, # pylint: disable-msg=R0914254def replicate(source_database, target_database, # pylint: disable=R0914
255 target_host=None, target_port=None, source_host=None,255 target_host=None, target_port=None, source_host=None,
256 source_port=None, source_ssl=False, target_ssl=False,256 source_port=None, source_ssl=False, target_ssl=False,
257 source_oauth=None, target_oauth=None, local_uri=None):257 source_oauth=None, target_oauth=None, local_uri=None):
@@ -268,7 +268,7 @@
268 else:268 else:
269 server.CouchDatabase(target_database, create=True, uri=local_uri)269 server.CouchDatabase(target_database, create=True, uri=local_uri)
270 logging.debug("db exists, and we're ready to replicate")270 logging.debug("db exists, and we're ready to replicate")
271 except: # pylint: disable-msg=W0702271 except: # pylint: disable=W0702
272 logging.exception(272 logging.exception(
273 "can't create/verify %r %s:%d oauth=%s", target_database,273 "can't create/verify %r %s:%d oauth=%s", target_database,
274 target_host, target_port, obsfuscate(target_oauth))274 target_host, target_port, obsfuscate(target_oauth))
@@ -309,7 +309,7 @@
309 content=record)309 content=record)
310 logging.debug(310 logging.debug(
311 "replicate result: %r %r", obsfuscate(resp), obsfuscate(data))311 "replicate result: %r %r", obsfuscate(resp), obsfuscate(data))
312 except: # pylint: disable-msg=W0702312 except: # pylint: disable=W0702
313 logging.exception("can't replicate %r %r <== %r", source_database,313 logging.exception("can't replicate %r %r <== %r", source_database,
314 local_uri, obsfuscate(record))314 local_uri, obsfuscate(record))
315315
316316
=== modified file 'desktopcouch/pair/tests/test_couchdb_io.py'
--- desktopcouch/pair/tests/test_couchdb_io.py 2010-11-09 17:37:32 +0000
+++ desktopcouch/pair/tests/test_couchdb_io.py 2010-11-12 14:40:00 +0000
@@ -67,8 +67,8 @@
6767
68 def tearDown(self):68 def tearDown(self):
69 """tear down each test"""69 """tear down each test"""
70 del self.mgt_database._server['management'] # pylint: disable-msg=W021270 del self.mgt_database._server['management'] # pylint: disable=W0212
71 del self.mgt_database._server['foo'] # pylint: disable-msg=W021271 del self.mgt_database._server['foo'] # pylint: disable=W0212
7272
73 def test_obsfuscation(self):73 def test_obsfuscation(self):
74 """Test the obfuscation of sensitive data."""74 """Test the obfuscation of sensitive data."""
@@ -98,7 +98,7 @@
98 self.assertEqual(couchdb_io.obsfuscate({1: {}}), {1: {}})98 self.assertEqual(couchdb_io.obsfuscate({1: {}}), {1: {}})
99 self.assertEqual(couchdb_io.obsfuscate({1: 1}), {1: 1})99 self.assertEqual(couchdb_io.obsfuscate({1: 1}), {1: 1})
100100
101 def test_put_static_paired_service(self): # pylint: disable-msg=R0201101 def test_put_static_paired_service(self): # pylint: disable=R0201
102 """Test putting a static paired service."""102 """Test putting a static paired service."""
103 service_name = "dummyfortest"103 service_name = "dummyfortest"
104 oauth_data = {104 oauth_data = {
105105
=== modified file 'desktopcouch/records/doc/field_registry.txt'
--- desktopcouch/records/doc/field_registry.txt 2009-10-05 13:36:43 +0000
+++ desktopcouch/records/doc/field_registry.txt 2010-11-12 14:40:00 +0000
@@ -9,7 +9,16 @@
9>>> from desktopcouch.records.record import Record9>>> from desktopcouch.records.record import Record
1010
11Say we have a very simple audiofile record type that defines 'artist'11Say we have a very simple audiofile record type that defines 'artist'
12and 'title' string fields. Now also say we have an application that12and 'title' string fields.
13
14>>> class AudioFile(Record):
15... """An audio file desktopcouch record."""
16...
17... def __init__(self, data=None):
18... super(AudioFile, self).__init__(
19... record_type='http://example.org/record_types/audio_file', data=data)
20
21Now also say we have an application that
13wants to interact with records of this type called 'My Awesome Music22wants to interact with records of this type called 'My Awesome Music
14Player' or MAMP. The developers of MAMP use a data structure that has23Player' or MAMP. The developers of MAMP use a data structure that has
15the same fields, but uses slightly different names for them:24the same fields, but uses slightly different names for them:
@@ -23,7 +32,8 @@
2332
24and instantiate a Transformer object:33and instantiate a Transformer object:
2534
26>>> my_transformer = Transformer('My Awesome Music Player', my_registry)35>>> my_transformer = Transformer(
36... 'My Awesome Music Player', my_registry, record_class=AudioFile)
2737
28If MAMP has the following song object (a plain dictionary):38If MAMP has the following song object (a plain dictionary):
2939
@@ -36,8 +46,7 @@
36object:46object:
3747
38>>> AUDIO_FILE_RECORD_TYPE = 'http://example.org/record_types/audio_file'48>>> AUDIO_FILE_RECORD_TYPE = 'http://example.org/record_types/audio_file'
39>>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE)49>>> new_record = my_transformer.from_app(my_song)
40>>> my_transformer.from_app(my_song, new_record)
4150
42Now we can look at the underlying data:51Now we can look at the underlying data:
4352
@@ -63,8 +72,7 @@
63this data. Let's see what happens if we run the transformation with72this data. Let's see what happens if we run the transformation with
64this field present, but undefined in the field registry:73this field present, but undefined in the field registry:
6574
66>>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE)75>>> new_record = my_transformer.from_app(my_song)
67>>> my_transformer.from_app(my_song, new_record)
6876
69>>> new_record._data #doctest: +NORMALIZE_WHITESPACE77>>> new_record._data #doctest: +NORMALIZE_WHITESPACE
70{'record_type': 'http://example.org/record_types/audio_file',78{'record_type': 'http://example.org/record_types/audio_file',
@@ -111,9 +119,9 @@
111... default_values={'description': 'subject'}),119... default_values={'description': 'subject'}),
112... }120... }
113121
114>>> my_transformer = Transformer('My Awesome Music Player', my_registry)122>>> my_transformer = Transformer(
115>>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE)123... 'My Awesome Music Player', my_registry, record_class=AudioFile)
116>>> my_transformer.from_app(my_song, new_record)124>>> new_record = my_transformer.from_app(my_song)
117125
118Since _data will now contain lots of uuids to keep references intact,126Since _data will now contain lots of uuids to keep references intact,
119it's less readable, and a less clear example, so I'll show you what127it's less readable, and a less clear example, so I'll show you what
@@ -141,8 +149,7 @@
141149
142and now look at transforming in the other direction:150and now look at transforming in the other direction:
143151
144>>> new_song = {}152>>> new_song = my_transformer.to_app(new_record)
145>>> my_transformer.to_app(new_record, new_song)
146>>> new_song #doctest: +NORMALIZE_WHITESPACE153>>> new_song #doctest: +NORMALIZE_WHITESPACE
147{'tag_title': 'shaking it',154{'tag_title': 'shaking it',
148 'tag_subject': 'talking',155 'tag_subject': 'talking',
@@ -193,7 +200,8 @@
193... 'songtitle': SimpleFieldMapping('title'),200... 'songtitle': SimpleFieldMapping('title'),
194... 'stars': StarIntMapping('score'),201... 'stars': StarIntMapping('score'),
195... }202... }
196>>> my_transformer = Transformer('My Awesome Music Player', my_registry)203>>> my_transformer = Transformer(
204... 'My Awesome Music Player', my_registry, record_class=AudioFile)
197205
198Create a song with a rating:206Create a song with a rating:
199207
@@ -204,10 +212,7 @@
204... 'number_of_times_played_in_mamp': 23212... 'number_of_times_played_in_mamp': 23
205... }213... }
206214
207>>> new_record = Record(record_type=AUDIO_FILE_RECORD_TYPE)215>>> new_record = my_transformer.from_app(my_song)
208>>> my_transformer.from_app(my_song, new_record)
209>>> new_record['score']216>>> new_record['score']
210100217100
211218
212And, I don't know if you've ever heard the song in question, but that
213is in fact correct! ;)
214219
=== modified file 'desktopcouch/records/field_registry.py'
--- desktopcouch/records/field_registry.py 2010-11-02 23:10:39 +0000
+++ desktopcouch/records/field_registry.py 2010-11-12 14:40:00 +0000
@@ -21,7 +21,7 @@
2121
22import copy22import copy
2323
24from desktopcouch.records.record import MergeableList 24from desktopcouch.records.record import MergeableList
25ANNOTATION_NAMESPACE = 'application_annotations'25ANNOTATION_NAMESPACE = 'application_annotations'
2626
2727
@@ -30,12 +30,15 @@
30 data.30 data.
31 """31 """
3232
33 def __init__(self, app_name, field_registry):33 def __init__(self, app_name, field_registry, record_class=None):
34 self.app_name = app_name34 self.app_name = app_name
35 self.field_registry = field_registry35 self.field_registry = field_registry
36 self.record_class = record_class
3637
37 def from_app(self, data, record):38 def from_app(self, data, record=None):
38 """Transform from application data to record data."""39 """Transform from application data to record data."""
40 if record is None:
41 record = self.record_class()
39 for key, val in data.items():42 for key, val in data.items():
40 if key in self.field_registry:43 if key in self.field_registry:
41 self.field_registry[key].setValue(record, val)44 self.field_registry[key].setValue(record, val)
@@ -43,15 +46,19 @@
43 record.application_annotations.setdefault(46 record.application_annotations.setdefault(
44 self.app_name, {}).setdefault(47 self.app_name, {}).setdefault(
45 'application_fields', {})[key] = val48 'application_fields', {})[key] = val
49 return record
4650
47 def to_app(self, record, data):51 def to_app(self, record, data=None):
48 """Transform from record data to application data."""52 """Transform from record data to application data."""
53 if data is None:
54 data = {}
49 annotations = record.application_annotations.get(self.app_name, {}).get(55 annotations = record.application_annotations.get(self.app_name, {}).get(
50 'application_fields', {})56 'application_fields', {})
51 for key, value in annotations.items():57 for key, value in annotations.items():
52 data[key] = value58 data[key] = value
53 for key in self.field_registry:59 for key in self.field_registry:
54 data[key] = self.field_registry[key].getValue(record)60 data[key] = self.field_registry[key].getValue(record)
61 return data
5562
5663
57class SimpleFieldMapping(object):64class SimpleFieldMapping(object):
@@ -81,8 +88,8 @@
81class MergeableListFieldMapping(object):88class MergeableListFieldMapping(object):
82 """Mapping between MergeableLists and application fields."""89 """Mapping between MergeableLists and application fields."""
8390
84 def __init__(91 def __init__(self, app_name, uuid_field, root_list, field_name,
85 self, app_name, uuid_field, root_list, field_name, default_values=None):92 default_values=None):
86 """initialize the default values"""93 """initialize the default values"""
87 self._app_name = app_name94 self._app_name = app_name
88 self._uuid_field = uuid_field95 self._uuid_field = uuid_field
@@ -119,8 +126,10 @@
119 uuid_key = self._uuidLookup(record)126 uuid_key = self._uuidLookup(record)
120 if not uuid_key:127 if not uuid_key:
121 return128 return
129 # pylint: disable=W0212
122 if self._field_name in root_list._data.get(uuid_key, []):130 if self._field_name in root_list._data.get(uuid_key, []):
123 del root_list._data[uuid_key][self._field_name]131 del root_list._data[uuid_key][self._field_name]
132 # pylint: enable=W0212
124133
125 def setValue(self, record, value):134 def setValue(self, record, value):
126 """set the value for the registered field"""135 """set the value for the registered field"""
@@ -155,5 +164,3 @@
155 application_annotations[self._uuid_field] = uuid_key164 application_annotations[self._uuid_field] = uuid_key
156 return165 return
157 record_dict[self._field_name] = value166 record_dict[self._field_name] = value
158
159
160167
=== modified file 'desktopcouch/records/server.py'
--- desktopcouch/records/server.py 2010-11-04 11:01:31 +0000
+++ desktopcouch/records/server.py 2010-11-12 14:40:00 +0000
@@ -18,10 +18,11 @@
18# Mark G. Saye <mark.saye@canonical.com>18# Mark G. Saye <mark.saye@canonical.com>
19# Stuart Langridge <stuart.langridge@canonical.com>19# Stuart Langridge <stuart.langridge@canonical.com>
20# Chad Miller <chad.miller@canonical.com>20# Chad Miller <chad.miller@canonical.com>
21 21
22"""The Desktop Couch Records API."""22"""The Desktop Couch Records API."""
2323
24import copy, uuid24import copy
25import uuid
2526
26from couchdb import Server27from couchdb import Server
27from couchdb.client import Resource28from couchdb.client import Resource
@@ -32,9 +33,10 @@
3233
33DCTRASH = 'dctrash'34DCTRASH = 'dctrash'
3435
36
35class OAuthCapableServer(Server):37class OAuthCapableServer(Server):
36 """Subclass Server to provide oauth magic"""38 """Subclass Server to provide oauth magic"""
37 # pylint: disable-msg=W023139 # pylint: disable=W0231
38 # __init__ method from base class is not called40 # __init__ method from base class is not called
39 def __init__(self, uri, oauth_tokens=None, ctx=None):41 def __init__(self, uri, oauth_tokens=None, ctx=None):
40 """Subclass of couchdb.client.Server which creates a custom42 """Subclass of couchdb.client.Server which creates a custom
@@ -47,17 +49,17 @@
47 if oauth_tokens is None:49 if oauth_tokens is None:
48 oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx)50 oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx)
49 (consumer_key, consumer_secret, token, token_secret) = (51 (consumer_key, consumer_secret, token, token_secret) = (
50 oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], 52 oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"],
51 oauth_tokens["token"], oauth_tokens["token_secret"])53 oauth_tokens["token"], oauth_tokens["token_secret"])
52 http.add_oauth_tokens(54 http.add_oauth_tokens(
53 consumer_key, consumer_secret, token, token_secret)55 consumer_key, consumer_secret, token, token_secret)
54 self.resource = Resource(http, uri)56 self.resource = Resource(http, uri)
55 # pylint: enable-msg=W023157 # pylint: enable=W0231
5658
57 59
58class CouchDatabase(server_base.CouchDatabaseBase):60class CouchDatabase(server_base.CouchDatabaseBase):
59 """An small records specific abstraction over a couch db database."""61 """An small records specific abstraction over a couch db database."""
60 62
61 def __init__(self, database, uri=None, record_factory=None, create=False,63 def __init__(self, database, uri=None, record_factory=None, create=False,
62 server_class=OAuthCapableServer, oauth_tokens=None,64 server_class=OAuthCapableServer, oauth_tokens=None,
63 ctx=desktopcouch.local_files.DEFAULT_CONTEXT):65 ctx=desktopcouch.local_files.DEFAULT_CONTEXT):
@@ -67,7 +69,7 @@
67 database, uri, record_factory=record_factory, create=create,69 database, uri, record_factory=record_factory, create=create,
68 server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx)70 server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx)
6971
70 # pylint: disable-msg=W021272 # pylint: disable=W0212
71 #Access to a protected member73 #Access to a protected member
72 def delete_record(self, record_id):74 def delete_record(self, record_id):
73 """Delete record with given id"""75 """Delete record with given id"""
@@ -79,9 +81,9 @@
79 create=True,81 create=True,
80 **self._server_class_extras)82 **self._server_class_extras)
81 new_record.record_id = str(uuid.uuid4())83 new_record.record_id = str(uuid.uuid4())
82 del new_record._data['_rev'] 84 del new_record._data['_rev']
83 try:85 try:
84 del new_record._data['_attachments'] 86 del new_record._data['_attachments']
85 except KeyError:87 except KeyError:
86 pass88 pass
87 new_record.application_annotations['desktopcouch'] = {89 new_record.application_annotations['desktopcouch'] = {
@@ -90,9 +92,9 @@
90 'original_id': record_id}}92 'original_id': record_id}}
91 del self.db[record_id]93 del self.db[record_id]
92 return dctrash.put_record(new_record)94 return dctrash.put_record(new_record)
93 # pylint: enable-msg=W021295 # pylint: enable=W0212
9496
95 # pylint: disable-msg=W022197 # pylint: disable=W0221
96 # Arguments number differs from overridden method98 # Arguments number differs from overridden method
97 def _reconnect(self):99 def _reconnect(self):
98 if not self.server_uri:100 if not self.server_uri:
@@ -101,4 +103,4 @@
101 else:103 else:
102 uri = self.server_uri104 uri = self.server_uri
103 super(CouchDatabase, self)._reconnect(uri=uri)105 super(CouchDatabase, self)._reconnect(uri=uri)
104 # pylint: enable-msg=W0221106 # pylint: enable=W0221
105107
=== modified file 'desktopcouch/records/server_base.py'
--- desktopcouch/records/server_base.py 2010-11-03 22:57:06 +0000
+++ desktopcouch/records/server_base.py 2010-11-12 14:40:00 +0000
@@ -22,31 +22,37 @@
2222
23"""The Desktop Couch Records API."""23"""The Desktop Couch Records API."""
2424
25import httplib2, urlparse, cgi, copy, warnings25import cgi
26import copy
27import httplib2
28import urlparse
29import warnings
30
26from time import time31from time import time
2732
28# please keep desktopcouch python 2.5 compatible for now33# please keep desktopcouch python 2.5 compatible for now
2934
30# pylint can't deal with failing imports even when they're handled35# pylint can't deal with failing imports even when they're handled
31# pylint: disable-msg=F040136# pylint: disable=F0401
32try:37try:
33 # Python 2.538 # Python 2.5
34 import simplejson as json39 import simplejson as json
35except ImportError:40except ImportError:
36 # Python 2.6+41 # Python 2.6+
37 import json42 import json
38# pylint: enable-msg=F040143# pylint: enable=F0401
3944
40from oauth import oauth45from oauth import oauth
4146
42from couchdb import Server47from couchdb import Server
43from couchdb.client import ResourceNotFound, ResourceConflict, uri as couchdburi48from couchdb.client import ResourceNotFound, ResourceConflict, uri as couchdburi
44from couchdb.design import ViewDefinition49from couchdb.design import ViewDefinition
45from record import Record50from desktopcouch.records.record import Record
46import logging51import logging
4752
48DEFAULT_DESIGN_DOCUMENT = None # each view in its own eponymous design doc.53DEFAULT_DESIGN_DOCUMENT = None # each view in its own eponymous design doc.
4954
55
50def get_changes(self, changes_since):56def get_changes(self, changes_since):
51 """This method is used to monkey patch the database to provide a57 """This method is used to monkey patch the database to provide a
52 get_changes method"""58 get_changes method"""
@@ -58,6 +64,7 @@
58 resp, data = self.resource.http.request(uri, "GET", "", {})64 resp, data = self.resource.http.request(uri, "GET", "", {})
59 return resp, data65 return resp, data
6066
67
61def transform_to_records(view_results):68def transform_to_records(view_results):
62 """Transform view resulst into Record objects."""69 """Transform view resulst into Record objects."""
63 for result in view_results:70 for result in view_results:
@@ -67,10 +74,10 @@
67class FieldsConflict(Exception):74class FieldsConflict(Exception):
68 """Raised in case of an unrecoverable couchdb conflict."""75 """Raised in case of an unrecoverable couchdb conflict."""
6976
70 #pylint: disable-msg=W023177 #pylint: disable=W0231
71 def __init__(self, conflicts):78 def __init__(self, conflicts):
72 self.conflicts = conflicts79 self.conflicts = conflicts
73 #pylint: enable-msg=W023180 #pylint: enable=W0231
7481
75 def __str__(self):82 def __str__(self):
76 return "<CouchDB Conflict Error: %s>" % self.conflicts83 return "<CouchDB Conflict Error: %s>" % self.conflicts
@@ -97,6 +104,7 @@
97 httplib2.Authentication.__init__(self, None, host, request_uri,104 httplib2.Authentication.__init__(self, None, host, request_uri,
98 headers, response, content, http)105 headers, response, content, http)
99106
107 # pylint: disable=R0914
100 def request(self, method, request_uri, headers, content):108 def request(self, method, request_uri, headers, content):
101 """Modify the request headers to add the appropriate109 """Modify the request headers to add the appropriate
102 Authorization header."""110 Authorization header."""
@@ -106,35 +114,43 @@
106 self.oauth_data['token_secret'])114 self.oauth_data['token_secret'])
107 sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1115 sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1
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)
109 schema, netloc, path, params, query, fragment = \117 # pylint: disable=W0612
110 urlparse.urlparse(full_http_url)118 # better than using dummy variables, in case we want to use
119 # any of them later on
120 schema, netloc, path, params, query, fragment = urlparse.urlparse(
121 full_http_url)
122 # pylint: enable=W0612
111 querystr_as_dict = dict(cgi.parse_qsl(query))123 querystr_as_dict = dict(cgi.parse_qsl(query))
112 req = oauth.OAuthRequest.from_consumer_and_token(124 req = oauth.OAuthRequest.from_consumer_and_token(
113 consumer,125 consumer,
114 access_token,126 access_token,
115 http_method = method,127 http_method=method,
116 http_url = full_http_url,128 http_url=full_http_url,
117 parameters = querystr_as_dict129 parameters=querystr_as_dict)
118 )
119 req.sign_request(sig_method(), consumer, access_token)130 req.sign_request(sig_method(), consumer, access_token)
131 # pylint: disable=W0212
120 headers.update(httplib2._normalize_headers(req.to_header()))132 headers.update(httplib2._normalize_headers(req.to_header()))
133 # pylint: enable=W0212
134 # pylint: enable=R0914
121135
122136
123class OAuthCapableHttp(httplib2.Http):137class OAuthCapableHttp(httplib2.Http):
124 """Subclass of httplib2.Http which specifically uses our OAuth138 """Subclass of httplib2.Http which specifically uses our OAuth
125 Authentication subclass (because httplib2 doesn't know about it)"""139 Authentication subclass (because httplib2 doesn't know about it)"""
126 def __init__(self, scheme="http", cache=None, timeout=None, proxy_info=None):140 def __init__(self, scheme="http", cache=None, timeout=None,
141 proxy_info=None):
127 self.__scheme = scheme142 self.__scheme = scheme
143 self.oauth_data = None
128 super(OAuthCapableHttp, self).__init__(cache, timeout, proxy_info)144 super(OAuthCapableHttp, self).__init__(cache, timeout, proxy_info)
129145
130 def add_oauth_tokens(self, consumer_key, consumer_secret,146 def add_oauth_tokens(self, consumer_key, consumer_secret,
131 token, token_secret):147 token, token_secret):
148 """Add the OAuth tokens to the Http object."""
132 self.oauth_data = {149 self.oauth_data = {
133 "consumer_key": consumer_key,150 "consumer_key": consumer_key,
134 "consumer_secret": consumer_secret,151 "consumer_secret": consumer_secret,
135 "token": token,152 "token": token,
136 "token_secret": token_secret153 "token_secret": token_secret}
137 }
138154
139 def _auth_from_challenge(self, host, request_uri, headers, response,155 def _auth_from_challenge(self, host, request_uri, headers, response,
140 content):156 content):
@@ -144,6 +160,7 @@
144 yield OAuthAuthentication(self.oauth_data, host, request_uri, headers,160 yield OAuthAuthentication(self.oauth_data, host, request_uri, headers,
145 response, content, self, self.__scheme)161 response, content, self, self.__scheme)
146162
163
147def row_is_deleted(row):164def row_is_deleted(row):
148 """Test if a row is marked as deleted. Smart views 'maps' should not165 """Test if a row is marked as deleted. Smart views 'maps' should not
149 return rows that are marked as deleted, so this function is not often166 return rows that are marked as deleted, so this function is not often
@@ -174,10 +191,12 @@
174191
175 @staticmethod192 @staticmethod
176 def _is_reconnection_fail(ex):193 def _is_reconnection_fail(ex):
194 """Check whether this is the bug in httplib."""
177 return isinstance(ex, AttributeError) and \195 return isinstance(ex, AttributeError) and \
178 ex.args == ("'NoneType' object has no attribute 'makefile'",)196 ex.args == ("'NoneType' object has no attribute 'makefile'",)
179197
180 def _reconnect(self, uri=None):198 def _reconnect(self, uri=None):
199 """Reconnect after losing connection."""
181 logging.info("Connecting to %s.",200 logging.info("Connecting to %s.",
182 self.server_uri or "discovered local port")201 self.server_uri or "discovered local port")
183 self._server = self._server_class(uri or self.server_uri,202 self._server = self._server_class(uri or self.server_uri,
@@ -211,8 +230,9 @@
211 """Closure storing the database for lower levels to use when needed.230 """Closure storing the database for lower levels to use when needed.
212 """231 """
213 def getter():232 def getter():
214 return source_db.get_attachment(document_id, attachment_name), \233 """Get the attachment and content type."""
215 content_type234 return source_db.get_attachment(
235 document_id, attachment_name), content_type
216 return getter236 return getter
217237
218 try:238 try:
@@ -240,7 +260,7 @@
240 from uuid import uuid4260 from uuid import uuid4
241 record.record_id = uuid4().hex261 record.record_id = uuid4().hex
242 try:262 try:
243 self.db[record.record_id] = record._data263 self.db[record.record_id] = record._data # pylint: disable=W0212
244 except ResourceConflict:264 except ResourceConflict:
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):
246 old_record = self.db[record.record_id]266 old_record = self.db[record.record_id]
@@ -249,14 +269,16 @@
249 # but we have marked deleted internally. Instead of269 # but we have marked deleted internally. Instead of
250 # complaining, pull up the previous revision ID and270 # complaining, pull up the previous revision ID and
251 # add that to the user's record, and re-send it.271 # add that to the user's record, and re-send it.
272 # pylint: disable=W0212
252 record._set_record_revision(old_record.rev)273 record._set_record_revision(old_record.rev)
253
254 self.db[record.record_id] = record._data274 self.db[record.record_id] = record._data
275 # pylint: enable=W0212
255 else:276 else:
256 raise277 raise
257 else:278 else:
258 raise279 raise
259280
281 # pylint: disable=W0212
260 for attachment_name in getattr(record, "_detached", []):282 for attachment_name in getattr(record, "_detached", []):
261 self.db.delete_attachment(record._data, attachment_name)283 self.db.delete_attachment(record._data, attachment_name)
262284
@@ -266,6 +288,7 @@
266 data,288 data,
267 attachment_name,289 attachment_name,
268 content_type)290 content_type)
291 # pylint: enable=W0212
269292
270 return record.record_id293 return record.record_id
271294
@@ -282,6 +305,7 @@
282 # although with a single record we need to test for the305 # although with a single record we need to test for the
283 # revisison, with a batch we do not, but we have to make sure306 # revisison, with a batch we do not, but we have to make sure
284 # that we did not get an error307 # that we did not get an error
308 # pylint: disable=W0212
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])
286 for current_tuple in batch_put_result:310 for current_tuple in batch_put_result:
287 success, docid, rev_or_exc = current_tuple311 success, docid, rev_or_exc = current_tuple
@@ -289,15 +313,14 @@
289 record = records_hash[docid]313 record = records_hash[docid]
290 # set the new rev314 # set the new rev
291 record._data["_rev"] = rev_or_exc315 record._data["_rev"] = rev_or_exc
292
293 for attachment_name in getattr(record, "_detached", []):316 for attachment_name in getattr(record, "_detached", []):
294 self.db.delete_attachment(record._data, attachment_name)317 self.db.delete_attachment(record._data, attachment_name)
295
296 for attachment_name in record.list_attachments():318 for attachment_name in record.list_attachments():
297 data, content_type = record.attachment_data(attachment_name)319 data, content_type = record.attachment_data(attachment_name)
298 self.db.put_attachment(320 self.db.put_attachment(
299 {"_id":record.record_id, "_rev":record["_rev"]},321 {"_id": record.record_id, "_rev": record["_rev"]},
300 data, attachment_name, content_type)322 data, attachment_name, content_type)
323 # pylint: enable=W0212
301 # all success record have the blobs added we return result of324 # all success record have the blobs added we return result of
302 # update325 # update
303 return batch_put_result326 return batch_put_result
@@ -322,7 +345,7 @@
322 if cached_record is None:345 if cached_record is None:
323 cached_record = self.db[record_id]346 cached_record = self.db[record_id]
324 if isinstance(cached_record, Record):347 if isinstance(cached_record, Record):
325 cached_record = cached_record._data348 cached_record = cached_record._data # pylint: disable=W0212
326 record = copy.deepcopy(cached_record)349 record = copy.deepcopy(cached_record)
327 # Loop until either failure or success has been determined350 # Loop until either failure or success has been determined
328 while True:351 while True:
@@ -404,7 +427,7 @@
404 # No atomic updates. Only read & mutate & write. Le sigh.427 # No atomic updates. Only read & mutate & write. Le sigh.
405 # First, get current contents.428 # First, get current contents.
406 try:429 try:
407 view_container = self.db[doc_id]["views"]430 view_container = self.db[doc_id]["views"]
408 except (KeyError, ResourceNotFound):431 except (KeyError, ResourceNotFound):
409 raise KeyError432 raise KeyError
410433
@@ -414,9 +437,7 @@
414 # Construct a new list of objects representing all views to have.437 # Construct a new list of objects representing all views to have.
415 views = [438 views = [
416 ViewDefinition(design_doc, k, v.get("map"), v.get("reduce"))439 ViewDefinition(design_doc, k, v.get("map"), v.get("reduce"))
417 for k, v440 for k, v in view_container.iteritems()]
418 in view_container.iteritems()
419 ]
420 # Push back a new batch of view. Pray to Eris that this doesn't441 # Push back a new batch of view. Pray to Eris that this doesn't
421 # clobber anything we want.442 # clobber anything we want.
422443
@@ -446,9 +467,8 @@
446 view_id_fmt = "_design/%(design_doc)s/_view/%(view_name)s"467 view_id_fmt = "_design/%(design_doc)s/_view/%(view_name)s"
447 return self.db.view(view_id_fmt % locals(), **params)468 return self.db.view(view_id_fmt % locals(), **params)
448469
449
450 def add_view(self, view_name, map_js, reduce_js,470 def add_view(self, view_name, map_js, reduce_js,
451 design_doc=DEFAULT_DESIGN_DOCUMENT):471 design_doc=DEFAULT_DESIGN_DOCUMENT):
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).
453 Return the document id."""473 Return the document id."""
454 if design_doc is None:474 if design_doc is None:
@@ -476,7 +496,7 @@
476 """Return a list of view names for a given design document. There is496 """Return a list of view names for a given design document. There is
477 no error if the design document does not exist or if there are no views497 no error if the design document does not exist or if there are no views
478 in it."""498 in it."""
479 doc_id = "_design/%(design_doc)s" % locals()499 doc_id = "_design/%s" % design_doc
480 try:500 try:
481 return list(self.db[doc_id]["views"])501 return list(self.db[doc_id]["views"])
482 except (KeyError, ResourceNotFound):502 except (KeyError, ResourceNotFound):
@@ -542,12 +562,14 @@
542 return viewdata[record_type]562 return viewdata[record_type]
543563
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):
565 """Get the results of a view as a list of Record objects."""
545 view_results = self.execute_view(view_name, view_name)566 view_results = self.execute_view(view_name, view_name)
546 if record_type:567 if record_type:
547 view_results = view_results[record_type]568 view_results = view_results[record_type]
548 return [record for record in transform_to_records(view_results)]569 return [record for record in transform_to_records(view_results)]
549570
550 def get_all_records(self, record_type=None):571 def get_all_records(self, record_type=None):
572 """Get all records from the database, optionally by record type."""
551 view_name = "get_records_and_type"573 view_name = "get_records_and_type"
552 try:574 try:
553 return self.get_view_results_as_records(575 return self.get_view_results_as_records(
@@ -568,7 +590,6 @@
568 return self.get_view_results_as_records(590 return self.get_view_results_as_records(
569 view_name, record_type=record_type)591 view_name, record_type=record_type)
570592
571
572 def get_changes(self, niceness=10):593 def get_changes(self, niceness=10):
573 """Get a list of database changes. This is the sister function of594 """Get a list of database changes. This is the sister function of
574 report_changes that returns a list instead of calling a function for595 report_changes that returns a list instead of calling a function for
@@ -610,7 +631,7 @@
610 raise IOError(631 raise IOError(
611 "HTTP response code %s.\n%s" % (resp["status"], data))632 "HTTP response code %s.\n%s" % (resp["status"], data))
612 structure = json.loads(data)633 structure = json.loads(data)
613 for change in structure.get("results"):634 for change in structure.get("results"): # pylint: disable=E1103
614 # kw-args can't have unicode keys635 # kw-args can't have unicode keys
615 change_encoded_keys = dict(636 change_encoded_keys = dict(
616 (k.encode("utf8"), v) for k, v in change.iteritems())637 (k.encode("utf8"), v) for k, v in change.iteritems())
617638
=== modified file 'desktopcouch/records/tests/test_field_registry.py'
--- desktopcouch/records/tests/test_field_registry.py 2010-11-11 19:01:41 +0000
+++ desktopcouch/records/tests/test_field_registry.py 2010-11-12 14:40:00 +0000
@@ -17,7 +17,10 @@
1717
18"""Test cases for field mapping"""18"""Test cases for field mapping"""
1919
20import copy, doctest, os20import copy
21import doctest
22import os
23
21from testtools import TestCase24from testtools import TestCase
2225
23import desktopcouch26import desktopcouch
@@ -34,25 +37,35 @@
34 'not_the_field': 'different value'},37 'not_the_field': 'different value'},
35 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3': {38 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3': {
36 'the field': 'another value'}},39 'the field': 'another value'}},
37 'application_annotations': {'Test App': {'private_application_annotations':{40 'application_annotations': {'Test App': {
38 'test_field': 'e47455fb-da05-481e-a2c7-88f14d5cc163'}}}}41 'private_application_annotations': {
42 'test_field': 'e47455fb-da05-481e-a2c7-88f14d5cc163'}}}}
3943
40APP_RECORD = {44APP_RECORD = {
41 'record_type': 'http://example.com/test',45 'record_type': 'http://example.com/test',
42 'simpleField': 23,46 'simpleField': 23,
43 'strawberryField': 'the value',}47 'strawberryField': 'the value'}
4448
45FIELD_REGISTRY = {49FIELD_REGISTRY = {
46 'simpleField': SimpleFieldMapping('simple_field'),50 'simpleField': SimpleFieldMapping('simple_field'),
47 'strawberryField': MergeableListFieldMapping(51 'strawberryField': MergeableListFieldMapping(
48 'Test App', 'test_field', 'test_fields', 'the_field'),}52 'Test App', 'test_field', 'test_fields', 'the_field')}
53
54
55class TestRecord(Record):
56 """A test record type."""
57
58 def __init__(self, data=None):
59 super(TestRecord, self).__init__(
60 data=data, record_type='http://example.com/test')
4961
5062
51class AppTransformer(Transformer):63class AppTransformer(Transformer):
52 """A test transformer class."""64 """A test transformer class."""
5365
54 def __init__(self):66 def __init__(self):
55 super(AppTransformer, self).__init__('Test App', FIELD_REGISTRY)67 super(AppTransformer, self).__init__(
68 'Test App', FIELD_REGISTRY, record_class=TestRecord)
5669
5770
58class TestFieldMapping(TestCase):71class TestFieldMapping(TestCase):
@@ -83,16 +96,16 @@
8396
84 def test_mergeable_list_field_mapping1(self):97 def test_mergeable_list_field_mapping1(self):
85 """Test the MergeableListFieldMapping object."""98 """Test the MergeableListFieldMapping object."""
86 # pylint: disable-msg=W021299 # pylint: disable=W0212
87 record = Record(self.test_record)100 record = Record(self.test_record)
88 mapping = MergeableListFieldMapping(101 mapping = MergeableListFieldMapping(
89 'Test App', 'test_field', 'test_fields', 'the_field')102 'Test App', 'test_field', 'test_fields', 'the_field')
90 self.assertEqual('the value', mapping.getValue(record))103 self.assertEqual('the value', mapping.getValue(record))
91 del record._data['test_fields'][104 del record._data['test_fields'][ # pylint: disable=W0212
92 'e47455fb-da05-481e-a2c7-88f14d5cc163']105 'e47455fb-da05-481e-a2c7-88f14d5cc163']
93 mapping.deleteValue(record)106 mapping.deleteValue(record)
94 self.assertEqual(None, mapping.getValue(record))107 self.assertEqual(None, mapping.getValue(record))
95 # pylint: enable-msg=W0212108 # pylint: enable=W0212
96109
97 def test_mergeable_list_field_mapping_empty_field(self):110 def test_mergeable_list_field_mapping_empty_field(self):
98 """Test setting empty values in the MergeableListFieldMapping object."""111 """Test setting empty values in the MergeableListFieldMapping object."""
@@ -112,21 +125,44 @@
112 self.transformer = AppTransformer()125 self.transformer = AppTransformer()
113126
114 def test_from_app(self):127 def test_from_app(self):
115 """Test transformation from app to Ubuntu One."""128 """Test transformation from app to desktopcouch."""
116 # pylint: disable-msg=W0212129 record = self.transformer.from_app(APP_RECORD)
130 underlying = record._data # pylint: disable=W0212
131 self.assertEqual(23, record['simple_field'])
132 the_uuid = record.application_annotations['Test App']\
133 ['private_application_annotations']['test_field']
134 self.assertEqual(
135 {'the_field': 'the value'},
136 underlying['test_fields'][the_uuid])
137
138 def test_from_app_with_record(self):
139 """Test transformation from app to desktopcouch when passing in
140 an existing record.
141
142 """
117 record = Record(record_type="http://example.com/test")143 record = Record(record_type="http://example.com/test")
118 self.transformer.from_app(APP_RECORD, record)144 self.transformer.from_app(APP_RECORD, record)
119 underlying = record._data145 underlying = record._data # pylint: disable=W0212
120 self.assertEqual(23, record['simple_field'])146 self.assertEqual(23, record['simple_field'])
121 the_uuid = record.application_annotations['Test App']\147 the_uuid = record.application_annotations['Test App']\
122 ['private_application_annotations']['test_field']148 ['private_application_annotations']['test_field']
123 self.assertEqual(149 self.assertEqual(
124 {'the_field': 'the value'},150 {'the_field': 'the value'},
125 underlying['test_fields'][the_uuid])151 underlying['test_fields'][the_uuid])
126 # pylint: enable-msg=W0212152 # pylint: enable=W0212
127153
128 def test_to_app(self):154 def test_to_app(self):
129 """Test transformation to app from Ubuntu One."""155 """Test transformation to app from desktopcouch."""
156 record = Record(TEST_RECORD)
157 data = self.transformer.to_app(record)
158 self.assertEqual(
159 {'simpleField': 23, 'strawberryField': 'the value'}, data)
160
161 def test_to_app_with_dictionary(self):
162 """Test transformation to app from desktopcouch when passing
163 in an existing dictionary.
164
165 """
130 record = Record(TEST_RECORD)166 record = Record(TEST_RECORD)
131 data = {}167 data = {}
132 self.transformer.to_app(record, data)168 self.transformer.to_app(record, data)
@@ -145,5 +181,4 @@
145 '../desktopcouch/records/doc/field_registry.txt'181 '../desktopcouch/records/doc/field_registry.txt'
146 results = doctest.testfile(field_registry_tests_path,182 results = doctest.testfile(field_registry_tests_path,
147 module_relative=False)183 module_relative=False)
148
149 self.assertEqual(0, results.failed)184 self.assertEqual(0, results.failed)
150185
=== modified file 'desktopcouch/records/tests/test_record.py'
--- desktopcouch/records/tests/test_record.py 2010-11-11 19:01:41 +0000
+++ desktopcouch/records/tests/test_record.py 2010-11-12 14:40:00 +0000
@@ -19,11 +19,12 @@
1919
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."""
2121
22import doctest, os22import doctest
23import os
23from testtools import TestCase24from testtools import TestCase
2425
25# pylint does not like relative imports from containing packages26# pylint does not like relative imports from containing packages
26# pylint: disable-msg=F040127# pylint: disable=F0401
27import desktopcouch28import desktopcouch
28from desktopcouch.records.server import CouchDatabase29from desktopcouch.records.server import CouchDatabase
29from desktopcouch.records.record import (Record, RecordDict, MergeableList,30from desktopcouch.records.record import (Record, RecordDict, MergeableList,
@@ -34,7 +35,7 @@
34class TestRecords(TestCase):35class TestRecords(TestCase):
35 """Test the record functionality"""36 """Test the record functionality"""
3637
37 # pylint: disable-msg=C010338 # pylint: disable=C0103
38 def setUp(self):39 def setUp(self):
39 """Test setup."""40 """Test setup."""
40 super(TestRecords, self).setUp()41 super(TestRecords, self).setUp()
@@ -47,7 +48,7 @@
47 "subfield_uuid": {48 "subfield_uuid": {
48 "e47455fb-da05-481e-a2c7-88f14d5cc163": {49 "e47455fb-da05-481e-a2c7-88f14d5cc163": {
49 "field11": "value11",50 "field11": "value11",
50 "field12": "value12",},51 "field12": "value12"},
51 "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": {52 "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": {
52 "field21": "value21",53 "field21": "value21",
53 "field22": "value22"}},54 "field22": "value22"}},
@@ -56,12 +57,13 @@
56 "record_type": "http://fnord.org/smorgasbord",57 "record_type": "http://fnord.org/smorgasbord",
57 }58 }
58 self.record = Record(self.dict)59 self.record = Record(self.dict)
59 # pylint: enable-msg=C010360 # pylint: enable=C0103
6061
61 def test_revision(self):62 def test_revision(self):
62 """Test document always has a revision field and that the revision63 """Test document always has a revision field and that the revision
63 changes when the document is updated"""64 changes when the document is updated"""
64 self.assertEquals(self.record.record_revision, None)65 self.assertEquals(self.record.record_revision, None)
66
65 def set_rev(rec):67 def set_rev(rec):
66 """Set revision."""68 """Set revision."""
67 rec.record_revision = "1"69 rec.record_revision = "1"
@@ -126,8 +128,7 @@
126 ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'],128 ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'],
127 sorted(self.record.keys()))129 sorted(self.record.keys()))
128 self.assertIn("a", self.record)130 self.assertIn("a", self.record)
129 self.assertTrue(self.record.has_key("a"))131 self.assertNotIn('f', self.record)
130 self.assertFalse(self.record.has_key("f"))
131 self.assertNotIn("_id", self.record) # is internal. play dumb.132 self.assertNotIn("_id", self.record) # is internal. play dumb.
132133
133 def test_application_annotations(self):134 def test_application_annotations(self):
@@ -249,7 +250,7 @@
249 value = [1, 2, 3, 4, 5]250 value = [1, 2, 3, 4, 5]
250 self.record["subfield_uuid"] = value251 self.record["subfield_uuid"] = value
251 self.assertRaises(IndexError, self.record["subfield_uuid"].pop,252 self.assertRaises(IndexError, self.record["subfield_uuid"].pop,
252 len(value)*2)253 len(value) * 2)
253254
254 def test_mergeable_list_pop_last(self):255 def test_mergeable_list_pop_last(self):
255 """Test that exception is raised when poping last item"""256 """Test that exception is raised when poping last item"""
@@ -272,9 +273,9 @@
272273
273 def test_dictionary_access_to_mergeable_list(self):274 def test_dictionary_access_to_mergeable_list(self):
274 """Test that appropriate errors are raised."""275 """Test that appropriate errors are raised."""
275 # pylint: disable-msg=W0212276 # pylint: disable=W0212
276 keys = self.record["subfield_uuid"]._data.keys()277 keys = self.record["subfield_uuid"]._data.keys()
277 # pylint: enable-msg=W0212278 # pylint: enable=W0212
278 self.assertRaises(279 self.assertRaises(
279 TypeError,280 TypeError,
280 self.record["subfield_uuid"].__getitem__, keys[0])281 self.record["subfield_uuid"].__getitem__, keys[0])
@@ -298,9 +299,9 @@
298299
299 def test_uuid_like_keys(self):300 def test_uuid_like_keys(self):
300 """Test that appropriate errors are raised."""301 """Test that appropriate errors are raised."""
301 # pylint: disable-msg=W0212302 # pylint: disable=W0212
302 keys = self.record["subfield_uuid"]._data.keys()303 keys = self.record["subfield_uuid"]._data.keys()
303 # pylint: enable-msg=W0212304 # pylint: enable=W0212
304 self.assertRaises(305 self.assertRaises(
305 IllegalKeyException,306 IllegalKeyException,
306 self.record["subfield"].__setitem__, keys[0], 'stuff')307 self.record["subfield"].__setitem__, keys[0], 'stuff')
@@ -313,7 +314,7 @@
313 def test_run_doctests(self):314 def test_run_doctests(self):
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)"""
315 ctx = test_environment.test_context316 ctx = test_environment.test_context
316 globs = { "db": CouchDatabase('testing', create=True, ctx=ctx) }317 globs = {"db": CouchDatabase('testing', create=True, ctx=ctx)}
317318
318 records_tests_path = os.path.dirname(319 records_tests_path = os.path.dirname(
319 desktopcouch.__file__) + '/records/doc/records.txt'320 desktopcouch.__file__) + '/records/doc/records.txt'
@@ -333,25 +334,24 @@
333 results = doctest.testfile(334 results = doctest.testfile(
334 '../desktopcouch/records/doc/an_example_application.txt',335 '../desktopcouch/records/doc/an_example_application.txt',
335 module_relative=False)336 module_relative=False)
336
337 self.assertEqual(0, results.failed)337 self.assertEqual(0, results.failed)
338338
339 def test_record_id(self):339 def test_record_id(self):
340 """Test all passible way to assign a record id"""340 """Test all passible way to assign a record id"""
341 data = {"_id":"recordid"}341 data = {"_id": "recordid"}
342 record = Record(data, record_type="url")342 record = Record(data, record_type="url")
343 self.assertEqual(data["_id"], record.record_id)343 self.assertEqual(data["_id"], record.record_id)
344 data = {}344 data = {}
345 record_id = "recordid"345 record_id = "recordid"
346 record = Record(data, record_type="url", record_id=record_id)346 record = Record(data, record_type="url", record_id=record_id)
347 self.assertEqual(record_id, record.record_id)347 self.assertEqual(record_id, record.record_id)
348 data = {"_id":"differentid"}348 data = {"_id": "differentid"}
349 self.assertRaises(ValueError,349 self.assertRaises(ValueError,
350 Record, data, record_id=record_id, record_type="url")350 Record, data, record_id=record_id, record_type="url")
351351
352 def test_record_type_version(self):352 def test_record_type_version(self):
353 """Test record type version support"""353 """Test record type version support"""
354 data = {"_id":"recordid"}354 data = {"_id": "recordid"}
355 record1 = Record(data, record_type="url")355 record1 = Record(data, record_type="url")
356 self.assertIs(None, record1.record_type_version)356 self.assertIs(None, record1.record_type_version)
357 record2 = Record(data, record_type="url", record_type_version=1)357 record2 = Record(data, record_type="url", record_type_version=1)
@@ -361,7 +361,7 @@
361class TestRecordFactory(TestCase):361class TestRecordFactory(TestCase):
362 """Test Record/Mergeable List factories."""362 """Test Record/Mergeable List factories."""
363363
364 # pylint: disable-msg=C0103364 # pylint: disable=C0103
365 def setUp(self):365 def setUp(self):
366 """Test setup."""366 """Test setup."""
367 super(TestRecordFactory, self).setUp()367 super(TestRecordFactory, self).setUp()
@@ -370,7 +370,7 @@
370 "b": "B",370 "b": "B",
371 "subfield": {371 "subfield": {
372 "field11s": "value11s",372 "field11s": "value11s",
373 "field12s": "value12s",},373 "field12s": "value12s"},
374 "subfield_uuid":374 "subfield_uuid":
375 [375 [
376 {"field11": "value11",376 {"field11": "value11",
@@ -379,7 +379,7 @@
379 "field22": "value22"}],379 "field22": "value22"}],
380 "record_type": "http://fnord.org/smorgasbord",380 "record_type": "http://fnord.org/smorgasbord",
381 }381 }
382 # pylint: enable-msg=C0103382 # pylint: enable=C0103
383383
384 def test_build(self):384 def test_build(self):
385 """Test RecordDict/MergeableList factory method."""385 """Test RecordDict/MergeableList factory method."""
386386
=== modified file 'desktopcouch/records/tests/test_server.py'
--- desktopcouch/records/tests/test_server.py 2010-11-03 22:57:06 +0000
+++ desktopcouch/records/tests/test_server.py 2010-11-12 14:40:00 +0000
@@ -32,27 +32,23 @@
32from desktopcouch.platform import find_pid32from desktopcouch.platform import find_pid
3333
34# pylint can't deal with failing imports even when they're handled34# pylint can't deal with failing imports even when they're handled
35# pylint: disable-msg=F040135# pylint: disable=F0401
36try:36try:
37 from io import StringIO37 from io import StringIO
38except ImportError:38except ImportError:
39 from cStringIO import StringIO as StringIO39 from cStringIO import StringIO as StringIO
40# pylint: enable-msg=F040140# pylint: enable=F0401
4141
42DCTRASH = 'dctrash'42DCTRASH = 'dctrash'
4343
44FAKE_RECORD_TYPE = "http://example.org/test"
45
46js = """
47function(doc) {
48 if (doc.record_type == '%s') {
49 emit(doc._id, null);
50 }
51}""" % FAKE_RECORD_TYPE
52
5344
54def get_test_context():45def get_test_context():
55 return test_environment.test_context46 """Return test context."""
47 return test_environment.test_context
48
49# pylint: disable=W0212
50# I don't care about private members in tests.
51
5652
57class TestCouchDatabaseDeprecated(testtools.TestCase):53class TestCouchDatabaseDeprecated(testtools.TestCase):
58 """Test specific for CouchDatabase"""54 """Test specific for CouchDatabase"""
@@ -84,9 +80,11 @@
84 super(TestCouchDatabaseDeprecated, self).tearDown()80 super(TestCouchDatabaseDeprecated, self).tearDown()
8581
86 def maybe_die(self):82 def maybe_die(self):
83 """Method that could kill couchdb. Or could it? Or COULD it?"""
87 pass84 pass
8885
89 def wait_until_server_dead(self, pid=None):86 def wait_until_server_dead(self, pid=None): # pylint: disable=R0201
87 """Wait until the server is no longer breathing."""
90 if pid is not None:88 if pid is not None:
91 pid = find_pid(89 pid = find_pid(
92 start_if_not_running=False, ctx=get_test_context())90 start_if_not_running=False, ctx=get_test_context())
@@ -102,11 +100,12 @@
102 def test_get_records_by_record_type_save_view(self):100 def test_get_records_by_record_type_save_view(self):
103 """Test getting mutliple records by type"""101 """Test getting mutliple records by type"""
104 records = self.database.get_records(102 records = self.database.get_records(
105 record_type="test.com",create_view=True)103 record_type="test.com", create_view=True)
106 self.maybe_die() # should be able to survive couchdb death104 self.maybe_die() # should be able to survive couchdb death
107 self.assertEqual(3, len(records))105 self.assertEqual(3, len(records))
108106
109 def test_func_get_records(self):107 def test_func_get_records(self):
108 """Functional test of get_records."""
110 record_ids_we_care_about = set()109 record_ids_we_care_about = set()
111 good_record_type = "http://example.com/unittest/good"110 good_record_type = "http://example.com/unittest/good"
112 other_record_type = "http://example.com/unittest/bad"111 other_record_type = "http://example.com/unittest/bad"
@@ -145,18 +144,19 @@
145 design_doc="mustNotExist", create_view=False)144 design_doc="mustNotExist", create_view=False)
146145
147 def test_get_view_by_type_new_but_already(self):146 def test_get_view_by_type_new_but_already(self):
147 """Test calling the view."""
148 self.database.get_records(create_view=True)148 self.database.get_records(create_view=True)
149 self.maybe_die() # should be able to survive couchdb death149 self.maybe_die() # should be able to survive couchdb death
150 self.database.get_records(create_view=True)150 self.database.get_records(create_view=True)
151 # No exceptions on second run? Yay.151 # No exceptions on second run? Yay.
152152
153 def test_get_view_by_type_createxcl_fail(self):153 def test_get_view_by_type_createxcl_fail(self):
154 """Test that calling the view without creating it fails."""
154 self.database.get_records(create_view=True)155 self.database.get_records(create_view=True)
155 self.maybe_die() # should be able to survive couchdb death156 self.maybe_die() # should be able to survive couchdb death
156 self.assertRaises(KeyError, self.database.get_records, create_view=None)157 self.assertRaises(KeyError, self.database.get_records, create_view=None)
157158
158159
159
160class TestCouchDatabase(testtools.TestCase):160class TestCouchDatabase(testtools.TestCase):
161 """tests specific for CouchDatabase"""161 """tests specific for CouchDatabase"""
162162
@@ -187,9 +187,11 @@
187 super(TestCouchDatabase, self).tearDown()187 super(TestCouchDatabase, self).tearDown()
188188
189 def maybe_die(self):189 def maybe_die(self):
190 """Method that could kill couchdb. Or could it? Or COULD it?"""
190 pass191 pass
191192
192 def wait_until_server_dead(self, pid=None):193 def wait_until_server_dead(self, pid=None): # pylint: disable=R0201
194 """Wait until the server is no longer breathing."""
193 if pid is not None:195 if pid is not None:
194 pid = find_pid(196 pid = find_pid(
195 start_if_not_running=False, ctx=get_test_context())197 start_if_not_running=False, ctx=get_test_context())
@@ -203,6 +205,7 @@
203 break205 break
204206
205 def test_database_not_exists(self):207 def test_database_not_exists(self):
208 """Test that the database does not exist."""
206 self.assertRaises(209 self.assertRaises(
207 NoSuchDatabase, CouchDatabase, "this-must-not-exist", create=False)210 NoSuchDatabase, CouchDatabase, "this-must-not-exist", create=False)
208211
@@ -251,7 +254,7 @@
251 # put the batch and ensure that the records have been added254 # put the batch and ensure that the records have been added
252 batch_result = self.database.put_records_batch(batch)255 batch_result = self.database.put_records_batch(batch)
253 for current_tuple in batch_result:256 for current_tuple in batch_result:
254 success, docid, rev_or_exc = current_tuple257 success, docid, rev_or_exc = current_tuple # pylint: disable=W0612
255 if success:258 if success:
256 self.assertTrue(self.database._server[self.dbname][docid])259 self.assertTrue(self.database._server[self.dbname][docid])
257 else:260 else:
@@ -299,15 +302,6 @@
299302
300 self.database.delete_record(new_record_id)303 self.database.delete_record(new_record_id)
301304
302 def test_get_deleted_record(self):
303 """Test (not) getting a deleted record."""
304 record = Record({'record_number': 0}, record_type="http://example.com/")
305 record_id = self.database.put_record(record)
306 self.database.delete_record(record_id)
307 self.maybe_die() # should be able to survive couchdb death
308 retrieved_record = self.database.get_record(record_id)
309 self.assertEqual(None, retrieved_record)
310
311 def test_record_exists(self):305 def test_record_exists(self):
312 """Test checking whether a record exists."""306 """Test checking whether a record exists."""
313 record = Record({'record_number': 0}, record_type="http://example.com/")307 record = Record({'record_number': 0}, record_type="http://example.com/")
@@ -338,6 +332,7 @@
338 self.assertEqual(3, working_copy['field3'])332 self.assertEqual(3, working_copy['field3'])
339333
340 def test_view_add_and_delete(self):334 def test_view_add_and_delete(self):
335 """Test adding and deleting a view."""
341 design_doc = "design"336 design_doc = "design"
342 view1_name = "unit_tests_are_wonderful"337 view1_name = "unit_tests_are_wonderful"
343 view2_name = "unit_tests_are_marvelous"338 view2_name = "unit_tests_are_marvelous"
@@ -365,6 +360,7 @@
365 KeyError, self.database.delete_view, view2_name, design_doc)360 KeyError, self.database.delete_view, view2_name, design_doc)
366361
367 def test_func_get_all_records(self):362 def test_func_get_all_records(self):
363 """Functional test of get_all_records."""
368 record_ids_we_care_about = set()364 record_ids_we_care_about = set()
369 good_record_type = "http://example.com/unittest/good"365 good_record_type = "http://example.com/unittest/good"
370 other_record_type = "http://example.com/unittest/bad"366 other_record_type = "http://example.com/unittest/bad"
@@ -401,6 +397,7 @@
401 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")397 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")
402398
403 def test_list_views(self):399 def test_list_views(self):
400 """Test the list views."""
404 design_doc = "d"401 design_doc = "d"
405 self.assertEqual(self.database.list_views(design_doc), [])402 self.assertEqual(self.database.list_views(design_doc), [])
406403
@@ -415,12 +412,14 @@
415 self.assertEqual(self.database.list_views(design_doc), [])412 self.assertEqual(self.database.list_views(design_doc), [])
416413
417 def test_get_view_by_type_new_but_already(self):414 def test_get_view_by_type_new_but_already(self):
415 """Test calling the view."""
418 self.database.get_all_records()416 self.database.get_all_records()
419 self.maybe_die() # should be able to survive couchdb death417 self.maybe_die() # should be able to survive couchdb death
420 self.database.get_all_records()418 self.database.get_all_records()
421 # No exceptions on second run? Yay.419 # No exceptions on second run? Yay.
422420
423 def test_get_changes(self):421 def test_get_changes(self):
422 """Test get_changes."""
424 self.test_put_record()423 self.test_put_record()
425 self.test_update_fields()424 self.test_update_fields()
426 self.test_delete_record()425 self.test_delete_record()
@@ -435,7 +434,10 @@
435 self.failUnless("id" in change)434 self.failUnless("id" in change)
436435
437 def test_report_changes_polite(self):436 def test_report_changes_polite(self):
437 """Test reporting changes."""
438
438 def rep(**kwargs):439 def rep(**kwargs):
440 """Check the keyword arguments for changes and id."""
439 self.failUnless("changes" in kwargs)441 self.failUnless("changes" in kwargs)
440 self.failUnless("id" in kwargs)442 self.failUnless("id" in kwargs)
441443
@@ -453,7 +455,10 @@
453 self.assertEqual(0, count)455 self.assertEqual(0, count)
454456
455 def test_report_changes_exceptions(self):457 def test_report_changes_exceptions(self):
458 """Test reporting changes in the face of exceptions."""
459
456 def rep(**kwargs):460 def rep(**kwargs):
461 """Check the keyword arguments for changes and id."""
457 self.failUnless("changes" in kwargs)462 self.failUnless("changes" in kwargs)
458 self.failUnless("id" in kwargs)463 self.failUnless("id" in kwargs)
459464
@@ -470,7 +475,7 @@
470475
471 # Exceptions in our callbacks do not consume an event.476 # Exceptions in our callbacks do not consume an event.
472 self.assertRaises(477 self.assertRaises(
473 ZeroDivisionError, self.database.report_changes, lambda **kw: 1/0)478 ZeroDivisionError, self.database.report_changes, lambda **kw: 1 / 0)
474479
475 # Ensure pos'n is same.480 # Ensure pos'n is same.
476 self.assertEqual(saved_position, self.database._changes_since)481 self.assertEqual(saved_position, self.database._changes_since)
@@ -488,7 +493,10 @@
488 self.assertEqual(saved_position + 1, self.database._changes_since)493 self.assertEqual(saved_position + 1, self.database._changes_since)
489494
490 def test_report_changes_all_ops_give_known_keys(self):495 def test_report_changes_all_ops_give_known_keys(self):
496 """Test reporting changes results have the right keys."""
497
491 def rep(**kwargs):498 def rep(**kwargs):
499 """Check the keyword arguments for changes and id."""
492 self.failUnless("changes" in kwargs)500 self.failUnless("changes" in kwargs)
493 self.failUnless("id" in kwargs)501 self.failUnless("id" in kwargs)
494502
@@ -498,7 +506,10 @@
498 self.database.report_changes(rep)506 self.database.report_changes(rep)
499507
500 def test_report_changes_nochanges(self):508 def test_report_changes_nochanges(self):
509 """Test report changes when there are none."""
510
501 def rep(**kwargs):511 def rep(**kwargs):
512 """Check the keyword arguments for changes and id."""
502 self.failUnless("changes" in kwargs)513 self.failUnless("changes" in kwargs)
503 self.failUnless("id" in kwargs)514 self.failUnless("id" in kwargs)
504515
@@ -515,6 +526,7 @@
515 self.assertEqual(saved_position, self.database._changes_since)526 self.assertEqual(saved_position, self.database._changes_since)
516527
517 def test_attachments(self):528 def test_attachments(self):
529 """Test attachments."""
518 content = StringIO("0123456789\n==========\n\n" * 5)530 content = StringIO("0123456789\n==========\n\n" * 5)
519531
520 constructed_record = Record(532 constructed_record = Record(
@@ -529,12 +541,12 @@
529 constructed_record.attach("string", "another document", "text/plain")541 constructed_record.attach("string", "another document", "text/plain")
530542
531 self.maybe_die() # should be able to survive couchdb death543 self.maybe_die() # should be able to survive couchdb death
532 constructed_record.attach("XXXXXXXXX", "never used", "text/plain")544 constructed_record.attach(
545 "SOMETHINGSOMETHING", "never used", "text/plain")
533 constructed_record.detach("never used") # detach works before commit.546 constructed_record.detach("never used") # detach works before commit.
534547
535 # We can read from a document that we constructed.548 # We can read from a document that we constructed.
536 out_file, out_content_type = \549 _, out_content_type = constructed_record.attachment_data("nu/mbe/rs")
537 constructed_record.attachment_data("nu/mbe/rs")
538 self.assertEqual(out_content_type, "text/plain")550 self.assertEqual(out_content_type, "text/plain")
539551
540 # One can not put another document of the same name.552 # One can not put another document of the same name.
@@ -604,6 +616,7 @@
604 self.database.delete_record(record_id)616 self.database.delete_record(record_id)
605617
606 def test_view_fetch(self):618 def test_view_fetch(self):
619 """Test view fetch."""
607 design_doc = "test_view_fetch"620 design_doc = "test_view_fetch"
608 view1_name = "unit_tests_are_great_yeah"621 view1_name = "unit_tests_are_great_yeah"
609622
@@ -641,7 +654,7 @@
641 non_working_copy['field3'] = 3654 non_working_copy['field3'] = 3
642 self.database.put_record(non_working_copy)655 self.database.put_record(non_working_copy)
643 self.database.update_fields(656 self.database.update_fields(
644 record_id, {'field1': 11,('nested', 'sub2'): 's2-changed'},657 record_id, {'field1': 11, ('nested', 'sub2'): 's2-changed'},
645 cached_record=record)658 cached_record=record)
646 working_copy = self.database.get_record(record_id)659 working_copy = self.database.get_record(record_id)
647 self.assertEqual(0, working_copy['record_number'])660 self.assertEqual(0, working_copy['record_number'])
648661
=== modified file 'utilities/lint.sh'
--- utilities/lint.sh 2010-11-11 19:01:41 +0000
+++ utilities/lint.sh 2010-11-12 14:40:00 +0000
@@ -37,7 +37,7 @@
37fi37fi
3838
39export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH"39export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH"
40pylint="`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"40pylint="`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"
4141
42pylint_notices=`$pylint $pyfiles`42pylint_notices=`$pylint $pyfiles`
4343

Subscribers

People subscribed via source and target branches