How to access desktopcouch contacts from outside evolution?

Asked by Matt Price on 2009-10-15

I'd like to be able to query my desktopcouch contacts list from mutt or emacs; this is because, while I use evolution on my full-powered system, on my small bring-it-everywhere laptop I run a minimal X session and emacs. I'd like to have my contacts available on both, though. Can you guys point me to a resource somewhere that documents how i could perform a query on the db externally, say from bash or python; i could then hook into mutt or emacs using their own interfaces.

thanks, i'm really looking forward to desktopcouch!
matt

Question information

Language:
English Edit question
Status:
Solved
For:
desktopcouch Edit question
Assignee:
Stuart Langridge Edit question
Solved by:
Matt Price
Solved:
2009-10-15
Last query:
2009-10-15
Last reply:
2009-10-15
Whiteboard:
Stuart, Can you answer this question for Matt? Thanks!
Nicola Larosa (teknico) said : #1

Hi Matt, there are some initial Python API docs in the desktopcouch/records/doc directory in the tarball or branch, try and see if they make sense.

Once you have any integration working with mutt or emacs, it'd be great to have a look at it!

Stuart Langridge (sil) said : #2

Matt: those docs should be on your machine as well as /usr/share/doc/python-desktopcouch-records/api/records.txt.

Matt Price (matt-price) said : #3

Nicola and Stuart, thanks for the pointers. Looking at the
        documentation, I think I may need some basic couchdb help. It doesn't
        seem entirely straightforward to query the database, say, looking for a
        name or an email. so, for instance, i have this couchdb record in my
        contacts database (ugly text copied from the html view):

        _id "pas-id-44C2A18600000000"
        _rev"1-74cb956983d160ec944f4219ad455c31"
        email_addresses
        eed38e30-f6c4-49b0-974f-5417eb5689d7
        description
                "(null)"
        address
                "<email address hidden>"
        first_name"me"
        last_name"gmail"
        record_type"http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact"

        if i know this in advance i can access the individual fields like this:
        >>> db = CouchDatabase('contacts')
        >>> fetched = db.get_record("pas-id-44C2A18600000000")
        >>> print fetched['first_name']

        but what if i want to search for people named 'me', or addresses
        containing "gmail"? Will i need to create a design document, then write
        a view, and a function that iterates overthe rows returned?
And do the map/reduce functions need to be written in
        javascript?
        alternatively, is there a design document somewhere -- say, the one used
        by evolution -- that i can query directly from python? (i noticed the
        web interface doesn't think there are any permanent design docs in the
        contacts db - i'm wondering also if that's one reason why email-address
        completion is currently so slow in evolution).

        thanks much for your help. i'll keep mining the web for more info, but
        any further assistance you guys can give is much appreciated.

        Matt

Matt Price (matt-price) said : #4

hey sorry for the lousy formatting on that last response, copied and
pasted from evolution after my last email was rejected...

Stuart Langridge (sil) said : #5

Yep; the way to query the database is by writing a view to do so. In your example, your view would look like:

function(doc) {
  emit(doc.first_name, doc)
}

and then you access that view by name

result = db.execute_view("your_view_name", "your_design_doc")
print result["me"]

Matt Price (matt-price) said : #6

On Thu, 2009-10-15 at 18:06 +0000, Stuart Langridge wrote:
> Your question #85917 on desktopcouch changed:
> https://answers.edge.launchpad.net/desktopcouch/+question/85917
>
> Stuart Langridge posted a new comment:
> Yep; the way to query the database is by writing a view to do so. In
> your example, your view would look like:
>
> function(doc) {
> emit(doc.first_name, doc)
> }
>
> and then you access that view by name
>
> result = db.execute_view("your_view_name", "your_design_doc")
> print result["me"]
>
since you're generous enough to answer,
this works great, thusly:

>>> map_js = "function(doc) {emit (doc.last_name,doc); }"
>>> db.add_view("last_name_query", map_js, None, "Some_container")
>>> result=db.execute_view("last_name_query","Some_container")
>>> print result['gmail'].rows
[<Row id='pas-id-44C2A18600000000', key='gmail', value={'first_name':
'me', 'last_name': 'gmail', '_rev':
'1-74cb956983d160ec944f4219ad455c31', 'record_type':
'http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact',
'_id': 'pas-id-44C2A18600000000', 'email_addresses':
{'eed38e30-f6c4-49b0-974f-5417eb5689d7': {'description': '(null)',
'address': '<email address hidden>'}}}>]

print result just gave me the object description:
<ViewResults <PermanentView
'_design/Some_container/_view/last_name_query'> {}>{'key': 'gmail'}>

