# vim: tabstop=4 expandtab autoindent shiftwidth=4 fileencoding=utf-8

import gdata.docs.service
import gdata.spreadsheet.service

# This is inconveniently different from gdata.spreadsheet.service.RequestError
# Used in test cases for example
from gdata.service import RequestError

class Client(object):
    """Unified client wraps around both docs and spreadsheet client
    """

    def __init__(self, email, password):
        """Authentication
        """

        self.ssclient = gdata.spreadsheet.service.SpreadsheetsService(email=email, password=password)
        self.docsclient = gdata.docs.service.DocsService(email=email, password=password)

        self.ssclient.ProgrammaticLogin()
        self.docsclient.ProgrammaticLogin()

class Database(object):
    """Spreadsheet-Database
    """

    def __init__(self, email, password, key=None):
        """Constructor, client is an external client we can reuse
        """

        self.client = Client(email, password)

        ## Emtpy tables; set SpreadsheetsWorksheetsFeed
        self.tables = None

        if key is None:
            ## Set by create() or open()
            self.db = None

            ## This is an identifier for our spreadsheet, so tables can be added correctly
            # Dependant on db
            self.key = None
        else:
            self.open(key)

    def __str__(self):
        return self.key

    def __unicode__(self):
        return u'%s' % self

    def refresh_tables(self):
        """Method that updates the db's tables (SpreadsheetsWorksheetsFeed)
        """

        if self.key is None:
            raise AttributeError('Can not refresh tables on uninitialised db')

        self.tables = self.client.ssclient.GetWorksheetsFeed(self.key)

    def create(self, name, data=',,,'):
        """Create a new spreadsheet-database identified by name, containing data from file ob/string
        """

        if not name or not isinstance(name, basestring):
            raise ValueError('Require textual, non-zero-length name')

        if not isinstance(data, file):
            from cStringIO import StringIO

            data = StringIO(data)

        clen = len(data.read())
        data.seek(0)

        data_source = gdata.MediaSource(file_handle=data, content_type='text/csv', content_length=clen)

        db = self.client.docsclient.Upload(data_source, name, label='Spreadsheet')

        self.db = db
        
        ## Create also the key
        id_parts = self.db.id.text.split('/')
        self.key = id_parts[-1].replace('spreadsheet%3A', '')

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

    def open(self, key):
        """Open an existing spreadsheet
        """

        ## Set our key
        self.key = key

        base_uri = r'/feeds/documents/private/full/spredsheet%%3A%s' % self.key

        ## Set also the "db"
        self.db = self.client.docsclient.GetDocumentListEntry(base_uri)

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

    def create_table(self, name, fields):
        """Create a new worksheet-table with given fields
        """

        if not self.db:
            raise AttributeError('Create a database first!')

        table = Table(self)

        table.create(name, fields)

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

        return table

    def open_table(self, name):
        """Retrieve table
        """

        try:
            worksheet = [t for t in self.tables.entry if t.title.text == name][0]

            ## Fake us a Table instance from that
            table = Table(self)

            table.worksheet = worksheet

            # Create its worksheet id
            id_parts = table.worksheet.id.text.split('/')
            table.worksheet_id = id_parts[-1]

            ### TODO: Figure out how to get fields from empty (fields-only) table
            ## Cheat the fields
            #row = table.get_row(1)
            
            ## The return is a dictionary, with titles and gdata.spreadsheet.Custom instances
            #table.fields = row.row.custom.keys()

            ## Make sure this does not remain dangling anywhere
            #del row

            return table
        except IndexError:
            raise AttributeError('Table "%s" not found' % name)


