Skip to content

Commit

Permalink
Merge branch 'staging'
Browse files Browse the repository at this point in the history
  • Loading branch information
Nithin Murali committed Feb 5, 2021
2 parents 5c1c3c0 + 65376f6 commit a03069d
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 29 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ Features:
* Work with range of cells easily with DataRange and Gridrange
* Data validation support. checkboxes, drop-downs etc.
* Conditional formatting support
* Offline calls batching support
* get multiple ranges with get_values_batch
* get multiple ranges with get_values_batch and update wit update_values_batch

## Updates
* version [2.0.4](https://github.com/nithinmurali/pygsheets/releases/tag/2.0.4) released
* version [2.0.5](https://github.com/nithinmurali/pygsheets/releases/tag/2.0.5) released

## Installation

Expand All @@ -38,11 +37,11 @@ pip install https://github.com/nithinmurali/pygsheets/archive/staging.zip

```

If you are installing from github please see the docs [here](https://pygsheets.readthedocs.io/en/latest/).
If you are installing from github please see the docs [here](https://pygsheets.readthedocs.io/en/staging/).

## Basic Usage

Basic features are shown here, for complete set of features see the full documentation [here](http://pygsheets.readthedocs.io/en/stable/).
Basic features are shown here, for complete set of features see the full documentation [here](http://pygsheets.readthedocs.io/en/staging/).

1. Obtain OAuth2 credentials from Google Developers Console for __google spreadsheet api__ and __drive api__ and save the file as `client_secret.json` in same directory as project. [read more here.](https://pygsheets.readthedocs.io/en/latest/authorization.html)

Expand Down Expand Up @@ -108,7 +107,7 @@ sh = gc.open("pygsheetTest")
sht1 = gc.open_by_key('1mwA-NmvjDqd3A65c8hsxOpqdfdggPR0fgfg5nXRKScZAuM')

# create a spreadsheet in a folder (by id)
sht2 = gc.create("new sheet", folder="adF345vfvcvby67ddfc")
sht2 = gc.create("new sheet", folder_name="my worksheets")

# open enable TeamDrive support
gc.drive.enable_team_drive("Dqd3A65c8hsxOpqdfdggPR0fgfg")
Expand Down Expand Up @@ -175,6 +174,9 @@ cell_matrix = wks.get_all_values(returnas='matrix')
# update a range of values with a cell list or matrix
wks.update_values(crange='A1:E10', values=values_mat)

# update multiple ranges with bath update
wks.update_values_batch(['A1:A2', 'B1:B2'], [[[1],[2]], [[3],[4]]])

# Insert 2 rows after 20th row and fill with values
wks.insert_rows(row=20, number=2, values=values_list)

Expand Down
2 changes: 1 addition & 1 deletion pygsheets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

__version__ = '2.0.4'
__version__ = '2.0.5'
__author__ = 'Nithin Murali'

from pygsheets.authorization import authorize
Expand Down
8 changes: 8 additions & 0 deletions pygsheets/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ def _calculate_label(self):
def _calculate_addresses(self, label):
""" update values from label """
self._start, self._end = Address(None, True), Address(None, True)

if len(label.split('!')) > 1:
self.worksheet_title = label.split('!')[0]
rem = label.split('!')[1]
Expand All @@ -437,8 +438,15 @@ def _calculate_addresses(self, label):
self._end = Address(rem.split(":")[1], allow_non_single=True)
else:
self._start = Address(rem, allow_non_single=True)
elif self._worksheet:
if ":" in label:
self._start = Address(label.split(":")[0], allow_non_single=True)
self._end = Address(label.split(":")[1], allow_non_single=True)
else:
self._start = Address(label, allow_non_single=True)
else:
pass

self._apply_index_constraints()

def to_json(self):
Expand Down
1 change: 1 addition & 0 deletions pygsheets/datarange.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ def set_json(self, api_obj):
self.description = api_obj.get('description', '')
self.editors = api_obj.get('editors', {})
self.warningOnly = api_obj.get('warningOnly', False)
self.requestingUserCanEdit = api_obj.get('requestingUserCanEdit', None)

def to_json(self):
api_obj = {
Expand Down
5 changes: 3 additions & 2 deletions pygsheets/drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,14 @@ def export(self, sheet, file_format, path='', filename=''):

import io
file_name = str(sheet.id or tmp) + file_extension if filename is None else filename + file_extension
fh = io.FileIO(path + file_name, 'wb')
file_path = os.path.join(path, file_name)
fh = io.FileIO(file_path, 'wb')
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
status, done = downloader.next_chunk()
# logging.info('Download progress: %d%%.', int(status.progress() * 100)) TODO fix this
logging.info('Download finished. File saved in %s.', path + file_name)
logging.info('Download finished. File saved in %s.', file_path)

if tmp is not None:
sheet.index = tmp + 1
Expand Down
11 changes: 8 additions & 3 deletions pygsheets/sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,14 @@ def values_batch_update(self, spreadsheet_id, body, parse=True):
valueInputOption=cformat)
self._execute_requests(request)

# def values_batch_update_by_data_filter(self):
# pass
def values_batch_update_by_data_filter(self, spreadsheet_id, data, parse=True):
body = {
"data": data,
"valueInputOption": 'USER_ENTERED' if parse else 'RAW',
"includeValuesInResponse": False
}
request = self.service.spreadsheets().values().batchUpdateByDataFilter(spreadsheetId=spreadsheet_id, body=body)
self._execute_requests(request)

# def values_clear(self):
# pass
Expand Down Expand Up @@ -473,7 +479,6 @@ def developer_metadata_update(self, spreadsheet_id, key, value, location, data_f
}
self.batch_update(spreadsheet_id, [request])


# TODO: implement as base for batch update.
# def values_update(self):
# pass
Expand Down
8 changes: 7 additions & 1 deletion pygsheets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ def numericise_all(input, empty_value=''):


def is_number(n):
return str(n).replace('.', '', 1).isdigit()
if '_' in str(n):
return False
try:
float(n)
except ValueError:
return False
return True


def format_addr(addr, output='flip'):
Expand Down
45 changes: 41 additions & 4 deletions pygsheets/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ def update_value(self, addr, val, parse=None):

@batchable
def update_values(self, crange=None, values=None, cell_list=None, extend=False, majordim='ROWS', parse=None):
"""Updates cell values in batch, it can take either a cell list or a range and values. cell list is only efficient
"""Updates a range cell values, it can take either a cell list or a range and its values. cell list is only efficient
for small lists. This will only update the cell values not other properties.
:param cell_list: List of a :class:`Cell` objects to update with their values. If you pass a matrix to this,\
Expand Down Expand Up @@ -698,6 +698,38 @@ def update_values(self, crange=None, values=None, cell_list=None, extend=False,
parse = parse if parse is not None else self.spreadsheet.default_parse
self.client.sheet.values_batch_update(self.spreadsheet.id, body, parse)

@batchable
def update_values_batch(self, ranges, values, majordim='ROWS', parse=None):
"""
update multiple ranges of values in a single call.
:param ranges: list of addresses of the range. can be GridRange, label, tuple, etc
:param values: list of values corresponding to ranges, should be list of matrices
:param majordim: major dimension of values provided. 'ROWS' or 'COLUMNS'
:param parse: if the values should be as if the user typed them into the UI else its stored as is. Default is
spreadsheet.default_parse
Example:
>>> wks.update_values_batch(['A1:A2', 'B1:B2'], [[[1],[2]], [[3],[4]]])
>>> wks.get_values_batch(['A1:A2', 'B1:B2'])
[[['1'], ['2']], [['3'], ['4']]]
>>> wks.update_values_batch([((1,1), (2,1)), 'B1:B2'], [[[1,2]], [[3,4]]], 'COLUMNS')
>>> wks.get_values_batch(['A1:A2', 'B1:B2'])
[[['1'], ['2']], [['3'], ['4']]]
"""
ranges = [GridRange.create(x, self).label for x in ranges]
if not isinstance(values, list):
raise InvalidArgumentValue('values is not a list')
if len(ranges) != len(values):
raise InvalidArgumentValue('number of ranges and values should match')
# TODO update to enable filters
data = [
{'dataFilter': {'a1Range': x[0]}, 'values': x[1], 'majorDimension': majordim} for x in zip(ranges, values)
]
self.client.sheet.values_batch_update_by_data_filter(self.spreadsheet.id, data, parse)

@batchable
def update_cells_prop(self, **kwargs):
warnings.warn(_warning_mesage.format('method', 'update_cells'), category=DeprecationWarning)
Expand Down Expand Up @@ -1416,7 +1448,7 @@ def get_as_df(self, has_header=True, index_column=None, start=None, end=None, nu
"""
if not self._linked: return False

include_tailing_empty = True if has_header else kwargs.get('include_tailing_empty', False)
include_tailing_empty = kwargs.get('include_tailing_empty', False)
include_tailing_empty_rows = kwargs.get('include_tailing_empty_rows', False)
index_column = index_column or kwargs.get('index_colum', None)

Expand All @@ -1431,13 +1463,18 @@ def get_as_df(self, has_header=True, index_column=None, start=None, end=None, nu
else:
values = self.get_all_values(returnas='matrix', include_tailing_empty=include_tailing_empty,
value_render=value_render, include_tailing_empty_rows=include_tailing_empty_rows)

max_row = max(len(row) for row in values)
values = [row + [empty_value] * (max_row - len(row)) for row in values]

if numerize:
values = [numericise_all(row, empty_value) for row in values]

if has_header:
keys = values[0]
values = [row[:len(keys)] for row in values[1:]]
values = values[1:]
if any(key == '' for key in keys):
warnings.warn('At least one column name in the data frame is an empty string. If this is a concern, please specify include_tailing_empty=False and/or ensure that each column containing data has a name.')
df = pd.DataFrame(values, columns=keys)
else:
df = pd.DataFrame(values)
Expand Down Expand Up @@ -1648,7 +1685,7 @@ def add_conditional_formatting(self, start, end, condition_type, format, conditi
self.client.sheet.batch_update(self.spreadsheet.id, request)

@batchable
def merge_cells(self, start, end, merge_type='MERGE_ALL', grange=None):
def merge_cells(self, start=None, end=None, merge_type='MERGE_ALL', grange=None):
"""
Merge cells in range
Expand Down
14 changes: 3 additions & 11 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,7 @@ def read(filename):


description = 'Google Spreadsheets Python API v4'

long_description = """
{index}
License
-------
MIT
Download
========
"""
long_description = read('README.md')

version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
read('pygsheets/__init__.py'), re.MULTILINE).group(1)
Expand All @@ -47,6 +37,8 @@ def read(filename):
name='pygsheets',
packages=['pygsheets'],
description=description,
long_description=long_description,
long_description_content_type='text/markdown',
version=version,
author='Nithin Murali',
author_email='imnmfotmal@gmail.com',
Expand Down
19 changes: 18 additions & 1 deletion tests/online_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pygsheets.exceptions import CannotRemoveOwnerError
from pygsheets.custom_types import ExportType
from pygsheets import Cell
import pygsheets.utils as utils
from pygsheets.custom_types import HorizontalAlignment, VerticalAlignment

try:
Expand Down Expand Up @@ -406,7 +407,7 @@ def test_iter(self):
def test_getitem(self):
self.worksheet.update_row(1, [1, 2, 3, 4, 5])
row = self.worksheet[1]
assert len(row) == self.worksheet.cols + 1
assert len(row) == self.worksheet.cols
assert row[0] == str(1) # TODO first index is dummy

def test_clear(self):
Expand Down Expand Up @@ -716,6 +717,7 @@ def test_developer_metadata(self):
final_meta = self.worksheet.get_developer_metadata()
assert len(final_meta) == len(old_meta)


# @pytest.mark.skip()
class TestDataRange(object):
def setup_class(self):
Expand Down Expand Up @@ -868,3 +870,18 @@ def test_start_ub_end(self):
assert self.grange.start == pygsheets.Address('1', True)
assert self.grange.end == pygsheets.Address('4', True)
assert self.grange.label == self.worksheet.title + '!' + '1' + ':' + '4'


class TestUtils(object):

def test_is_number(self):
assert utils.is_number("1")
assert utils.is_number("-1")
assert utils.is_number("1.234")
assert utils.is_number("-1.234324")
assert utils.is_number("+1.345")
assert not utils.is_number("yuy")
assert not utils.is_number("12yuy34")
assert not utils.is_number("12.3.4")
assert not utils.is_number("12?34")
assert not utils.is_number("1.345_34")

0 comments on commit a03069d

Please sign in to comment.