From 8a434d95b54aa13af117a78310ccc97be65bdb03 Mon Sep 17 00:00:00 2001 From: Jon Grace-Cox <30441316+jongracecox@users.noreply.github.com> Date: Mon, 8 Aug 2022 23:19:50 -0400 Subject: [PATCH] Drop <3.7 support and add type hints (#59) - Drop support for Python <3.7 - Bump the pre-commit version used in CI - Add type hinting and docstrings - Add mypy to pre-commit config - Fix typing issues - Update colors module - Update colors module to use uppercase hex codes - Add `__lt__` to allow sorting colors by name - Fix `build_examples.py` to work with color Enum - Update example badges in `README.md` - Fix typing issues in server - Update travis links in `README.md` - Fix PyPi deployment bug (#60) --- .pre-commit-config.yaml | 6 +- .travis.yml | 10 +- README.md | 48 +++---- anybadge/badge.py | 212 ++++++++++++++++++++++------- anybadge/colors.py | 15 +- anybadge/config.py | 16 ++- anybadge/helpers.py | 2 +- anybadge/server/cli.py | 43 +++--- anybadge/server/config.py | 8 +- anybadge/server/request_handler.py | 13 +- anybadge/styles.py | 2 +- anybadge/templates/__init__.py | 7 +- build_examples.py | 19 ++- examples/color_aqua.svg | 16 +-- examples/color_black.svg | 16 +-- examples/color_blue.svg | 16 +-- examples/color_bright_red.svg | 16 +-- examples/color_bright_yellow.svg | 16 +-- examples/color_fuchsia.svg | 16 +-- examples/color_gray.svg | 16 +-- examples/color_green.svg | 16 +-- examples/color_light_grey.svg | 16 +-- examples/color_lime.svg | 16 +-- examples/color_maroon.svg | 16 +-- examples/color_navy.svg | 16 +-- examples/color_olive.svg | 16 +-- examples/color_orange.svg | 16 +-- examples/color_purple.svg | 16 +-- examples/color_red.svg | 16 +-- examples/color_silver.svg | 16 +-- examples/color_teal.svg | 16 +-- examples/color_white.svg | 16 +-- examples/color_yellow.svg | 16 +-- examples/color_yellow_green.svg | 16 +-- setup.py | 2 +- tests/test_anybadge.py | 4 +- 36 files changed, 430 insertions(+), 313 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 364f189..2106dff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,4 +13,8 @@ repos: - repo: https://github.com/psf/black rev: 22.6.0 hooks: - - id: black + - id: black +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.971 + hooks: + - id: mypy diff --git a/.travis.yml b/.travis.yml index 9e0dfef..cffd141 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,6 @@ dist: xenial language: python jobs: include: - - python: '3.4' - env: RUN_PRE_COMMIT=0 - - python: '3.5' - env: RUN_PRE_COMMIT=0 - - python: '3.6' - env: RUN_PRE_COMMIT=0 - python: '3.7' env: RUN_PRE_COMMIT=0 - python: '3.8' @@ -18,13 +12,13 @@ install: - pip install -U setuptools pip -r build-requirements.txt script: - if [[ $RUN_PRE_COMMIT = 1 ]]; then - pip install -U pre-commit==2.12.1 && + pip install -U pre-commit==2.20.0 && pre-commit install && pre-commit run --all; fi - pytest --doctest-modules --cov=anybadge --cov-report html:htmlcov anybadge tests before_deploy: -- sed -i "s/^version = .*/version = __version__ = \"$TRAVIS_TAG\"/" anybadge.py +- sed -i "s/^version = .*/version = __version__ = \"$TRAVIS_TAG\"/" anybadge/__init__.py deploy: - provider: pypi skip_cleanup: true diff --git a/README.md b/README.md index 7c298a2..c36acab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Python project for generating badges for your projects [](https://pypi.org/project/anybadge) -[](https://travis-ci.org/jongracecox/anybadge) +[](https://app.travis-ci.com/github/jongracecox/anybadge) [](https://pypistats.org/packages/anybadge) [](https://github.com/jongracecox/anybadge/commits/master) [](https://github.com/jongracecox/anybadge/blob/master/LICENSE) @@ -137,29 +137,29 @@ badge = anybadge.Badge(label='custom color', value='teal', default_color='#00808 Available named colors are: -| Color Name | Hex Code | Example | -| ---------- | -------- | ------- | -| aqua | #00FFFF |  | -| black | #000000 |  | -| blue | #0000FF |  | -| bright_red | #FF0000 |  | -| bright_yellow | #FFFF00 |  | -| fuchsia | #FF00FF |  | -| gray | #808080 |  | -| green | #4C1 |  | -| light_grey | #9F9F9F |  | -| lime | #00FF00 |  | -| maroon | #800000 |  | -| navy | #000080 |  | -| olive | #808000 |  | -| orange | #FE7D37 |  | -| purple | #800080 |  | -| red | #E05D44 |  | -| silver | #C0C0C0 |  | -| teal | #008080 |  | -| white | #FFFFFF |  | -| yellow | #DFB317 |  | -| yellow_green | #A4A61D |  | +| Color Name | Hex | Example | +| ------------- | ------- | ------- | +| aqua | #00FFFF |  | +| black | #000000 |  | +| blue | #0000FF |  | +| bright_red | #FF0000 |  | +| bright_yellow | #FFFF00 | | +| fuchsia | #FF00FF |  | +| gray | #808080 |  | +| green | #4C1 |  | +| light_grey | #9F9F9F |  | +| lime | #00FF00 |  | +| maroon | #800000 |  | +| navy | #000080 |  | +| olive | #808000 |  | +| orange | #FE7D37 |  | +| purple | #800080 |  | +| red | #E05D44 |  | +| silver | #C0C0C0 |  | +| teal | #008080 |  | +| white | #FFFFFF |  | +| yellow | #DFB317 |  | +| yellow_green | #A4A61D |  | ### Semantic version support diff --git a/anybadge/badge.py b/anybadge/badge.py index a0c4c34..e894192 100644 --- a/anybadge/badge.py +++ b/anybadge/badge.py @@ -1,5 +1,6 @@ import os from collections import OrderedDict +from typing import Dict, Type, Optional, Union from . import config from .colors import Color @@ -98,25 +99,28 @@ class Badge: '#4c1' """ + #: Singleton variable to track current max mask_id. This is used by _get_next_mask_str class method. + mask_id: int + def __init__( self, label, value, - font_name=None, - font_size=None, - num_padding_chars=None, - num_label_padding_chars=None, - num_value_padding_chars=None, - template=None, - style=None, - value_prefix="", - value_suffix="", - thresholds=None, - default_color=None, - use_max_when_value_exceeds=True, - value_format=None, - text_color=None, - semver=False, + font_name: str = None, + font_size: int = None, + num_padding_chars: int = None, + num_label_padding_chars: float = None, + num_value_padding_chars: float = None, + template: str = None, + style: str = None, + value_prefix: str = "", + value_suffix: str = "", + thresholds: Optional[Dict[float, str]] = None, + default_color: str = None, + use_max_when_value_exceeds: bool = True, + value_format: str = None, + text_color: str = None, + semver: bool = False, ): """Constructor for Badge class.""" # Set defaults if values were not passed @@ -181,9 +185,9 @@ class Badge: self.value_text_color = text_colors[1] self.use_max_when_value_exceeds = use_max_when_value_exceeds - self.mask_id = self.__class__._get_next_mask_id() + self.mask_str = self.__class__._get_next_mask_str() - def __repr__(self): + def __repr__(self) -> str: """Return a representation of the Badge object instance. The output of the __repr__ function could be used to recreate the current object. @@ -264,7 +268,7 @@ class Badge: optional_args, ) - def _repr_svg_(self): + def _repr_svg_(self) -> str: """Return SVG representation when used inside Jupyter notebook cells. This will render the SVG immediately inside a notebook cell when creating @@ -273,7 +277,7 @@ class Badge: return self.badge_svg_text @classmethod - def _get_next_mask_id(cls): + def _get_next_mask_str(cls) -> str: """Return a new mask ID from a singleton sequence maintained on the class. Returns: str @@ -285,7 +289,7 @@ class Badge: return config.MASK_ID_PREFIX + str(cls.mask_id) - def _get_svg_template(self): + def _get_svg_template(self) -> str: """Return the correct SVG template to render, based on the style and template that have been set @@ -308,7 +312,7 @@ class Badge: return self.template @property - def semver_version(self): + def semver_version(self) -> Version: """The semantic version represented by the value string. Returns: Version @@ -316,20 +320,25 @@ class Badge: return Version(self.value) @property - def semver_thresholds(self): + def semver_thresholds(self) -> Optional[OrderedDict]: """Thresholds as a dict using Version as keys.""" # Version is not a hashable type, so can't be used to create an # ordered dict directly. First we need to create an ordered list of keys + if not self.thresholds: + return None + ordered_keys = sorted(self.thresholds.keys(), key=Version) return OrderedDict((key, self.thresholds[key]) for key in ordered_keys) @property - def float_thresholds(self): + def float_thresholds(self) -> Optional[Dict[float, str]]: """Thresholds as a dict using floats as keys.""" + if not self.thresholds: + return None return {float(k): v for k, v in self.thresholds.items()} @property - def value_is_float(self): + def value_is_float(self) -> bool: """Identify whether the value text is a float. Returns: bool @@ -349,7 +358,7 @@ class Badge: return True @property - def value_is_int(self): + def value_is_int(self) -> bool: """Identify whether the value text is an int. Returns: bool @@ -363,7 +372,7 @@ class Badge: return a == b @property - def value_type(self): + def value_type(self) -> Type: """The Python type associated with the value. Returns: type @@ -378,9 +387,17 @@ class Badge: return str @property - def label_width(self): + def label_width(self) -> int: """The SVG width of the label text. + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀────────▶ + label_width + Returns: int """ return int( @@ -389,9 +406,17 @@ class Badge: ) @property - def value_width(self): + def value_width(self) -> int: """The SVG width of the value text. + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀────────▶ + value_width + Returns: int """ return int( @@ -400,15 +425,23 @@ class Badge: ) @property - def value_box_width(self): + def value_box_width(self) -> int: """The SVG width of the value text box. + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀────────────────────────────────▶ + value_box_width + Returns: int """ return self.value_width - 9 @property - def font_width(self): + def font_width(self) -> int: """Return the width multiplier for a font. Returns: @@ -422,25 +455,61 @@ class Badge: return config.FONT_WIDTHS[self.font_name][self.font_size] @property - def color_split_position(self): + def color_split_position(self) -> int: """The SVG x position where the color split should occur. + Split + │ + ┌──┘ + ▼ + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀───────────────────────────▶ + color_split_pos + Returns: int """ return self.badge_width - self.value_width @property - def label_anchor(self): + def label_anchor(self) -> float: """The SVG x position of the middle anchor for the label text. + Middle of + label + ┌──┘ + ▼ + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀───────────▶ + label_anchor + Returns: float """ return self.color_split_position / 2 @property - def value_anchor(self): + def value_anchor(self) -> float: """The SVG x position of the middle anchor for the value text. + Middle of + value + ┌──┘ + ▼ + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀──────────────────────────────────────────▶ + value_anchor + Returns: float """ return self.color_split_position + ( @@ -448,25 +517,62 @@ class Badge: ) @property - def label_anchor_shadow(self): + def label_anchor_shadow(self) -> float: """The SVG x position of the label shadow anchor. + The shadow for the label will appear behind the label. + + ┌ Text ──────────────────┐ + │ │ + │ ├────┐ + │ │ │ + └────┬───────────────────┘ │ + │ │ + └───────────── Shadow ───┘ + + The label_anchor_shadow is the distance from left to center of shadow: + + ┌─────────────────────────────┬─────────────────────────────────┐ + │ ┌────────────┐ │ │ + │ │ ├─┐ │ Value text │ + │ └─┬──────────┘ │ │ │ + │ └────────────┘ │ │ + └─────────────────────────────┴─────────────────────────────────┘ + ◀─────────────▶ + label_anchor_shadow + Returns: float """ return self.label_anchor + 1 @property - def value_anchor_shadow(self): + def value_anchor_shadow(self) -> float: """The SVG x position of the value shadow anchor. + ┌─────────────────────────────┬─────────────────────────────────┐ + │ │ ┌────────────┐ │ + │ Label text │ │ ├─┐ │ + │ │ └─┬──────────┘ │ │ + │ │ └────────────┘ │ + └─────────────────────────────┴─────────────────────────────────┘ + ◀───────────────────────────────────────────────▶ + value_anchor_shadow Returns: float """ return self.value_anchor + 1 @property - def badge_width(self): + def badge_width(self) -> int: """The total width of badge. + ┌───────────────────────────┬────────────────────────────────┐ + │ │ │ + │ Label text │ Value text │ + │ │ │ + └───────────────────────────┴────────────────────────────────┘ + ◀───────────────────────────────────────────────────────────▶ + badge_width + Returns: int Examples: @@ -478,8 +584,8 @@ class Badge: return self.label_width + self.value_width @property - def arc_start(self): - """The position where the arc on the gitlab-scoped should start. + def arc_start(self) -> int: + """The position where the arc on the arc should start. Returns: int @@ -492,7 +598,7 @@ class Badge: return self.badge_width - 10 @property - def badge_svg_text(self): + def badge_svg_text(self) -> str: """The badge SVG text. Returns: str @@ -515,12 +621,12 @@ class Badge: .replace("{{ value text color }}", self.value_text_color) .replace("{{ color split x }}", str(self.color_split_position)) .replace("{{ value width }}", str(self.value_width)) - .replace("{{ mask id }}", self.mask_id) + .replace("{{ mask id }}", self.mask_str) .replace("{{ value box width }}", str(self.value_box_width)) .replace("{{ arc start }}", str(self.arc_start)) ) - def __str__(self): + def __str__(self) -> str: """Return string representation of badge. This will return the badge SVG text. @@ -535,7 +641,7 @@ class Badge: """ return self.badge_svg_text - def get_text_width(self, text): + def get_text_width(self, text) -> int: """Return the width of text. Args: @@ -554,7 +660,7 @@ class Badge: return _get_approx_string_width(text, self.font_width) @property - def badge_color(self): + def badge_color(self) -> str: """Badge color based on the configured thresholds. Returns: str""" @@ -568,6 +674,8 @@ class Badge: else: return self.default_color + thresholds: Optional[Union[Dict[float, str], OrderedDict[float, str]]] + # Set value and thresholds based on the value type. This will result in either # value and thresholds as floats or value and thresholds as semantic versions. if self.value_type == Version: @@ -577,15 +685,17 @@ class Badge: value = float(self.value) thresholds = self.float_thresholds - # Convert the threshold dictionary into a sorted list of lists - threshold_list = [[self.value_type(i[0]), i[1]] for i in thresholds.items()] - threshold_list.sort(key=lambda x: x[0]) - color = None - for threshold, color in threshold_list: - if value < threshold: - return color + if thresholds: + + # Convert the threshold dictionary into a sorted list of lists + threshold_list = [[self.value_type(i[0]), i[1]] for i in thresholds.items()] + threshold_list.sort(key=lambda x: x[0]) + + for threshold, color in threshold_list: + if value < threshold: + return color # If we drop out the top of the range then return the last max color if color and self.use_max_when_value_exceeds: @@ -633,7 +743,7 @@ class Badge: (color, ", ".join(list(Color.__members__.keys()))), ) - def write_badge(self, file_path, overwrite=False): + def write_badge(self, file_path, overwrite=False) -> None: """Write badge to file.""" # Validate path (part 1) diff --git a/anybadge/colors.py b/anybadge/colors.py index 2b70da3..96f70e9 100644 --- a/anybadge/colors.py +++ b/anybadge/colors.py @@ -8,20 +8,23 @@ class Color(Enum): SILVER = "#C0C0C0" GRAY = "#808080" BLACK = "#000000" - RED = "#e05d44" + RED = "#E05D44" BRIGHT_RED = "#FF0000" MAROON = "#800000" OLIVE = "#808000" LIME = "#00FF00" BRIGHT_YELLOW = "#FFFF00" - YELLOW = "#dfb317" - GREEN = "#4c1" - YELLOW_GREEN = "#a4a61d" + YELLOW = "#DFB317" + GREEN = "#4C1" + YELLOW_GREEN = "#A4A61D" AQUA = "#00FFFF" TEAL = "#008080" BLUE = "#0000FF" NAVY = "#000080" FUCHSIA = "#FF00FF" PURPLE = "#800080" - ORANGE = "#fe7d37" - LIGHT_GREY = "#9f9f9f" + ORANGE = "#FE7D37" + LIGHT_GREY = "#9F9F9F" + + def __lt__(self, other): + return self.name < other.name diff --git a/anybadge/config.py b/anybadge/config.py index 12a8db2..b96d2df 100644 --- a/anybadge/config.py +++ b/anybadge/config.py @@ -1,14 +1,16 @@ # Set some defaults -DEFAULT_FONT = "DejaVu Sans,Verdana,Geneva,sans-serif" -DEFAULT_FONT_SIZE = 11 -NUM_PADDING_CHARS = 0.5 -DEFAULT_COLOR = "#4c1" -DEFAULT_TEXT_COLOR = "#fff" -MASK_ID_PREFIX = "anybadge_" +from typing import Dict + +DEFAULT_FONT: str = "DejaVu Sans,Verdana,Geneva,sans-serif" +DEFAULT_FONT_SIZE: int = 11 +NUM_PADDING_CHARS: float = 0.5 +DEFAULT_COLOR: str = "#4c1" +DEFAULT_TEXT_COLOR: str = "#fff" +MASK_ID_PREFIX: str = "anybadge_" # Dictionary for looking up approx pixel widths of # supported fonts and font sizes. -FONT_WIDTHS = { +FONT_WIDTHS: Dict[str, Dict[int, int]] = { "DejaVu Sans,Verdana,Geneva,sans-serif": { 10: 9, 11: 10, diff --git a/anybadge/helpers.py b/anybadge/helpers.py index 1dc0572..e92ed95 100644 --- a/anybadge/helpers.py +++ b/anybadge/helpers.py @@ -1,5 +1,5 @@ # Based on the following SO answer: https://stackoverflow.com/a/16008023/6252525 -def _get_approx_string_width(text, font_width, fixed_width=False): +def _get_approx_string_width(text, font_width, fixed_width=False) -> int: """ Get the approximate width of a string using a specific average font width. diff --git a/anybadge/server/cli.py b/anybadge/server/cli.py index 3d65061..4d31fc4 100644 --- a/anybadge/server/cli.py +++ b/anybadge/server/cli.py @@ -10,42 +10,43 @@ logger = logging.getLogger(__name__) def run(listen_address: str = None, port: int = None): + """Run a persistent webserver.""" if not listen_address: listen_address = config.DEFAULT_SERVER_LISTEN_ADDRESS if not port: port = config.DEFAULT_SERVER_PORT - server_address = (listen_address, port) - - global SERVER_PORT, SERVER_LISTEN_ADDRESS - - SERVER_PORT = port - SERVER_LISTEN_ADDRESS = listen_address + server_address: Tuple[str, int] = (listen_address, port) # type: ignore httpd = HTTPServer(server_address, AnyBadgeHTTPRequestHandler) logger.info("Serving at: http://%s:%s" % server_address) - httpd.serve_forever() + + try: + httpd.serve_forever() + except KeyboardInterrupt: + logger.info("Received keyboard interrupt. Shutting down...") -def parse_args(): +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" logger.debug("Parsing command line arguments.") parser = argparse.ArgumentParser(description="Run an anybadge server.") parser.add_argument( "-p", "--port", type=int, - default=DEFAULT_SERVER_PORT, - help="Server port number. Default is %s. This can also be set via an environment " - "variable called ``ANYBADGE_PORT``." % DEFAULT_SERVER_PORT, + default=config.DEFAULT_SERVER_PORT, + help=f"Server port number. Default is {config.DEFAULT_SERVER_PORT}. This can also be set via an environment " + "variable called ``ANYBADGE_PORT``.", ) parser.add_argument( "-l", "--listen-address", type=str, - default=DEFAULT_SERVER_LISTEN_ADDRESS, - help="Server listen address. Default is %s. This can also be set via an environment " - "variable called ``ANYBADGE_LISTEN_ADDRESS``." % DEFAULT_SERVER_LISTEN_ADDRESS, + default=config.DEFAULT_SERVER_LISTEN_ADDRESS, + help=f"Server listen address. Default is {config.DEFAULT_SERVER_LISTEN_ADDRESS}. This can also be set via an " + f"environment variable called ``ANYBADGE_LISTEN_ADDRESS``.", ) parser.add_argument( "-d", "--debug", action="store_true", help="Enable debug logging." @@ -56,23 +57,23 @@ def parse_args(): def main(): """Run server.""" - global DEFAULT_SERVER_PORT, DEFAULT_SERVER_LISTEN_ADDRESS, DEFAULT_LOGGING_LEVEL - # Check for environment variables if "ANYBADGE_PORT" in environ: - DEFAULT_SERVER_PORT = environ["ANYBADGE_PORT"] + config.DEFAULT_SERVER_PORT = environ["ANYBADGE_PORT"] if "ANYBADGE_LISTEN_ADDRESS" in environ: - DEFAULT_SERVER_LISTEN_ADDRESS = environ["ANYBADGE_LISTEN_ADDRESS"] + config.DEFAULT_SERVER_LISTEN_ADDRESS = environ["ANYBADGE_LISTEN_ADDRESS"] if "ANYBADGE_LOG_LEVEL" in environ: - DEFAULT_LOGGING_LEVEL = logging.getLevelName(environ["ANYBADGE_LOG_LEVEL"]) + config.DEFAULT_LOGGING_LEVEL = logging.getLevelName( + environ["ANYBADGE_LOG_LEVEL"] + ) # Parse command line args - args = parse_args() + args: argparse.Namespace = parse_args() # Set logging level - logging_level = DEFAULT_LOGGING_LEVEL + logging_level = config.DEFAULT_LOGGING_LEVEL if args.debug: logging_level = logging.DEBUG diff --git a/anybadge/server/config.py b/anybadge/server/config.py index 42297c5..7544e1a 100644 --- a/anybadge/server/config.py +++ b/anybadge/server/config.py @@ -1,9 +1,9 @@ import logging -DEFAULT_SERVER_PORT = 8000 -DEFAULT_SERVER_LISTEN_ADDRESS = "localhost" +DEFAULT_SERVER_PORT: int = 8000 +DEFAULT_SERVER_LISTEN_ADDRESS: str = "localhost" DEFAULT_LOGGING_LEVEL = logging.INFO -SERVER_PORT = DEFAULT_SERVER_PORT -SERVER_LISTEN_ADDRESS = DEFAULT_SERVER_LISTEN_ADDRESS +SERVER_PORT: int = DEFAULT_SERVER_PORT +SERVER_LISTEN_ADDRESS: str = DEFAULT_SERVER_LISTEN_ADDRESS diff --git a/anybadge/server/request_handler.py b/anybadge/server/request_handler.py index e514a2d..9de9daa 100644 --- a/anybadge/server/request_handler.py +++ b/anybadge/server/request_handler.py @@ -3,7 +3,6 @@ import urllib.parse as urlparse from http.server import BaseHTTPRequestHandler from anybadge import Badge -from anybadge.server import config logger = logging.getLogger(__name__) @@ -55,7 +54,9 @@ class AnyBadgeHTTPRequestHandler(BaseHTTPRequestHandler): self.wfile.write(b"
%s
" % line)) diff --git a/anybadge/styles.py b/anybadge/styles.py index ac7f532..8e63564 100644 --- a/anybadge/styles.py +++ b/anybadge/styles.py @@ -16,7 +16,7 @@ class Style(Enum): self.suffix = suffix @classmethod - def exists(cls, name: str): + def exists(cls, name: str) -> bool: """Test whether a style exists.""" try: _ = cls[name] diff --git a/anybadge/templates/__init__.py b/anybadge/templates/__init__.py index 3872fdb..38e45aa 100644 --- a/anybadge/templates/__init__.py +++ b/anybadge/templates/__init__.py @@ -14,6 +14,11 @@ def get_template(name: str) -> str: """ try: - return pkgutil.get_data(__name__, name + ".svg").decode("utf-8") + data = pkgutil.get_data(__name__, name + ".svg") except FileNotFoundError as e: raise UnknownBadgeTemplate from e + + if not data: + raise UnknownBadgeTemplate + + return data.decode("utf-8") diff --git a/build_examples.py b/build_examples.py index 5601298..7d99517 100644 --- a/build_examples.py +++ b/build_examples.py @@ -3,21 +3,20 @@ import anybadge if __name__ == "__main__": print( - """| Color Name | Hex Code | Example | - | ---------- | -------- | ------- |""" + """ +| Color Name | Hex | Example | +| ------------- | ------- | ------- |""" ) - for color, hex in sorted(anybadge.COLORS.items()): + for color in sorted(anybadge.colors.Color): - file = "examples/color_" + color + ".svg" + file = "examples/color_" + color.name.lower() + ".svg" url = "https://cdn.rawgit.com/jongracecox/anybadge/master/" + file - anybadge.Badge(label="Color", value=color, default_color=color).write_badge( - file, overwrite=True - ) + anybadge.Badge( + label="Color", value=color.name.lower(), default_color=color.value + ).write_badge(file, overwrite=True) print( - "| {color} | {hex} |  |".format( - color=color, hex=hex.upper(), url=url - ) + f"| {color.name.lower():<13} | {color.value.upper():<7} | ':<84}|" ) diff --git a/examples/color_aqua.svg b/examples/color_aqua.svg index c0edaaf..5560569 100644 --- a/examples/color_aqua.svg +++ b/examples/color_aqua.svg @@ -1,23 +1,23 @@ -