Skip to content

Commit

Permalink
Merge pull request #1278 from burnash/deprecation/colors
Browse files Browse the repository at this point in the history
Add deprecation warnings for colors
  • Loading branch information
alifeee authored Aug 18, 2023
2 parents c20e57f + 98ee6d3 commit 70783f3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 1 deletion.
84 changes: 84 additions & 0 deletions gspread/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from functools import wraps
from itertools import chain
from math import inf
from typing import Mapping
from urllib.parse import quote as uquote

from google.auth.credentials import Credentials as Credentials
Expand Down Expand Up @@ -770,6 +771,89 @@ def combined_merge_values(worksheet_metadata, values):
return new_values


def convert_hex_to_colors_dict(hex_color: str) -> Mapping[str, float]:
"""Convert a hex color code to RGB color values.
:param str hex_color: Hex color code in the format "#RRGGBB".
:returns: Dict containing the color's red, green and blue values between 0 and 1.
:rtype: dict
:raises:
ValueError: If the input hex string is not in the correct format or length.
Examples:
>>> convert_hex_to_colors_dict("#3300CC")
{'red': 0.2, 'green': 0.0, 'blue': 0.8}
>>> convert_hex_to_colors_dict("#30C")
{'red': 0.2, 'green': 0.0, 'blue': 0.8}
"""
hex_color = hex_color.lstrip("#")

# Google API ColorStyle Reference:
# "The alpha value in the Color object isn't generally supported."
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#colorstyle
if len(hex_color) == 8:
hex_color = hex_color[:-2]

# Expand 3 character hex.
if len(hex_color) == 3:
hex_color = "".join([char * 2 for char in hex_color])

if len(hex_color) != 6:
raise ValueError("Hex color code must be in the format '#RRGGBB'.")

try:
rgb_color = {
"red": int(hex_color[0:2], 16) / 255,
"green": int(hex_color[2:4], 16) / 255,
"blue": int(hex_color[4:6], 16) / 255,
}

return rgb_color
except ValueError as ex:
raise ValueError(f"Invalid character in hex color string: #{hex_color}") from ex


def convert_colors_to_hex_value(
red: float = 0.0, green: float = 0.0, blue: float = 0.0
) -> str:
"""Convert RGB color values to a hex color code.
:param float red: Red color value (0-1).
:param float green: Green color value (0-1).
:param float blue: Blue color value (0-1).
:returns: Hex color code in the format "#RRGGBB".
:rtype: str
:raises:
ValueError: If any color value is out of the accepted range (0-1).
Example:
>>> convert_colors_to_hex_value(0.2, 0, 0.8)
'#3300CC'
>>> convert_colors_to_hex_value(green=0.5)
'#008000'
"""

def to_hex(value: float) -> str:
"""
Convert an integer to a 2-digit uppercase hex string.
"""
hex_value = hex(round(value * 255))[2:]
return hex_value.upper().zfill(2)

if any(value < 0 or value > 1 for value in (red, green, blue)):
raise ValueError("Color value out of accepted range 0-1.")

return f"#{to_hex(red)}{to_hex(green)}{to_hex(blue)}"


if __name__ == "__main__":
import doctest

Expand Down
32 changes: 31 additions & 1 deletion gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import warnings
from typing import Union

from .cell import Cell
from .exceptions import GSpreadException
Expand All @@ -26,6 +27,8 @@
cast_to_a1_notation,
cell_list_to_rect,
combined_merge_values,
convert_colors_to_hex_value,
convert_hex_to_colors_dict,
fill_gaps,
filter_dict_values,
finditem,
Expand Down Expand Up @@ -204,8 +207,23 @@ def tab_color(self):
"""Tab color style. Dict with RGB color values.
If any of R, G, B are 0, they will not be present in the dict.
"""
warnings.warn(
DEPRECATION_WARNING_TEMPLATE.format(
v_deprecated="6.0.0",
msg_deprecated="""color format will change to hex format "#RRGGBB".
To suppress warning, use "get_tab_color()" and convert back to dict format, use gspread.utils.convert_hex_to_colors_dict.
However, we recommend changing your code to use hex format.""",
)
)
return self._properties.get("tabColorStyle", {}).get("rgbColor", None)

