<commit_before>"""
A wrapper around a wrapper to get Google spreadsheets look like csv.DictReader


Usage:
------
Add this import:

    from gspreadheet import GSpreadsheet

Get a spreadsheet if you know the key and worksheet:

    sheet = GSpreadsheet(key='tuTazWC8sZ_r0cddKj8qfFg', worksheet="od6")

Get a spreadsheet if you just know the url:

    sheet = GSpreadsheet(url="https://docs.google.com/spreadsheet/"
                             "ccc?key=<KEY>")

Since just knowing the url is the most common use case, specifying it as a
kwarg is optional. Just pass whatever url is in your browser as the first arg.

    sheet = GSpreadsheet("https://docs.google.com/spreadsheet/"
                         "ccc?key=<KEY>")

Google will also complain if you're anonymous. Best specify some credentials.
Get a spreadsheet as a certain user:

    sheet = GSpreadsheet(email="<EMAIL>", password="<PASSWORD>",
                         key='<KEY>', worksheet="od6")

Then iterate over each row.

    for row in sheet:
        print row
        if row['deleteme']:
            row.delete()  # delete the row from the worksheet
        row['hash'] = md5(row['name'])  # compute the hash and save it back

    data = row.copy()   # get the last row as a plain dict
    sheet.add_row(data)  # copy the last row and append it back to the sheet

Future Plans/TODOs:
-------------------
Let you address by cells

"""
__all__ = ["GSpreadsheet", "ReadOnlyException"]

from UserDict import DictMixin
import logging
import re
import os

from gdata.service import RequestError

from .auth import Auth
# from .utils import PrintFeed


logger = logging.getLogger(__name__)


class BaseException(Exception):
    pass


class ReadOnlyException(BaseException):
    """Attempted to write to a read only sheet."""
    pass


class GDataRow(DictMixin):
    # TODO use collections.MutableMapping as the docs recommend
    """A dict-like object that represents a row of a worksheet"""
    def __init__(self, entry, sheet, deferred_save=False):
        self._entry = entry
        self._data = dict([(key, value.text) for key, value in
            entry.custom.iteritems()])
        self._defer_save = deferred_save
        self._changed = False
        self._sheet = sheet

    def __getitem__(self, *args):
        return self._data.__getitem__(*args)

    def __setitem__(self, key, value):
        if self._data.get(key) != value:
            self._data[key] = value
            self._changed = True
        if not self._defer_save:
            return self.save()

    def __delitem__(self, *args):
        raise NameError("Deleting Values Not Allowed")

    def __copy__(self):
        """Get an ordinary dict of the row"""
        return self._data.copy()

    #def __del__(self):
    #    self.delete()
    #    return super(GDataRow, self).__del__()

    def keys(self):
        return self._data.keys()

    def copy(self):
        """Get an ordinary dict of the row"""
        return self.__copy__()

    def save(self):
        """Save the row back to the spreadsheet"""
        if self._sheet.readonly:
            raise ReadOnlyException
        if not self._changed:
            # nothing to save
            return
        gd_client = self._sheet.client
        assert gd_client is not None
        try:
            entry = gd_client.UpdateRow(self._entry, self._data)
        except RequestError as e:
            error_data = e.args[0]
            if error_data.status == 403:
                #  Forbidden
                raise
            if error_data.status == 409:
                # conflict
                raise
            else:
                raise
        self._entry = entry
        # reset `_changed` flag
        self._changed = False
        return entry

    def delete(self):
        """Delete the row from the spreadsheet"""
        if self._sheet.readonly:
            raise ReadOnlyException
        gd_client = self._sheet.client
        assert gd_client is not None
        return gd_client.DeleteRow(self._entry)


