diff --git a/.gitignore b/.gitignore index d2d9a89..5fa0e95 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ Temporary Items .Python env/ venv/ +.venv/ build/ develop-eggs/ dist/ @@ -98,3 +99,4 @@ cookiecutter-pypackage-env/ # vscode settings .history/ *.code-workspace +.vscode/ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/risutools/__init__.py b/src/risutools/__init__.py index e69de29..b2105a8 100644 --- a/src/risutools/__init__.py +++ b/src/risutools/__init__.py @@ -0,0 +1,3 @@ +from .nodes import UUIDGenerator +NODE_CLASS_MAPPINGS = { "UUIDGenerator" : UUIDGenerator } +__all__ = ["NODE_CLASS_MAPPINGS"] diff --git a/src/risutools/nodes.py b/src/risutools/nodes.py index fab5cff..a4f0d72 100644 --- a/src/risutools/nodes.py +++ b/src/risutools/nodes.py @@ -1,118 +1,83 @@ from inspect import cleandoc -class Example: +from typing import Literal +import uuid + +UUIDVersions = Literal["v1", "v3", "v4", "v5"] +class UUIDGenerator: """ - A example node + A UUID generator node + + This node generates a UUID string based on a string input. It can generate different + types of UUIDs and optionally use the input string as a namespace. Class methods ------------- INPUT_TYPES (dict): Tell the main program input parameters of nodes. - IS_CHANGED: - optional method to control when the node is re executed. Attributes ---------- RETURN_TYPES (`tuple`): - The type of each element in the output tulple. + The type of each element in the output tuple. RETURN_NAMES (`tuple`): - Optional: The name of each output in the output tulple. + The name of each output in the output tuple. FUNCTION (`str`): - The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() - OUTPUT_NODE ([`bool`]): - If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. - The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. - Assumed to be False if not present. + The name of the entry-point method. CATEGORY (`str`): The category the node should appear in the UI. - execute(s) -> tuple || None: - The entry point method. The name of this method must be the same as the value of property `FUNCTION`. - For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. """ def __init__(self): pass @classmethod - def INPUT_TYPES(s): + def INPUT_TYPES(cls): """ Return a dictionary which contains config for all input fields. - Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". - Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. - The type can be a list for selection. - - Returns: `dict`: - - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` - - Value input_fields (`dict`): Contains input fields config: - * Key field_name (`string`): Name of a entry-point method's argument - * Value field_config (`tuple`): - + First value is a string indicate the type of field or a list for selection. - + Secound value is a config for type "INT", "STRING" or "FLOAT". """ return { "required": { - "image": ("Image", { "tooltip": "This is an image"}), - "int_field": ("INT", { - "default": 0, - "min": 0, #Minimum value - "max": 4096, #Maximum value - "step": 64, #Slider's step - "display": "number" # Cosmetic only: display as "number" or "slider" - }), - "float_field": ("FLOAT", { - "default": 1.0, - "min": 0.0, - "max": 10.0, - "step": 0.01, - "round": 0.001, #The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. - "display": "number"}), - "print_to_screen": (["enable", "disable"],), - "string_field": ("STRING", { - "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node - "default": "Hello World!" + "input_string": ("STRING", { + "multiline": False, + "default": "seed string" }), + "uuid_version": (["v1", "v3", "v4", "v5"], { "default" : "v5" }) }, } - RETURN_TYPES = ("IMAGE",) - #RETURN_NAMES = ("image_output_name",) - DESCRIPTION = cleandoc(__doc__) - FUNCTION = "test" + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("uuid",) + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "generate_uuid" - #OUTPUT_NODE = False - #OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node + CATEGORY = "RisuTools/String" - CATEGORY = "Example" + def generate_uuid(self, input_string, uuid_version : UUIDVersions = "v5"): + namespace = uuid.UUID('0413fcc2-9f97-5739-82fd-523d122514b7') + # Generate UUID based on version + if uuid_version == "v1": + # Version 1: Time-based + result = str(uuid.uuid1()) + elif uuid_version == "v3": + # Version 3: MD5 hash of namespace + name + result = str(uuid.uuid3(namespace, input_string)) + elif uuid_version == "v4": + # Version 4: Random + result = str(uuid.uuid4()) + elif uuid_version == "v5": + # Version 5: SHA-1 hash of namespace + name + result = str(uuid.uuid5(namespace, input_string)) - def test(self, image, string_field, int_field, float_field, print_to_screen): - if print_to_screen == "enable": - print(f"""Your input contains: - string_field aka input text: {string_field} - int_field: {int_field} - float_field: {float_field} - """) - #do some processing on the image, in this example I just invert it - image = 1.0 - image - return (image,) - - """ - The node will always be re executed if any of the inputs change but - this method can be used to force the node to execute again even when the inputs don't change. - You can make this node return a number or a string. This value will be compared to the one returned the last time the node was - executed, if it is different the node will be executed again. - This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash - changes between executions the LoadImage node is executed again. - """ - #@classmethod - #def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen): - # return "" + return (result,) # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { - "Example": Example + "UUIDGenerator": UUIDGenerator } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { - "Example": "Example Node" + "UUIDGenerator": "UUID Generator" } + diff --git a/tests/test_risutools.py b/tests/test_risutools.py index 352f615..62c95fb 100644 --- a/tests/test_risutools.py +++ b/tests/test_risutools.py @@ -3,19 +3,72 @@ """Tests for `risutools` package.""" import pytest -from src.risutools.nodes import Example +import uuid +from src.risutools.nodes import UUIDGenerator @pytest.fixture -def example_node(): - """Fixture to create an Example node instance.""" - return Example() +def uuid_generator_node(): + """Fixture to create a UUIDGenerator node instance.""" + return UUIDGenerator() -def test_example_node_initialization(example_node): +def test_uuid_generator_initialization(uuid_generator_node): """Test that the node can be instantiated.""" - assert isinstance(example_node, Example) + assert isinstance(uuid_generator_node, UUIDGenerator) def test_return_types(): """Test the node's metadata.""" - assert Example.RETURN_TYPES == ("IMAGE",) - assert Example.FUNCTION == "test" - assert Example.CATEGORY == "Example" + assert UUIDGenerator.RETURN_TYPES == ("STRING",) + assert UUIDGenerator.RETURN_NAMES == ("uuid",) + assert UUIDGenerator.FUNCTION == "generate_uuid" + assert UUIDGenerator.CATEGORY == "RisuTools/String" + +def test_uuid_generation_v5(): + """Test UUID v5 generation.""" + node = UUIDGenerator() + test_string = "test string" + result, = node.generate_uuid(test_string, "v5") + + # Verify it's a valid UUID string + assert uuid.UUID(result) + + # Verify deterministic behavior - same input should produce same UUID + result2, = node.generate_uuid(test_string, "v5") + assert result == result2 + + # Different input should produce different UUID + result3, = node.generate_uuid("different string", "v5") + assert result != result3 + +def test_uuid_generation_v4(): + """Test UUID v4 generation (random).""" + node = UUIDGenerator() + result, = node.generate_uuid("any string", "v4") + + # Verify it's a valid UUID string + assert uuid.UUID(result) + + # v4 is random, so multiple calls should produce different results + # Note: There's a tiny probability this could fail by random chance + result2, = node.generate_uuid("any string", "v4") + assert result != result2 + +def test_uuid_generation_v3(): + """Test UUID v3 generation.""" + node = UUIDGenerator() + test_string = "test string" + result, = node.generate_uuid(test_string, "v3") + + # Verify it's a valid UUID string + assert uuid.UUID(result) + + # Verify deterministic behavior - same input should produce same UUID + result2, = node.generate_uuid(test_string, "v3") + assert result == result2 + +def test_uuid_generation_v1(): + """Test UUID v1 generation (time-based).""" + node = UUIDGenerator() + result, = node.generate_uuid("any string", "v1") + + # Verify it's a valid UUID string + assert uuid.UUID(result)