class Table(object):
    """Table object
    """

    def __init__(self, db_instance):
        """Create a Table instance for db, call create() to actually create it
        """

        self.db = db_instance

        # Set some shortcuts for ease
        self.client = self.db.client

        # And this is to come
        self.worksheet = None
        self.worksheet_id = None

        # This must be populated by create, maybe in the future also by get
        self.fields = None

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text

    def create(self, name, fields):
        """Create remote table
        """

        self.fields = fields

        ## Defaults
        row_count = 1
        col_count = len(fields)

        self.worksheet = self.client.ssclient.AddWorksheet(name, row_count, col_count, self.db.key)

        # Create our worksheet id
        id_parts = self.worksheet.id.text.split('/')
        self.worksheet_id = id_parts[-1]

        ## Apparently Google Spreadsheet sucks enough to use row 1 as titles
        i = 0
        for column in self.fields:
            i += 1
            self.client.ssclient.UpdateCell(1, i, column, self.db.key, self.worksheet_id)

    def insert_into(self, **kwargs):
        """Insert a new row
        Give the data as keyword arguments, eg insert_into(uid=1, username='foo')
        """

        if not self.worksheet:
            raise AttributeError('Create a worksheet first!')

        ## TODO: Figure out how to get fields out of an empty table!
        #for key in kwargs.keys():
        #    if not key in self.fields:
        #        raise KeyError('Table does not accept key "%s"' % key)

        row = Row(self)

        row.create(**kwargs)

        return row

    def get_row(self, row_num):
        """Get a row by number; 1-indexed
        """

        qry = gdata.spreadsheet.service.ListQuery()
        qry.start_index = unicode(row_num)
        qry.max_results = '1'

        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.worksheet_id, query=qry)

        if len(res.entry):
            row = Row(self)

            ## Set the real data as if we were just created
            row.row = res.entry[0]

            ## Populate the data dictionary too
            for label, bob in row.row.custom.items():
                row.data[label] = bob.text

            return row

        raise ValueError('No such row')

    def filter(self, reverse=False, orderby=None, **kwargs):
        """Retrieve certain rows by some kind of filter in ascending order
        reverse makes it descend, False here is also Google's default
        """

        qry = gdata.spreadsheet.service.ListQuery()

        ## Support only equality for now in kwargs
        # TODO: http://code.google.com/apis/spreadsheets/data/1.0/reference.html#list_Parameters
        sq = ''
        kwlist = []
        for k, v in kwargs.items():
            kwlist.append('%s == %s' % (k, v))

        sq = ' and '.join(kwlist)

        if sq:
            qry.sq = sq

        if reverse:
            qry.reverse = 'true'

        if orderby:
            qry.orderby = 'column:%s' % orderby

        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.worksheet_id, query=qry)

        if len(res.entry):
            rows = []
            for e in res.entry:
                row = Row(self)

                ## Set the real data as if we were just created
                row.row = e

                ## Populate the data dictionary too
                for label, bob in row.row.custom.items():
                    row.data[label] = bob.text

                rows.append(row)

            return rows

        ## To have django-like iterability even on empty results
        return []

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """

        from random import choice

        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)

        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """

        self.table = table

        ## Lots of shortcutting
        # "Connection"
        self.client = self.table.db.client

        # Table object
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id

        # Addressing
        self.fields = self.table.fields

        ## And something to come later
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """

        for k, v in kwargs.items():
            kwargs[k] = unicode(v)

        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key, wksht_id=self.worksheet_id)

        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """

        ## Anything not given is defaulted to empty in Google
        data = self.data
        data.update(kwargs)

        self.row = self.client.ssclient.UpdateRow(self.row, data)

        self.data = data

# EOF


620bebeaab27a9e16c8762055f9476db8f087f3a
<mask token>


class Table(object):
    <mask token>
    <mask token>

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """
        from random import choice
        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)
        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """
        self.table = table
        self.client = self.table.db.client
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id
        self.fields = self.table.fields
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """
        for k, v in kwargs.items():
            kwargs[k] = unicode(v)
        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key,
            wksht_id=self.worksheet_id)
        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """
        data = self.data
        data.update(kwargs)
        self.row = self.client.ssclient.UpdateRow(self.row, data)
        self.data = data

<mask token>


class Database(object):
    <mask token>
    <mask token>
    <mask token>

    def __unicode__(self):
        return u'%s' % self

    def refresh_tables(self):
        """Method that updates the db's tables (SpreadsheetsWorksheetsFeed)
        """
        if self.key is None:
            raise AttributeError('Can not refresh tables on uninitialised db')
        self.tables = self.client.ssclient.GetWorksheetsFeed(self.key)
    <mask token>

    def open(self, key):
        """Open an existing spreadsheet
        """
        self.key = key
        base_uri = '/feeds/documents/private/full/spredsheet%%3A%s' % self.key
        self.db = self.client.docsclient.GetDocumentListEntry(base_uri)
        self.refresh_tables()
    <mask token>
    <mask token>