class GSpreadsheet(object):
    """
    A Google spreadsheet.

    Parameters:
      readonly -  default: True
    """
    client = None
    email = None
    password = None
    key = None  # your spreadsheet key
    worksheet = 'default'  # The worksheet's id (e.g. 'od6', 'od7') or 'default'
    readonly = False

    def __init__(self, url=None, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)

        # Get key from url
        if url is not None:
            try:
                self.key = re.search(r'key=([0-9a-zA-Z\-]+)', url).group(1)
            except (AttributeError, IndexError):
                # TODO raise ImproperlyConfigured
                print "! not a valid url:", url<commit_msg>update docstring to GSpreadsheet with better info<commit_after>"""
A wrapper around a wrapper to get Google spreadsheets look like csv.DictReader


Usage:
------
Add this import:

    from gspreadheet import GSpreadsheet

Get a spreadsheet if you know the key and worksheet:

    sheet = GSpreadsheet(key='<KEY>', worksheet="od6")

Get a spreadsheet if you just know the url:

    sheet = GSpreadsheet(url="https://docs.google.com/spreadsheet/"
                             "ccc?key=<KEY>")

Since just knowing the url is the most common use case, specifying it as a
kwarg is optional. Just pass whatever url is in your browser as the first arg.

    sheet = GSpreadsheet("https://docs.google.com/spreadsheet/"
                         "ccc?key=<KEY>")

Google will also complain if you're anonymous. Best specify some credentials.
Get a spreadsheet as a certain user:

    sheet = GSpreadsheet(email="<EMAIL>", password="<PASSWORD>",
                         key='<KEY>sZ_r0cddKj8qfFg', worksheet="od6")

Then iterate over each row.

    for row in sheet:
        print row
        if row['deleteme']:
            row.delete()  # delete the row from the worksheet
        row['hash'] = md5(row['name'])  # compute the hash and save it back

    data = row.copy()   # get the last row as a plain dict
    sheet.add_row(data)  # copy the last row and append it back to the sheet

Future Plans/TODOs:
-------------------
Let you address by cells

"""
__all__ = ["GSpreadsheet", "ReadOnlyException"]

from UserDict import DictMixin
import logging
import re
import os

from gdata.service import RequestError

from .auth import Auth
# from .utils import PrintFeed


logger = logging.getLogger(__name__)


class BaseException(Exception):
    pass


class ReadOnlyException(BaseException):
    """Attempted to write to a read only sheet."""
    pass


class GDataRow(DictMixin):
    # TODO use collections.MutableMapping as the docs recommend
    """A dict-like object that represents a row of a worksheet"""
    def __init__(self, entry, sheet, deferred_save=False):
        self._entry = entry
        self._data = dict([(key, value.text) for key, value in
            entry.custom.iteritems()])
        self._defer_save = deferred_save
        self._changed = False
        self._sheet = sheet

    def __getitem__(self, *args):
        return self._data.__getitem__(*args)

    def __setitem__(self, key, value):
        if self._data.get(key) != value:
            self._data[key] = value
            self._changed = True
        if not self._defer_save:
            return self.save()

    def __delitem__(self, *args):
        raise NameError("Deleting Values Not Allowed")

    def __copy__(self):
        """Get an ordinary dict of the row"""
        return self._data.copy()

    #def __del__(self):
    #    self.delete()
    #    return super(GDataRow, self).__del__()

    def keys(self):
        return self._data.keys()

    def copy(self):
        """Get an ordinary dict of the row"""
        return self.__copy__()

    def save(self):
        """Save the row back to the spreadsheet"""
        if self._sheet.readonly:
            raise ReadOnlyException
        if not self._changed:
            # nothing to save
            return
        gd_client = self._sheet.client
        assert gd_client is not None
        try:
            entry = gd_client.UpdateRow(self._entry, self._data)
        except RequestError as e:
            error_data = e.args[0]
            if error_data.status == 403:
                #  Forbidden
                raise
            if error_data.status == 409:
                # conflict
                raise
            else:
                raise
        self._entry = entry
        # reset `_changed` flag
        self._changed = False
        return entry

    def delete(self):
        """Delete the row from the spreadsheet"""
        if self._sheet.readonly:
            raise ReadOnlyException
        gd_client = self._sheet.client
        assert gd_client is not None
        return gd_client.DeleteRow(self._entry)


class GSpreadsheet(object):
    """
    Represents a Google spreadsheet an iterable of dicts.

    Example usage:

        sheet = GSpreadsheet(url)
        for row in sheet:
            name = row['name']

    Parameters:
      url       : The url to the spreadsheet. Specify this or the key.
      key       : The key to the spreadsheet. Specify this or the url.
      worksheet : The name of the worksheet, like 'od6' or 'od7'.
                  (default: 'default')
      email     : Your account email address.
      password  : <PASSWORD>
      readonly  : Whether to allow changes to the `GSpreadsheet`, which can
                  also change the original. (default: False)
    """
    # parameters
    key = None
    worksheet = 'default'
    email = None
    password = <PASSWORD>
    readonly = False

    # state
    client = None  # save auth
    is_authed = False  # store whether client is authenticated or anonymous

    def __init__(self, url=None, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)

        # Get key from url
        if url is not None:
            try:
                self.key = re.search(r'key=([0-9a-zA-Z\-]+)', url).group(1)
            except (AttributeError, IndexError):
                # TODO raise ImproperlyConfigured
                print "! not a valid url:", url
{'fraction_non_alphanumeric': 0.09468731621250735, 'fraction_numerical': 0.0034306998627720055, 'mean_word_length': 2.1335995085995085, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 16, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '747800', 'n_tokens_mistral': 2934, 'n_tokens_neox': 2799, 'n_words': 976}