From 516d6da2867ecd4fa183a52e84171cdeae6d151d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Fri, 6 Feb 2026 14:25:22 +0300 Subject: [PATCH 1/5] Add basic ruff config --- .pre-commit-config.yaml | 7 +++++++ ruff.toml | 1 + 2 files changed, 8 insertions(+) create mode 100644 ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 566a598..c043df8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,13 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.0 + hooks: + - id: ruff-check + args: ["--fix"] + - id: ruff-format + - repo: https://github.com/google/yamlfmt rev: v0.21.0 hooks: diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..f11cf63 --- /dev/null +++ b/ruff.toml @@ -0,0 +1 @@ +line-length = 120 From 6f83c669f80769c525d88234126fd6f433fe8f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Fri, 6 Feb 2026 14:25:41 +0300 Subject: [PATCH 2/5] Apply automatic fixes from ruff --- colourlovers/clapi.py | 65 ++++++++++++++++++--------------- colourlovers/data_containers.py | 9 +++-- setup.py | 33 ++++++++--------- tests/test_suite.py | 4 +- 4 files changed, 59 insertions(+), 52 deletions(-) diff --git a/colourlovers/clapi.py b/colourlovers/clapi.py index 1d57119..47bf792 100755 --- a/colourlovers/clapi.py +++ b/colourlovers/clapi.py @@ -1,7 +1,7 @@ # Imports import collections import json -from urllib.request import Request, urlopen, URLError +from urllib.request import Request, urlopen from colourlovers.data_containers import * @@ -21,6 +21,7 @@ class ColourLovers(object): """ ColourLovers API python wrapper """ + def __init__(self): self.__API_URL = "https://www.colourlovers.com/api/" @@ -42,27 +43,27 @@ def __init__(self): { "new", "top", - "random" + "random", } ), "palettes": set( { "new", "top", - "random" + "random", } ), "patterns": set( { "new", "top", - "random" + "random", } ), "lovers": set( { "new", - "top" + "top", } ), "stats": set( @@ -70,13 +71,13 @@ def __init__(self): "colors", "palettes", "patterns", - "lovers" + "lovers", } ), "color": set(), "palette": set(), "pattern": set(), - "lover": set() + "lover": set(), } self.__API_PARAMETERS = { "colors": set( @@ -91,7 +92,7 @@ def __init__(self): "numResults", "resultOffset", "format", - "jsonCallback" + "jsonCallback", } ), "palettes": set( @@ -108,7 +109,7 @@ def __init__(self): "resultOffset", "format", "jsonCallback", - "showPaletteWidths" + "showPaletteWidths", } ), "patterns": set( @@ -124,7 +125,7 @@ def __init__(self): "numResults", "resultOffset", "format", - "jsonCallback" + "jsonCallback", } ), "lovers": set( @@ -134,44 +135,44 @@ def __init__(self): "numResults", "resultOffset", "format", - "jsonCallback" + "jsonCallback", } ), "stats": set( { "format", - "jsonCallback" + "jsonCallback", } ), "color": set( { "format", - "jsonCallback" + "jsonCallback", } ), "palette": set( { "format", - "jsonCallback" + "jsonCallback", } ), "pattern": set( { "format", - "jsonCallback" + "jsonCallback", } ), "lover": set( { "comments", "format", - "jsonCallback" + "jsonCallback", } - ) + ), } self.__API_SWITCHES = { "palette": set({"showPaletteWidths"}), - "lover": set({"comments"}) + "lover": set({"comments"}), } self.__API_ADD_PARAM = ["&", "=", "?", "/"] self.__API_COLORS = "colors" @@ -215,6 +216,7 @@ def __public_api_method(self, search_type, data_container): :return: function that will know how to make queries for the specified type and process and store the obtained data """ + def _api_search(raw_data=False, **kwargs): """ This method validates the request parameters and, if all @@ -237,7 +239,7 @@ def _api_search(raw_data=False, **kwargs): # type of request (pattern, palette, colour, ...) that is to be performed processed_request = self.__process_optional_requests(search_type, **kwargs) if type(raw_data) != bool: - raise ValueError("Invalid parameter "+str(raw_data)) + raise ValueError("Invalid parameter " + str(raw_data)) if not raw_data: # if user hasn't asked for the raw data of the API @@ -245,9 +247,11 @@ def _api_search(raw_data=False, **kwargs): # the data in json format processed_request.kwargs["format"] = "json" # Once request type has been validated make the query to the API - api_response = self.__query(search_type, - processed_request.optional_request, - **processed_request.kwargs).decode() + api_response = self.__query( + search_type, + processed_request.optional_request, + **processed_request.kwargs, + ).decode() # Process the data obtained from the query. We will build container # objects by default unless otherwise specified containers = self.__process_response(raw_data, api_response, data_container) @@ -255,6 +259,7 @@ def _api_search(raw_data=False, **kwargs): return containers else: print("The data you asked for could not be retrieved") + return _api_search def __query(self, search_term, optional_request_term, **kwargs): @@ -300,7 +305,7 @@ def __check_args(self, search_term, **kwargs): # Look for invalid parameter names invalid_parameters = set(kwargs.keys()) - self.__API_PARAMETERS[search_term] if invalid_parameters: - raise ValueError("Unsupported search argument/s " + ', '.join(invalid_parameters)) + raise ValueError("Unsupported search argument/s " + ", ".join(invalid_parameters)) # Look for invalid parameter value types types = [(i, type(value)) for (i, value) in enumerate(kwargs.values())] for parameter_type in types: @@ -309,8 +314,9 @@ def __check_args(self, search_term, **kwargs): # If the argument value is a list, the type of all the elements in the list should be # a valid type and also, all the values should be of the same type elif parameter_type[1] == list: - parameter_values_types = [type(parameter_value) for parameter_value in - kwargs.values()[parameter_type[0]]] + parameter_values_types = [ + type(parameter_value) for parameter_value in kwargs.values()[parameter_type[0]] + ] # Select the type of the first value in list as the parameter type # and look for inconsistencies or invalid types type_selector = parameter_values_types[0] @@ -319,7 +325,8 @@ def __check_args(self, search_term, **kwargs): for parameter_value_type in parameter_values_types: if parameter_value_type != type_selector: raise ValueError( - "Inconsistent value types in argument " + str(kwargs.keys()[parameter_type[0]])) + "Inconsistent value types in argument " + str(kwargs.keys()[parameter_type[0]]) + ) return True def __request(self, search_term, optional_request_term, **kwargs): @@ -344,7 +351,7 @@ def __request(self, search_term, optional_request_term, **kwargs): for argument, values in kwargs.items(): # build API parameter specification string if type(kwargs[argument]) == list: - values = ','.join([str(value) for value in values]) + values = ",".join([str(value) for value in values]) else: values = str(values) additional_parameters.append(f"{argument}={values}") @@ -353,7 +360,7 @@ def __request(self, search_term, optional_request_term, **kwargs): additional_parameters = "&".join(additional_parameters) api_request_url += "?" + additional_parameters # HTTP API request - req = Request(api_request_url, headers={'User-Agent': "Magic Browser"}) + req = Request(api_request_url, headers={"User-Agent": "Magic Browser"}) # Make request and read response response = urlopen(req) data = response.read() @@ -395,7 +402,7 @@ def __process_optional_requests(self, search_type, **kwargs): :param kwargs: :return: """ - processed_request = collections.namedtuple('Processed_request', ['kwargs', 'optional_request']) + processed_request = collections.namedtuple("Processed_request", ["kwargs", "optional_request"]) optional_request_term = None # Only process optional request if it is present in the keywords valid_keyword = self.__API_REQUEST_KEYWORDS[search_type] or None diff --git a/colourlovers/data_containers.py b/colourlovers/data_containers.py index e4a6084..b24666f 100755 --- a/colourlovers/data_containers.py +++ b/colourlovers/data_containers.py @@ -29,7 +29,7 @@ def __init__(self, json_data): def hex_to_rgb(self): # Converts color in hex to RGB. Returns list of tuples where the channel order is (R,G,B) - return [tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) for hex_color in self.colors] + return [tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) for hex_color in self.colors] def hex_to_hsv(self): # Converts color in hex to HSV. Returns list of tuples such that each tuple is (H,S,V) @@ -51,6 +51,7 @@ def __init__(self, hsv): self.value = hsv["value"] self.hsv = (self.hue, self.saturation, self.value) + class DrawColors(object): def __init__(self, rgb_colors): self.rgb_colors = rgb_colors @@ -58,12 +59,12 @@ def __init__(self, rgb_colors): def draw(self, tile_size=24, offset=8): # Allows the visualization of colors - im = Image.new("RGB", ((offset+tile_size+offset)*self.num_colors, tile_size), "black") + im = Image.new("RGB", ((offset + tile_size + offset) * self.num_colors, tile_size), "black") draw = ImageDraw.Draw(im) for i in range(self.num_colors): draw.rectangle( - (((offset+tile_size)*i, 0), ((offset+tile_size)*(i+1)-offset, tile_size)), - fill=self.rgb_colors[i] + (((offset + tile_size) * i, 0), ((offset + tile_size) * (i + 1) - offset, tile_size)), + fill=self.rgb_colors[i], ) im.show() diff --git a/setup.py b/setup.py index a7d5354..c2355ee 100755 --- a/setup.py +++ b/setup.py @@ -1,27 +1,26 @@ from setuptools import setup -version = '0.2.0' +version = "0.2.0" setup( - name='colourlovers', - packages=['colourlovers'], # this must be the same as the name above + name="colourlovers", + packages=["colourlovers"], # this must be the same as the name above version=version, - description='A python wrapper for ColourLovers API', - long_description='\n\n'.join([ - open('README.rst').read(), open('CHANGES.rst').read()]), - author='Juan Gallostra', - author_email='juangallostra@gmail.com', - license='MIT', - url='https://github.com/juangallostra/Colourlovers-API-wrapper', # use the URL to the github repo - download_url='https://github.com/juangallostra/Colourlovers-API-wrapper/archive/'+version+'.tar.gz', # I'll explain this in a second - keywords=['color', 'colour', 'palette', 'api', 'colourlovers', 'wrapper'], + description="A python wrapper for ColourLovers API", + long_description="\n\n".join([open("README.rst").read(), open("CHANGES.rst").read()]), + author="Juan Gallostra", + author_email="juangallostra@gmail.com", + license="MIT", + url="https://github.com/juangallostra/Colourlovers-API-wrapper", # use the URL to the github repo + download_url=f"https://github.com/juangallostra/Colourlovers-API-wrapper/archive/{version}.tar.gz", # I'll explain this in a second + keywords=["color", "colour", "palette", "api", "colourlovers", "wrapper"], classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3' + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", ], install_requires=[ - 'Pillow', + "Pillow", ], ) diff --git a/tests/test_suite.py b/tests/test_suite.py index 6fef276..ac7820b 100755 --- a/tests/test_suite.py +++ b/tests/test_suite.py @@ -6,11 +6,11 @@ UNDER DEVELOPMENT """ -from colourlovers import clapi +from colourlovers import clapi -if __name__=="__main__": +if __name__ == "__main__": cl = clapi.ColourLovers() # Method test From 254065e5730c847e58d2921f900cf029cabc386d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Fri, 6 Feb 2026 14:28:51 +0300 Subject: [PATCH 3/5] Don't use wildcard import Resolves F403 warnings https://docs.astral.sh/ruff/rules/undefined-local-with-import-star/ --- colourlovers/clapi.py | 9 ++++++++- colourlovers/data_containers.py | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/colourlovers/clapi.py b/colourlovers/clapi.py index 47bf792..4ed431f 100755 --- a/colourlovers/clapi.py +++ b/colourlovers/clapi.py @@ -2,7 +2,14 @@ import collections import json from urllib.request import Request, urlopen -from colourlovers.data_containers import * + +from .data_containers import ( + Color, + Palette, + Pattern, + Lover, + Stats, +) # TODO diff --git a/colourlovers/data_containers.py b/colourlovers/data_containers.py index b24666f..40aaa12 100755 --- a/colourlovers/data_containers.py +++ b/colourlovers/data_containers.py @@ -2,6 +2,15 @@ from PIL import Image, ImageDraw +__all__ = ( + "Color", + "Palette", + "Pattern", + "Lover", + "Stats", +) + + # Data containers for API responses # Base classes class CommonData(object): From 771daf11bbf8479925eec532db2307f4b72c8c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Fri, 6 Feb 2026 14:32:24 +0300 Subject: [PATCH 4/5] Improve type comparisons Resolves E721 warnings https://docs.astral.sh/ruff/rules/type-comparison/ --- colourlovers/clapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colourlovers/clapi.py b/colourlovers/clapi.py index 4ed431f..4b9eafb 100755 --- a/colourlovers/clapi.py +++ b/colourlovers/clapi.py @@ -245,7 +245,7 @@ def _api_search(raw_data=False, **kwargs): # Validate the type of request (new, top, random, ...) taking into account the # type of request (pattern, palette, colour, ...) that is to be performed processed_request = self.__process_optional_requests(search_type, **kwargs) - if type(raw_data) != bool: + if not isinstance(raw_data, bool): raise ValueError("Invalid parameter " + str(raw_data)) if not raw_data: @@ -320,7 +320,7 @@ def __check_args(self, search_term, **kwargs): raise ValueError("Unsupported argument value type " + str(parameter_type)) # If the argument value is a list, the type of all the elements in the list should be # a valid type and also, all the values should be of the same type - elif parameter_type[1] == list: + elif isinstance(parameter_type[1], list): parameter_values_types = [ type(parameter_value) for parameter_value in kwargs.values()[parameter_type[0]] ] @@ -357,7 +357,7 @@ def __request(self, search_term, optional_request_term, **kwargs): additional_parameters = [] for argument, values in kwargs.items(): # build API parameter specification string - if type(kwargs[argument]) == list: + if isinstance(kwargs[argument], list): values = ",".join([str(value) for value in values]) else: values = str(values) @@ -394,7 +394,7 @@ def __process_response(self, raw_data, api_response, request_type_class): return api_response else: parsed_json = json.loads(api_response) - if type(parsed_json) == dict: + if isinstance(parsed_json, dict): response_containers = request_type_class(parsed_json) else: response_containers = [] From 57c06a64842e1046fa0d710e2782937dbf83e84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Fri, 6 Feb 2026 14:36:30 +0300 Subject: [PATCH 5/5] Remove bare excepts Resolves E722 warnings https://docs.astral.sh/ruff/rules/bare-except/ --- colourlovers/clapi.py | 7 +++---- colourlovers/data_containers.py | 7 ++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/colourlovers/clapi.py b/colourlovers/clapi.py index 4b9eafb..225dc9a 100755 --- a/colourlovers/clapi.py +++ b/colourlovers/clapi.py @@ -349,10 +349,9 @@ def __request(self, search_term, optional_request_term, **kwargs): :return: the response of the request as raw data in json or xml format """ # build API request - try: - api_request_url = self.__API_URL + search_term + optional_request_term - except: - api_request_url = self.__API_URL + search_term + api_request_url = self.__API_URL + search_term + if optional_request_term: + api_request_url += optional_request_term additional_parameters = [] for argument, values in kwargs.items(): diff --git a/colourlovers/data_containers.py b/colourlovers/data_containers.py index 40aaa12..4f87b14 100755 --- a/colourlovers/data_containers.py +++ b/colourlovers/data_containers.py @@ -117,11 +117,8 @@ def __init__(self, json_data): self.num_comments_made = json_data["numCommentsMade"] self.num_lovers = json_data["numLovers"] self.num_comments_on_profile = json_data["numCommentsOnProfile"] - try: - pass - # implement comments section -> switch - except: - pass + + # TODO: implement comments section -> switch class Stats(object):