class Table(object):
    """Table object
    """

    def __init__(self, db_instance):
        """Create a Table instance for db, call create() to actually create it
        """
        self.db = db_instance
        self.client = self.db.client
        self.worksheet = None
        self.worksheet_id = None
        self.fields = None

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text

    def create(self, name, fields):
        """Create remote table
        """
        self.fields = fields
        row_count = 1
        col_count = len(fields)
        self.worksheet = self.client.ssclient.AddWorksheet(name, row_count,
            col_count, self.db.key)
        id_parts = self.worksheet.id.text.split('/')
        self.worksheet_id = id_parts[-1]
        i = 0
        for column in self.fields:
            i += 1
            self.client.ssclient.UpdateCell(1, i, column, self.db.key, self
                .worksheet_id)

    def insert_into(self, **kwargs):
        """Insert a new row
        Give the data as keyword arguments, eg insert_into(uid=1, username='foo')
        """
        if not self.worksheet:
            raise AttributeError('Create a worksheet first!')
        row = Row(self)
        row.create(**kwargs)
        return row

    def get_row(self, row_num):
        """Get a row by number; 1-indexed
        """
        qry = gdata.spreadsheet.service.ListQuery()
        qry.start_index = unicode(row_num)
        qry.max_results = '1'
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            row = Row(self)
            row.row = res.entry[0]
            for label, bob in row.row.custom.items():
                row.data[label] = bob.text
            return row
        raise ValueError('No such row')

    def filter(self, reverse=False, orderby=None, **kwargs):
        """Retrieve certain rows by some kind of filter in ascending order
        reverse makes it descend, False here is also Google's default
        """
        qry = gdata.spreadsheet.service.ListQuery()
        sq = ''
        kwlist = []
        for k, v in kwargs.items():
            kwlist.append('%s == %s' % (k, v))
        sq = ' and '.join(kwlist)
        if sq:
            qry.sq = sq
        if reverse:
            qry.reverse = 'true'
        if orderby:
            qry.orderby = 'column:%s' % orderby
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            rows = []
            for e in res.entry:
                row = Row(self)
                row.row = e
                for label, bob in row.row.custom.items():
                    row.data[label] = bob.text
                rows.append(row)
            return rows
        return []

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """
        from random import choice
        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)
        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """
        self.table = table
        self.client = self.table.db.client
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id
        self.fields = self.table.fields
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """
        for k, v in kwargs.items():
            kwargs[k] = unicode(v)
        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key,
            wksht_id=self.worksheet_id)
        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """
        data = self.data
        data.update(kwargs)
        self.row = self.client.ssclient.UpdateRow(self.row, data)
        self.data = data

<mask token>


class Database(object):
    <mask token>

    def __init__(self, email, password, key=None):
        """Constructor, client is an external client we can reuse
        """
        self.client = Client(email, password)
        self.tables = None
        if key is None:
            self.db = None
            self.key = None
        else:
            self.open(key)

    def __str__(self):
        return self.key

    def __unicode__(self):
        return u'%s' % self

    def refresh_tables(self):
        """Method that updates the db's tables (SpreadsheetsWorksheetsFeed)
        """
        if self.key is None:
            raise AttributeError('Can not refresh tables on uninitialised db')
        self.tables = self.client.ssclient.GetWorksheetsFeed(self.key)

    def create(self, name, data=',,,'):
        """Create a new spreadsheet-database identified by name, containing data from file ob/string
        """
        if not name or not isinstance(name, basestring):
            raise ValueError('Require textual, non-zero-length name')
        if not isinstance(data, file):
            from cStringIO import StringIO
            data = StringIO(data)
        clen = len(data.read())
        data.seek(0)
        data_source = gdata.MediaSource(file_handle=data, content_type=
            'text/csv', content_length=clen)
        db = self.client.docsclient.Upload(data_source, name, label=
            'Spreadsheet')
        self.db = db
        id_parts = self.db.id.text.split('/')
        self.key = id_parts[-1].replace('spreadsheet%3A', '')
        self.refresh_tables()

    def open(self, key):
        """Open an existing spreadsheet
        """
        self.key = key
        base_uri = '/feeds/documents/private/full/spredsheet%%3A%s' % self.key
        self.db = self.client.docsclient.GetDocumentListEntry(base_uri)
        self.refresh_tables()

    def create_table(self, name, fields):
        """Create a new worksheet-table with given fields
        """
        if not self.db:
            raise AttributeError('Create a database first!')
        table = Table(self)
        table.create(name, fields)
        self.refresh_tables()
        return table

    def open_table(self, name):
        """Retrieve table
        """
        try:
            worksheet = [t for t in self.tables.entry if t.title.text == name][
                0]
            table = Table(self)
            table.worksheet = worksheet
            id_parts = table.worksheet.id.text.split('/')
            table.worksheet_id = id_parts[-1]
            return table
        except IndexError:
            raise AttributeError('Table "%s" not found' % name)


