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/colourlovers/clapi.py b/colourlovers/clapi.py index 1d57119..225dc9a 100755 --- a/colourlovers/clapi.py +++ b/colourlovers/clapi.py @@ -1,8 +1,15 @@ # Imports import collections import json -from urllib.request import Request, urlopen, URLError -from colourlovers.data_containers import * +from urllib.request import Request, urlopen + +from .data_containers import ( + Color, + Palette, + Pattern, + Lover, + Stats, +) # TODO @@ -21,6 +28,7 @@ class ColourLovers(object): """ ColourLovers API python wrapper """ + def __init__(self): self.__API_URL = "https://www.colourlovers.com/api/" @@ -42,27 +50,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 +78,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 +99,7 @@ def __init__(self): "numResults", "resultOffset", "format", - "jsonCallback" + "jsonCallback", } ), "palettes": set( @@ -108,7 +116,7 @@ def __init__(self): "resultOffset", "format", "jsonCallback", - "showPaletteWidths" + "showPaletteWidths", } ), "patterns": set( @@ -124,7 +132,7 @@ def __init__(self): "numResults", "resultOffset", "format", - "jsonCallback" + "jsonCallback", } ), "lovers": set( @@ -134,44 +142,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 +223,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 @@ -236,8 +245,8 @@ 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: - raise ValueError("Invalid parameter "+str(raw_data)) + if not isinstance(raw_data, bool): + 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 +254,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 +266,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 +312,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: @@ -308,9 +320,10 @@ 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: - parameter_values_types = [type(parameter_value) for parameter_value in - kwargs.values()[parameter_type[0]]] + elif isinstance(parameter_type[1], list): + 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 +332,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): @@ -335,16 +349,15 @@ 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(): # build API parameter specification string - if type(kwargs[argument]) == list: - values = ','.join([str(value) for value in values]) + if isinstance(kwargs[argument], list): + values = ",".join([str(value) for value in values]) else: values = str(values) additional_parameters.append(f"{argument}={values}") @@ -353,7 +366,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() @@ -380,7 +393,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 = [] @@ -395,7 +408,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..4f87b14 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): @@ -29,7 +38,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 +60,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 +68,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() @@ -107,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): 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 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