Skip to content

Commit

Permalink
Add strict type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
joowani committed Feb 10, 2021
1 parent 0f9cd03 commit f5a966f
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 97 deletions.
48 changes: 24 additions & 24 deletions colorpedia/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
from distutils.util import strtobool
from json import dumps as json_dumps
from typing import Callable, Iterable, Optional
from typing import Any, Callable, Dict, Iterable, Optional

from fire import Fire
from pkg_resources import get_distribution
Expand Down Expand Up @@ -48,15 +48,15 @@ def prompt_user(question: str) -> bool:
print('Please respond with "y" or "n"\n')


def print_colors(config: Config, colors: Iterable[Color]):
def print_colors(config: Config, colors: Iterable[Color]) -> None:
if config.always_output_json:
print(json_dumps([c.get_dict(config.json_keys) for c in colors]))
else:
for color in colors:
print(format_list_view(config, color))


def print_color(config: Config, color: Color):
def print_color(config: Config, color: Color) -> None:
if config.default_shades_count:
print_colors(config, color.get_shades(config.default_shades_count))

Expand All @@ -66,11 +66,11 @@ def print_color(config: Config, color: Color):
print(format_get_view(config, color))


def print_config(config: Config, sort: bool = True, indent: int = 2):
def print_config(config: Config, sort: bool = True, indent: int = 2) -> None:
print(json_dumps(config.dump(), sort_keys=sort, indent=indent))


def get_version(json: Optional[bool] = None):
def get_version(json: Optional[bool] = None) -> None:
"""Display Colorpedia CLI version.
Colorpedia follows Semantic Versioning 2.0.0 (semver.org).
Expand All @@ -82,7 +82,7 @@ def get_version(json: Optional[bool] = None):
print({"version": version} if json else version)


def init_config(force: bool = False):
def init_config(force: bool = False) -> None:
"""Initialize or reset Colorpedia configuration.
Configuration file is located at ~/.config/colorpedia/config.json.
Expand All @@ -97,7 +97,7 @@ def init_config(force: bool = False):
print_config(init_config_file())


def show_config(sort: bool = True, indent: int = 2):
def show_config(sort: bool = True, indent: int = 2) -> None:
"""Display Colorpedia configuration.
Configuration file is located at ~/.config/colorpedia/config.json.
Expand All @@ -114,7 +114,7 @@ def show_config(sort: bool = True, indent: int = 2):
print_config(config, sort=sort, indent=indent)


def edit_config(editor: str = None):
def edit_config(editor: Optional[str] = None) -> None:
"""Edit Colorpedia configuration using a text editor.
If --editor flag is not specified, $VISUAL and $EDITOR environment
Expand All @@ -132,12 +132,12 @@ def edit_config(editor: str = None):
print_config(config)