class Table(object):
    """Table object
    """

    def __init__(self, db_instance):
        """Create a Table instance for db, call create() to actually create it
        """
        self.db = db_instance
        self.client = self.db.client
        self.worksheet = None
        self.worksheet_id = None
        self.fields = None

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text

    def create(self, name, fields):
        """Create remote table
        """
        self.fields = fields
        row_count = 1
        col_count = len(fields)
        self.worksheet = self.client.ssclient.AddWorksheet(name, row_count,
            col_count, self.db.key)
        id_parts = self.worksheet.id.text.split('/')
        self.worksheet_id = id_parts[-1]
        i = 0
        for column in self.fields:
            i += 1
            self.client.ssclient.UpdateCell(1, i, column, self.db.key, self
                .worksheet_id)

    def insert_into(self, **kwargs):
        """Insert a new row
        Give the data as keyword arguments, eg insert_into(uid=1, username='foo')
        """
        if not self.worksheet:
            raise AttributeError('Create a worksheet first!')
        row = Row(self)
        row.create(**kwargs)
        return row

    def get_row(self, row_num):
        """Get a row by number; 1-indexed
        """
        qry = gdata.spreadsheet.service.ListQuery()
        qry.start_index = unicode(row_num)
        qry.max_results = '1'
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            row = Row(self)
            row.row = res.entry[0]
            for label, bob in row.row.custom.items():
                row.data[label] = bob.text
            return row
        raise ValueError('No such row')

    def filter(self, reverse=False, orderby=None, **kwargs):
        """Retrieve certain rows by some kind of filter in ascending order
        reverse makes it descend, False here is also Google's default
        """
        qry = gdata.spreadsheet.service.ListQuery()
        sq = ''
        kwlist = []
        for k, v in kwargs.items():
            kwlist.append('%s == %s' % (k, v))
        sq = ' and '.join(kwlist)
        if sq:
            qry.sq = sq
        if reverse:
            qry.reverse = 'true'
        if orderby:
            qry.orderby = 'column:%s' % orderby
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            rows = []
            for e in res.entry:
                row = Row(self)
                row.row = e
                for label, bob in row.row.custom.items():
                    row.data[label] = bob.text
                rows.append(row)
            return rows
        return []

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """
        from random import choice
        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)
        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """
        self.table = table
        self.client = self.table.db.client
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id
        self.fields = self.table.fields
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """
        for k, v in kwargs.items():
            kwargs[k] = unicode(v)
        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key,
            wksht_id=self.worksheet_id)
        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """
        data = self.data
        data.update(kwargs)
        self.row = self.client.ssclient.UpdateRow(self.row, data)
        self.data = data

import gdata.docs.service
import gdata.spreadsheet.service
from gdata.service import RequestError


class Client(object):
    """Unified client wraps around both docs and spreadsheet client
    """

    def __init__(self, email, password):
        """Authentication
        """
        self.ssclient = gdata.spreadsheet.service.SpreadsheetsService(email
            =email, password=password)
        self.docsclient = gdata.docs.service.DocsService(email=email,
            password=password)
        self.ssclient.ProgrammaticLogin()
        self.docsclient.ProgrammaticLogin()