since what i really want is a completion function, i think i need to
return something like a list of dictionaries, or maybe just of lists:
[['Matt Price', '<email address hidden>'],['Matt
Price','<email address hidden>'],['Stuart','<email address hidden>']]

What do you think would be the best way to get this information? i
thought from the way you responded last time that there's an efficient
way of grabbing the data directly from couchdb, rather than getting a
big old python object and then massaging it slowly in my python code.

thanks for being patient with me. it's fun to learn this stuff.

matt

--
Matt Price
<email address hidden>

Matt Price (matt-price) said : #7

> Stuart Langridge posted a new comment:
> Yep; the way to query the database is by writing a view to do so. In
> your example, your view would look like:
>
> function(doc) {
> emit(doc.first_name, doc)
> }
>
> and then you access that view by name
>
> result = db.execute_view("your_view_name", "your_design_doc")
> print result["me"]

getting closer to understanding this. if i do this:

result=db.get_records(None,True,'Some_container')

then result.rows returns a tuple of row objects that look like this:
<Row id='pas-id-49C403F900000000', key='http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact', value={'first_name': 'Shivrang', 'last_name': 'Setlur', '_rev': '1-1944d6d0b46cedb7963e77387c3d9795', 'record_type': 'http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact', '_id': 'pas-id-49C403F900000000', 'email_addresses': {'44791a75-38ea-4ec9-9f71-0898538b1175': {'description': '(null)', 'address': '<email address hidden>'}}}>

but if instead i define a very simple mapping function:

map_js = "function(doc) {emit (doc.last_name,doc); }"
db.add_view("last_name_query", map_js, None, "Some_container")
result=db.execute_view("last_name_query","Some_container")

then the tuple returned looks like this:
<Row id='pas-id-4AC73EFD00000000', key='Snell', value={'first_name': 'Kim', 'last_name': 'Snell', '_rev': '1-d2ea1f47ad75bc33ef89ebaa085a8eec', 'record_type': 'http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact', '_id': 'pas-id-4AC73EFD00000000', 'email_addresses': {'04d498a5-347e-4da8-a432-f62ec6e3bde9': {'description': '(null)', 'address': '<email address hidden>'}}}>

and the key is now a useful value. nonetheless i can't just execute

print result["me"]

and get the values i'm looking for. instead i have a somewhat
ugly-feeling selection logic:

for x in result:
 if x.key="me":
  for y in x.value['email_addresses']:
    print x.value['email_addresses'][y]['address']

this seems a little clumsy to me and i'm wondering whether i'm missing a
class somewhere that would make my code less convoluted and ultimately
more reusable by other people.

it seems a bit odd to keep posting to this question, and i know you're
all busy with the release coming up; so just ignore this if it's the
wrong forum.

thanks for all the help,
matt

Matt Price (matt-price) said : #8

I'm returning to this after a while and I have some very simple code
that works OK for a query on last name:

Matt Price (matt-price) said : #9

[oops, hit ctrl-return]

here's the code:

!/usr/bin/python
import sys
from desktopcouch.records.server import CouchDatabase
from desktopcouch.records.record import Record

# define the search string
searchString=""
if len(sys.argv) > 1:
    searchString= sys.argv[1]
# initialize db
db = CouchDatabase('contacts')

#create view
design_doc = "matts_cli_client"
map_js = "function(doc) {emit (doc.last_name,doc); }"
db.add_view("last_name_query", map_js, None, design_doc)
# results=db.get_records(None,True,design_doc)
results=db.execute_view("last_name_query",design_doc)

# print matching results
for x in results:
    if searchString.lower() in x.key.lower():
        print x.value["first_name"] + " " + x.value["last_name"]
        if "email_addresses" in x.value:
            for y in x.value['email_addresses']:
                print " " + x.value['email_addresses'][y]['address']

------------------------------------
This seems pretty functional for now, and I can figure out the best way
to format the output for emacs or mutt a little later. First, though,
I'd like to improve the search a little. This very simple function lets
me search on last name only, but it would probably be better if it
checked for matches in a variety of fields -- say first, last, and all
the email addresses. It'd be great if my key, in the view, were a
combination of those fields (so that key looked like, say, "Matt Price
<email address hidden>"). In fact, if I could just get the map/reduce
functions to produce a sequence such keys, that's really be all i need.
So I just wondered whether you could tell me where the evolution
contact-search design document is, so I could usei t as a model -- I
assume it's somewhere, but i didn't find it in a quick search through
the source of evolution-couchdb and desktopcouch.

Thanks as always!

Stuart Langridge (sil) said : #10

Matt,

You may not know that a map function can emit more than one document. So:

import sys
from desktopcouch.records.server import CouchDatabase
from desktopcouch.records.record import Record

# define the search string
searchString=""
if len(sys.argv) > 1:
    searchstring= sys.argv[1]
# initialize db
db = CouchDatabase('contacts')

#create view
design_doc = "matts_cli_client"
map_js = """function(doc) {
    if (doc.last_name) emit(doc.last_name.toLowerCase(), doc);
    if (doc.first_name) emit(doc.first_name.toLowerCase(), doc);
    for (k in doc.email_addresses) {
        emit(doc.email_addresses[k].address.toLowerCase(), doc);
    }
}"""
db.add_view("names_emails_query", map_js, None, design_doc)
# results=db.get_records(None,True,design_doc)
results=db.execute_view("names_emails_query", design_doc)

# print matching results
for contact in results[searchstring:searchstring]:
    print "%(first_name)s %(last_name)s" % contact.value
    if "email_addresses" in contact.value:
        for eml in contact.value['email_addresses']:
            print " %s" % contact.value['email_addresses'][eml]['address']