def get_palette_func(name: str) -> Callable:
def get_palette_func(name: str) -> Callable[..., None]:
def function(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
config = load_config_file()
config.set_flags(
json=validate_boolean_flag(json),
Expand All @@ -157,13 +157,13 @@ def function(
return function


def get_color_by_name_func(name: str, hex_code: str) -> Callable:
def get_color_by_name_func(name: str, hex_code: str) -> Callable[..., None]:
def function(
shades: int = 0,
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
config = load_config_file()
config.set_flags(
shades=validate_shades_count(shades),
Expand Down Expand Up @@ -194,7 +194,7 @@ def get_color_by_cmyk(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
"""Look up colors by CMYK (Cyan Magenta Yellow Black) values.
CMYK is a subtractive color model used in color printing. It refers
Expand Down Expand Up @@ -236,7 +236,7 @@ def get_color_by_hex(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
"""Look up colors by hexadecimal (web) code.
The hexadecimal code must be specified without the hash (#) prefix
Expand Down Expand Up @@ -273,7 +273,7 @@ def get_color_by_hsl(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
"""Look up colors by HSL (Hue Saturation Lightness) values.
Hue is a point on the color wheel from 0 to 360 where 0 is red,
Expand Down Expand Up @@ -316,7 +316,7 @@ def get_color_by_hsv(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
"""Look up colors by HSV (Hue Saturation Brightness/Value) values.
Hue is a point on the color wheel from 0 to 360 where 0 is red,
Expand Down Expand Up @@ -360,7 +360,7 @@ def get_color_by_rgb(
json: Optional[bool] = None,
all: bool = False,
units: Optional[bool] = None,
):
) -> None:
"""Look up colors by RGB (Red Green Blue) values.
RGB is an additive color model where red, green, and blue lights are
Expand Down Expand Up @@ -394,7 +394,7 @@ def get_color_by_rgb(
print_color(config, Color(r, g, b))


class MainCommand(dict):
class MainCommand(Dict[str, Any]):
"""Colorpedia CLI.
Look up colors using various models:
Expand Down Expand Up @@ -427,19 +427,19 @@ class MainCommand(dict):
"""


class NameSubCommand(dict):
class NameSubCommand(Dict[str, Any]):
"""Look up colors by CSS3 name."""


class PaletteSubCommand(dict):
class PaletteSubCommand(Dict[str, Any]):
"""Look up color palettes."""


class ConfigSubCommand(dict):
class ConfigSubCommand(Dict[str, Any]):
"""Manage CLI configuration."""


def entry_point(name: str):
def entry_point(name: str) -> None:
try:
# We need this to get colors working on windows.
os.system("")
Expand Down Expand Up @@ -479,9 +479,9 @@ def entry_point(name: str):
sys.exit(1)


def entry_point_color():
def entry_point_color() -> None:
entry_point("color")


def entry_point_colorpedia():
def entry_point_colorpedia() -> None:
entry_point("colorpedia")
6 changes: 3 additions & 3 deletions colorpedia/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Color:
g: int
b: int

def __post_init__(self):
def __post_init__(self) -> None:
self.rgb = (self.r, self.g, self.b)
self.names, self.is_name_exact = rgb_to_names(*self.rgb)
self.name = "/".join(self.names)
Expand All @@ -30,8 +30,8 @@ def get_shades(self, size: int) -> Iterable["Color"]:
h, s, l = self.hsl
return (Color(*rgb) for rgb in hsl_to_rgb_shades(h, s, l, size))

def get_dict(self, keys: Union[FrozenSet, Set]) -> Dict[str, Any]:
result = {}
def get_dict(self, keys: Union[FrozenSet[str], Set[str]]) -> Dict[str, Any]:
result: Dict[str, Any] = {}
if "hex" in keys:
result["hex"] = self.hex
if "rgb" in keys:
Expand Down
12 changes: 6 additions & 6 deletions colorpedia/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ def update(self, data: Dict[str, Any]) -> None:
if type(data) != dict:
raise ConfigFileError("Bad JSON: expecting an object")

def validate_string(name):
def validate_string(name: str) -> None:
if type(getattr(self, name)) != str:
raise ConfigValueError(name, "a string")

def validate_boolean(name):
def validate_boolean(name: str) -> None:
if type(getattr(self, name)) != bool:
raise ConfigValueError(name, "true or false")

def validate_number(name):
def validate_number(name: str) -> None:
value = getattr(self, name)
if not (type(value) == int and 1 <= value <= 100):
raise ConfigValueError(name, "an integer between 1 and 100")

def validate_view_keys(name):
def validate_view_keys(name: str) -> None:
keys = getattr(self, name)
if not (
type(keys) in (list, frozenset)
Expand All @@ -66,7 +66,7 @@ def validate_view_keys(name):
name, f"non-empty array of strings in {list(VIEW_KEYS)}"
)

def validate_json_keys(name):
def validate_json_keys(name: str) -> None:
keys = getattr(self, name)
if not (
type(keys) in (list, frozenset)
Expand Down Expand Up @@ -179,7 +179,7 @@ def init_config_file() -> Config: # pragma: no cover
return config


def edit_config_file(editor: str = None): # pragma: no cover
def edit_config_file(editor: Optional[str] = None) -> Config: # pragma: no cover
editor = editor or os.environ.get("VISUAL") or os.environ.get("EDITOR")
if editor:
editor = shlex.split(editor)[0] # Prevent arbitrary code execution
Expand Down
2 changes: 1 addition & 1 deletion colorpedia/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def rgb_to_names(r: int, g: int, b: int) -> Tuple[Tuple[str, ...], bool]:
return HEX_CODE_TO_NAMES[f"{r:02x}{g:02x}{b:02x}".upper()], True
except KeyError:
minimum_diff = maxsize
nearest_names: Tuple = tuple()
nearest_names: Tuple[str, ...] = tuple()

for hex_code, names in HEX_CODE_TO_NAMES.items():
_r, _g, _b = hex_to_rgb(hex_code)
Expand Down
7 changes: 5 additions & 2 deletions colorpedia/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from typing import Optional


class ColorpediaError(Exception):
"""Generic Colorpedia exception."""


class ConfigFileError(ColorpediaError):
"""Configuration file cannot be accessed, created or updated."""

def __init__(self, msg, err: Exception = None):
def __init__(self, msg: str, err: Optional[Exception] = None):
if isinstance(err, OSError):
msg = f"{msg}: {err.strerror} (errno: {err.errno})"
elif err:
Expand All @@ -32,5 +35,5 @@ def __init__(self, key: str, exp: str):
class InputValueError(ColorpediaError):
"""Invalid input value from user."""

def __init__(self, name: str, exp):
def __init__(self, name: str, exp: str):
super(InputValueError, self).__init__(f"Bad {name} (expecting {exp})")
10 changes: 5 additions & 5 deletions colorpedia/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from colorpedia.hexcodes import HEX_REGEX


def validate_indent_width(value: int):
def validate_indent_width(value: int) -> int:
if type(value) == int and 0 <= value <= 8:
return value
raise InputValueError("indent width", "an integer between 0 and 8")


def validate_boolean_flag(value: Optional[bool]):
def validate_boolean_flag(value: Optional[bool]) -> Optional[bool]:
if value is True or value is False or value is None:
return value
raise InputValueError("boolean flag", "True, False or no value")
Expand All @@ -23,7 +23,7 @@ def validate_shades_count(value: Union[bool, int]) -> int:
raise InputValueError("shades count", "an integer between 0 and 100")


def validate_editor(value: Optional[str]):
def validate_editor(value: Optional[str]) -> Optional[str]:
if value is None or (type(value) == str and len(value) > 0 and " " not in value):
return value
raise InputValueError("editor", "a shell-executable command without whitespaces")
Expand All @@ -41,13 +41,13 @@ def normalize_degree_angle(value: Union[float, int]) -> float:
raise InputValueError("degree angle", "a float between 0.0 and 360.0")


def normalize_hex_code(value: str) -> str:
def normalize_hex_code(value: Union[int, str]) -> str:
if type(value) == int:
if value == 0:
return "000000"
else:
value = str(value)
if type(value) == str and re.search(HEX_REGEX, value):
if isinstance(value, str) and re.search(HEX_REGEX, value):
return value if len(value) == 6 else "".join(c * 2 for c in value)
raise InputValueError("hex code", f"a string matching {HEX_REGEX}")

Expand Down
12 changes: 6 additions & 6 deletions tests/test_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from colorpedia.color import Color


def test_color_black():
def test_color_black() -> None:
color = Color(0, 0, 0)
assert color.name == "black"
assert color.names == ("black",)
Expand All @@ -15,7 +15,7 @@ def test_color_black():
assert color.hsv == (0, 0, 0)


def test_color_dimgray():
def test_color_dimgray() -> None:
color = Color(100, 100, 100)
assert color.name == "dimgray/dimgrey"
assert color.names == ("dimgray", "dimgrey")
Expand All @@ -27,7 +27,7 @@ def test_color_dimgray():
assert color.hsv == (0.0, 0.0, 0.39215686274509803)


def test_color_darkslateblue():
def test_color_darkslateblue() -> None:
color = Color(50, 100, 150)
assert color.name == "darkslateblue"
assert color.names == ("darkslateblue",)
Expand All @@ -52,7 +52,7 @@ def test_color_darkslateblue():
)


def test_color_white():
def test_color_white() -> None:
color = Color(255, 255, 255)
assert color.name == "white"
assert color.names == ("white",)
Expand All @@ -68,7 +68,7 @@ def test_color_white():
("r", "g", "b", "shades_count"),
((0, 0, 0, 1), (30, 40, 50, 1), (30, 40, 50, 5), (255, 255, 255, 5)),
)
def test_color_get_shades(r, g, b, shades_count):
def test_color_get_shades(r: int, g: int, b: int, shades_count: int) -> None:
color = Color(r, g, b)
colors = set(color.get_shades(shades_count))
assert len(colors) == shades_count
Expand All @@ -77,7 +77,7 @@ def test_color_get_shades(r, g, b, shades_count):


@pytest.mark.parametrize(("r", "g", "b"), ((0, 0, 0), (10, 20, 30), (255, 255, 255)))
def test_color_to_dict(r, g, b):
def test_color_to_dict(r: int, g: int, b: int) -> None:
color = Color(r, g, b)

assert color.get_dict(set()) == {}
Expand Down
Loading

0 comments on commit f5a966f

Please sign in to comment.