class Database(object):
    """Spreadsheet-Database
    """

    def __init__(self, email, password, key=None):
        """Constructor, client is an external client we can reuse
        """
        self.client = Client(email, password)
        self.tables = None
        if key is None:
            self.db = None
            self.key = None
        else:
            self.open(key)

    def __str__(self):
        return self.key

    def __unicode__(self):
        return u'%s' % self

    def refresh_tables(self):
        """Method that updates the db's tables (SpreadsheetsWorksheetsFeed)
        """
        if self.key is None:
            raise AttributeError('Can not refresh tables on uninitialised db')
        self.tables = self.client.ssclient.GetWorksheetsFeed(self.key)

    def create(self, name, data=',,,'):
        """Create a new spreadsheet-database identified by name, containing data from file ob/string
        """
        if not name or not isinstance(name, basestring):
            raise ValueError('Require textual, non-zero-length name')
        if not isinstance(data, file):
            from cStringIO import StringIO
            data = StringIO(data)
        clen = len(data.read())
        data.seek(0)
        data_source = gdata.MediaSource(file_handle=data, content_type=
            'text/csv', content_length=clen)
        db = self.client.docsclient.Upload(data_source, name, label=
            'Spreadsheet')
        self.db = db
        id_parts = self.db.id.text.split('/')
        self.key = id_parts[-1].replace('spreadsheet%3A', '')
        self.refresh_tables()

    def open(self, key):
        """Open an existing spreadsheet
        """
        self.key = key
        base_uri = '/feeds/documents/private/full/spredsheet%%3A%s' % self.key
        self.db = self.client.docsclient.GetDocumentListEntry(base_uri)
        self.refresh_tables()

    def create_table(self, name, fields):
        """Create a new worksheet-table with given fields
        """
        if not self.db:
            raise AttributeError('Create a database first!')
        table = Table(self)
        table.create(name, fields)
        self.refresh_tables()
        return table

    def open_table(self, name):
        """Retrieve table
        """
        try:
            worksheet = [t for t in self.tables.entry if t.title.text == name][
                0]
            table = Table(self)
            table.worksheet = worksheet
            id_parts = table.worksheet.id.text.split('/')
            table.worksheet_id = id_parts[-1]
            return table
        except IndexError:
            raise AttributeError('Table "%s" not found' % name)


class Table(object):
    """Table object
    """

    def __init__(self, db_instance):
        """Create a Table instance for db, call create() to actually create it
        """
        self.db = db_instance
        self.client = self.db.client
        self.worksheet = None
        self.worksheet_id = None
        self.fields = None

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text

    def create(self, name, fields):
        """Create remote table
        """
        self.fields = fields
        row_count = 1
        col_count = len(fields)
        self.worksheet = self.client.ssclient.AddWorksheet(name, row_count,
            col_count, self.db.key)
        id_parts = self.worksheet.id.text.split('/')
        self.worksheet_id = id_parts[-1]
        i = 0
        for column in self.fields:
            i += 1
            self.client.ssclient.UpdateCell(1, i, column, self.db.key, self
                .worksheet_id)

    def insert_into(self, **kwargs):
        """Insert a new row
        Give the data as keyword arguments, eg insert_into(uid=1, username='foo')
        """
        if not self.worksheet:
            raise AttributeError('Create a worksheet first!')
        row = Row(self)
        row.create(**kwargs)
        return row

    def get_row(self, row_num):
        """Get a row by number; 1-indexed
        """
        qry = gdata.spreadsheet.service.ListQuery()
        qry.start_index = unicode(row_num)
        qry.max_results = '1'
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            row = Row(self)
            row.row = res.entry[0]
            for label, bob in row.row.custom.items():
                row.data[label] = bob.text
            return row
        raise ValueError('No such row')

    def filter(self, reverse=False, orderby=None, **kwargs):
        """Retrieve certain rows by some kind of filter in ascending order
        reverse makes it descend, False here is also Google's default
        """
        qry = gdata.spreadsheet.service.ListQuery()
        sq = ''
        kwlist = []
        for k, v in kwargs.items():
            kwlist.append('%s == %s' % (k, v))
        sq = ' and '.join(kwlist)
        if sq:
            qry.sq = sq
        if reverse:
            qry.reverse = 'true'
        if orderby:
            qry.orderby = 'column:%s' % orderby
        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.
            worksheet_id, query=qry)
        if len(res.entry):
            rows = []
            for e in res.entry:
                row = Row(self)
                row.row = e
                for label, bob in row.row.custom.items():
                    row.data[label] = bob.text
                rows.append(row)
            return rows
        return []

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """
        from random import choice
        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)
        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """
        self.table = table
        self.client = self.table.db.client
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id
        self.fields = self.table.fields
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """
        for k, v in kwargs.items():
            kwargs[k] = unicode(v)
        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key,
            wksht_id=self.worksheet_id)
        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """
        data = self.data
        data.update(kwargs)
        self.row = self.client.ssclient.UpdateRow(self.row, data)
        self.data = data