def get_tab_color(self) -> Union[str, None]:
"""Tab color style in hex format. String."""
tab_color = self._properties.get("tabColorStyle", {}).get("rgbColor", None)
if tab_color is None:
return None
return convert_colors_to_hex_value(**tab_color)

def _get_sheet_property(self, property, default_value):
"""return a property of this worksheet or default value if not found"""
meta = self.spreadsheet.fetch_sheet_metadata()
Expand Down Expand Up @@ -1465,12 +1483,24 @@ def update_title(self, title):
self._properties["title"] = title
return response

def update_tab_color(self, color: dict):
def update_tab_color(self, color: Union[dict, str]):
"""Changes the worksheet's tab color.
Use clear_tab_color() to remove the color.
:param dict color: The red, green and blue values of the color, between 0 and 1.
"""

if isinstance(color, str):
color = convert_hex_to_colors_dict(color)
else:
warnings.warn(
message=DEPRECATION_WARNING_TEMPLATE.format(
v_deprecated="6.0.0",
msg_deprecated="""color format will change to hex format "#RRGGBB".
To suppress this warning, first convert color to hex with "gspread.utils.convert_colors_to_hex_value(color)""",
)
)

red, green, blue = color["red"], color["green"], color["blue"]
body = {
"requests": [
Expand Down
38 changes: 38 additions & 0 deletions tests/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,44 @@ def test_combine_merge_values(self):

self.assertEqual(actual_combine, expected_combine)

def test_convert_colors_to_hex_value(self):
color = {"red": 1, "green": 0.5, "blue": 0}
expected_hex = "#FF8000"

# successful convert from colors
hex = utils.convert_colors_to_hex_value(**color)
self.assertEqual(hex, expected_hex)

# successful convert from partial input
hex = utils.convert_colors_to_hex_value(green=1)
self.assertEqual(hex, "#00FF00")

# throw ValueError on color values out of range (0-1)
with self.assertRaises(ValueError):
utils.convert_colors_to_hex_value(1.23, 0, -50)

def test_convert_hex_to_color(self):
hexcolor = "#FF7F00"
expected_color = {"red": 1, "green": 0.49803922, "blue": 0}

# successful convert from hex to color
rgbcolor = utils.convert_hex_to_colors_dict(hexcolor)
for key, rgbvalue in rgbcolor.items():
self.assertAlmostEqual(rgbvalue, expected_color[key])

# successful ignore alpha
rgbcolor = utils.convert_hex_to_colors_dict(f"{hexcolor}42")
for key, rgbvalue in rgbcolor.items():
self.assertAlmostEqual(rgbvalue, expected_color[key])

# raise ValueError on invalid hex length
with self.assertRaises(ValueError):
utils.convert_hex_to_colors_dict("123456abcdef")

# raise ValueError on invalid hex characters
with self.assertRaises(ValueError):
utils.convert_hex_to_colors_dict("axbcde")

def test_fill_gaps(self):
"""test fill_gaps function"""
matrix = [
Expand Down
4 changes: 4 additions & 0 deletions tests/worksheet_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def test_update_tab_color(self):
.get("rgbColor", None)
)
color_param_before = self.sheet.tab_color
color_hex_before = self.sheet.get_tab_color()

self.sheet.update_tab_color(pink_color)

Expand All @@ -249,14 +250,17 @@ def test_update_tab_color(self):
.get("rgbColor", None)
)
color_param_after = self.sheet.tab_color
color_hex_after = self.sheet.get_tab_color()

# params are set to whatever the user sets them to
# google returns the closest 8-bit value
# so these are different
self.assertEqual(color_before, None)
self.assertEqual(color_param_before, None)
self.assertEqual(color_hex_before, None)
self.assertEqual(color_after, pink_color_from_google)
self.assertEqual(color_param_after, pink_color)
self.assertEqual(color_hex_after, "#FF0080")

@pytest.mark.vcr()
def test_clear_tab_color(self):
Expand Down

0 comments on commit 70783f3

Please sign in to comment.