# vim: tabstop=4 expandtab autoindent shiftwidth=4 fileencoding=utf-8

import gdata.docs.service
import gdata.spreadsheet.service

# This is inconveniently different from gdata.spreadsheet.service.RequestError
# Used in test cases for example
from gdata.service import RequestError

class Client(object):
    """Unified client wraps around both docs and spreadsheet client
    """

    def __init__(self, email, password):
        """Authentication
        """

        self.ssclient = gdata.spreadsheet.service.SpreadsheetsService(email=email, password=password)
        self.docsclient = gdata.docs.service.DocsService(email=email, password=password)

        self.ssclient.ProgrammaticLogin()
        self.docsclient.ProgrammaticLogin()

class Database(object):
    """Spreadsheet-Database
    """

    def __init__(self, email, password, key=None):
        """Constructor, client is an external client we can reuse
        """

        self.client = Client(email, password)

        ## Emtpy tables; set SpreadsheetsWorksheetsFeed
        self.tables = None

        if key is None:
            ## Set by create() or open()
            self.db = None

            ## This is an identifier for our spreadsheet, so tables can be added correctly
            # Dependant on db
            self.key = None
        else:
            self.open(key)

    def __str__(self):
        return self.key

    def __unicode__(self):
        return u'%s' % self

    def refresh_tables(self):
        """Method that updates the db's tables (SpreadsheetsWorksheetsFeed)
        """

        if self.key is None:
            raise AttributeError('Can not refresh tables on uninitialised db')

        self.tables = self.client.ssclient.GetWorksheetsFeed(self.key)

    def create(self, name, data=',,,'):
        """Create a new spreadsheet-database identified by name, containing data from file ob/string
        """

        if not name or not isinstance(name, basestring):
            raise ValueError('Require textual, non-zero-length name')

        if not isinstance(data, file):
            from cStringIO import StringIO

            data = StringIO(data)

        clen = len(data.read())
        data.seek(0)

        data_source = gdata.MediaSource(file_handle=data, content_type='text/csv', content_length=clen)

        db = self.client.docsclient.Upload(data_source, name, label='Spreadsheet')

        self.db = db
        
        ## Create also the key
        id_parts = self.db.id.text.split('/')
        self.key = id_parts[-1].replace('spreadsheet%3A', '')

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

    def open(self, key):
        """Open an existing spreadsheet
        """

        ## Set our key
        self.key = key

        base_uri = r'/feeds/documents/private/full/spredsheet%%3A%s' % self.key

        ## Set also the "db"
        self.db = self.client.docsclient.GetDocumentListEntry(base_uri)

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

    def create_table(self, name, fields):
        """Create a new worksheet-table with given fields
        """

        if not self.db:
            raise AttributeError('Create a database first!')

        table = Table(self)

        table.create(name, fields)

        ## Make sure internal book-keeping is ok
        self.refresh_tables()

        return table

    def open_table(self, name):
        """Retrieve table
        """

        try:
            worksheet = [t for t in self.tables.entry if t.title.text == name][0]

            ## Fake us a Table instance from that
            table = Table(self)

            table.worksheet = worksheet

            # Create its worksheet id
            id_parts = table.worksheet.id.text.split('/')
            table.worksheet_id = id_parts[-1]

            ### TODO: Figure out how to get fields from empty (fields-only) table
            ## Cheat the fields
            #row = table.get_row(1)
            
            ## The return is a dictionary, with titles and gdata.spreadsheet.Custom instances
            #table.fields = row.row.custom.keys()

            ## Make sure this does not remain dangling anywhere
            #del row

            return table
        except IndexError:
            raise AttributeError('Table "%s" not found' % name)


class Table(object):
    """Table object
    """

    def __init__(self, db_instance):
        """Create a Table instance for db, call create() to actually create it
        """

        self.db = db_instance

        # Set some shortcuts for ease
        self.client = self.db.client

        # And this is to come
        self.worksheet = None
        self.worksheet_id = None

        # This must be populated by create, maybe in the future also by get
        self.fields = None

    def __unicode__(self):
        return u'%s' % self

    def __str__(self):
        return self.title.text

    def create(self, name, fields):
        """Create remote table
        """

        self.fields = fields

        ## Defaults
        row_count = 1
        col_count = len(fields)

        self.worksheet = self.client.ssclient.AddWorksheet(name, row_count, col_count, self.db.key)

        # Create our worksheet id
        id_parts = self.worksheet.id.text.split('/')
        self.worksheet_id = id_parts[-1]

        ## Apparently Google Spreadsheet sucks enough to use row 1 as titles
        i = 0
        for column in self.fields:
            i += 1
            self.client.ssclient.UpdateCell(1, i, column, self.db.key, self.worksheet_id)

    def insert_into(self, **kwargs):
        """Insert a new row
        Give the data as keyword arguments, eg insert_into(uid=1, username='foo')
        """

        if not self.worksheet:
            raise AttributeError('Create a worksheet first!')

        ## TODO: Figure out how to get fields out of an empty table!
        #for key in kwargs.keys():
        #    if not key in self.fields:
        #        raise KeyError('Table does not accept key "%s"' % key)

        row = Row(self)

        row.create(**kwargs)

        return row

    def get_row(self, row_num):
        """Get a row by number; 1-indexed
        """

        qry = gdata.spreadsheet.service.ListQuery()
        qry.start_index = unicode(row_num)
        qry.max_results = '1'

        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.worksheet_id, query=qry)

        if len(res.entry):
            row = Row(self)

            ## Set the real data as if we were just created
            row.row = res.entry[0]

            ## Populate the data dictionary too
            for label, bob in row.row.custom.items():
                row.data[label] = bob.text

            return row

        raise ValueError('No such row')

    def filter(self, reverse=False, orderby=None, **kwargs):
        """Retrieve certain rows by some kind of filter in ascending order
        reverse makes it descend, False here is also Google's default
        """

        qry = gdata.spreadsheet.service.ListQuery()

        ## Support only equality for now in kwargs
        # TODO: http://code.google.com/apis/spreadsheets/data/1.0/reference.html#list_Parameters
        sq = ''
        kwlist = []
        for k, v in kwargs.items():
            kwlist.append('%s == %s' % (k, v))

        sq = ' and '.join(kwlist)

        if sq:
            qry.sq = sq

        if reverse:
            qry.reverse = 'true'

        if orderby:
            qry.orderby = 'column:%s' % orderby

        res = self.client.ssclient.GetListFeed(self.db.key, wksht_id=self.worksheet_id, query=qry)

        if len(res.entry):
            rows = []
            for e in res.entry:
                row = Row(self)

                ## Set the real data as if we were just created
                row.row = e

                ## Populate the data dictionary too
                for label, bob in row.row.custom.items():
                    row.data[label] = bob.text

                rows.append(row)

            return rows

        ## To have django-like iterability even on empty results
        return []

    def get_random(self, reverse=False, orderby=None, **kwargs):
        """Get a random entry
        """

        from random import choice

        rows = self.filter(reverse=reverse, orderby=orderby, **kwargs)

        return choice(rows)


class Row(object):
    """A spreadsheet row
    """

    def __init__(self, table):
        """Construct row with references to table
        """

        self.table = table

        ## Lots of shortcutting
        # "Connection"
        self.client = self.table.db.client

        # Table object
        self.worksheet = self.table.worksheet
        self.worksheet_id = self.table.worksheet_id

        # Addressing
        self.fields = self.table.fields

        ## And something to come later
        self.row = None
        self.data = {}

    def __unicode__(self):
        return unicode(self.data)

    def __str__(self):
        return str(self.data)

    def create(self, **kwargs):
        """Inesrt data appropriately into row
        """

        for k, v in kwargs.items():
            kwargs[k] = unicode(v)

        self.row = self.client.ssclient.InsertRow(kwargs, self.table.db.key, wksht_id=self.worksheet_id)

        self.data = kwargs

    def update(self, **kwargs):
        """Update a row
        """

        ## Anything not given is defaulted to empty in Google
        data = self.data
        data.update(kwargs)

        self.row = self.client.ssclient.UpdateRow(self.row, data)

        self.data = data

# EOF

