diff --git a/comfy_config/config_parser.py b/comfy_config/config_parser.py new file mode 100644 index 000000000..ee41f6769 --- /dev/null +++ b/comfy_config/config_parser.py @@ -0,0 +1,115 @@ +import os +from typing import Optional + +import tomlkit +import tomlkit.exceptions + +import logging + +from comfy_config.types import ( + ComfyConfig, + License, + Model, + ProjectConfig, + PyProjectConfig, + URLs, +) + +""" +Original implementation comes from https://github.com/Comfy-Org/comfy-cli/blob/2e36f33dd39ef43b5acf7d1fc5acc5e01be92360/comfy_cli/registry/config_parser.py#L146 + +Extract configuration from a custom node directory's pyproject.toml file. + +This function reads and parses the pyproject.toml file in the specified directory +to extract project and ComfyUI-specific configuration information. If no +pyproject.toml file is found, it creates a minimal configuration using the +folder name as the project name. + +Args: + path (str): Path to the directory containing the pyproject.toml file. + If pyproject.toml doesn't exist, the folder name will be used + as the default project name. + +Returns: + Optional[PyProjectConfig]: A PyProjectConfig object containing: + - project: Basic project information (name, version, dependencies, etc.) + - tool_comfy: ComfyUI-specific configuration (publisher_id, models, etc.) + Returns None if configuration extraction fails. + +Notes: + - If pyproject.toml is missing, creates a default config with folder name + +Example: + >>> from comfy_config import config_parser + >>> custom_node_dir = os.path.dirname(os.path.realpath(__file__)) + >>> project_config = config_parser.extract_node_configuration(custom_node_dir) + >>> print(project_config.project.name) # "my_custom_node" or name from pyproject.toml + >>> nodes.EXTENSION_WEB_DIRS[project_config.project.name] = js_dir +""" +def extract_node_configuration( + path, +) -> Optional[PyProjectConfig]: + folder_name = os.path.basename(path) + + path = os.path.join(path, "pyproject.toml") + + if not os.path.isfile(path): + logging.warning("No pyproject.toml file found in the current directory, will use custom node folder name as project name as default.") + + project = ProjectConfig( + name=folder_name, + ) + + return PyProjectConfig(project=project) + + with open(path, "r") as file: + data = tomlkit.load(file) + + project_data = data.get("project", {}) + urls_data = project_data.get("urls", {}) + comfy_data = data.get("tool", {}).get("comfy", {}) + + license_data = project_data.get("license", {}) + if isinstance(license_data, str): + license = License(text=license_data) + logging.warning( + 'Warning: License should be in one of these two formats: license = {file = "LICENSE"} OR license = {text = "MIT License"}. Please check the documentation: https://docs.comfy.org/registry/specifications.' + ) + elif isinstance(license_data, dict): + if "file" in license_data or "text" in license_data: + license = License(file=license_data.get("file", ""), text=license_data.get("text", "")) + else: + logging.warning( + 'Warning: License should be in one of these two formats: license = {file = "LICENSE"} OR license = {text = "MIT License"}. Please check the documentation: https://docs.comfy.org/registry/specifications.' + ) + license = License() + else: + license = License() + logging.warning( + 'Warning: License should be in one of these two formats: license = {file = "LICENSE"} OR license = {text = "MIT License"}. Please check the documentation: https://docs.comfy.org/registry/specifications.' + ) + + project = ProjectConfig( + name=project_data.get("name", ""), + description=project_data.get("description", ""), + version=project_data.get("version", ""), + requires_python=project_data.get("requires-python", ""), + dependencies=project_data.get("dependencies", []), + license=license, + urls=URLs( + homepage=urls_data.get("Homepage", ""), + documentation=urls_data.get("Documentation", ""), + repository=urls_data.get("Repository", ""), + issues=urls_data.get("Issues", ""), + ), + ) + + comfy = ComfyConfig( + publisher_id=comfy_data.get("PublisherId", ""), + display_name=comfy_data.get("DisplayName", ""), + icon=comfy_data.get("Icon", ""), + models=[Model(location=m["location"], model_url=m["model_url"]) for m in comfy_data.get("Models", [])], + includes=comfy_data.get("includes", []), + ) + + return PyProjectConfig(project=project, tool_comfy=comfy) diff --git a/comfy_config/types.py b/comfy_config/types.py new file mode 100644 index 000000000..f56b5f4c1 --- /dev/null +++ b/comfy_config/types.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass, field +from typing import List, Optional + +# IMPORTANT: The type definitions specified in pyproject.toml for custom nodes +# must remain synchronized with the corresponding files in the https://github.com/Comfy-Org/comfy-cli/blob/main/comfy_cli/registry/types.py. +# Any changes to one must be reflected in the other to maintain consistency. + +@dataclass +class NodeVersion: + changelog: str + dependencies: List[str] + deprecated: bool + id: str + version: str + download_url: str + + +@dataclass +class Node: + id: str + name: str + description: str + author: Optional[str] = None + license: Optional[str] = None + icon: Optional[str] = None + repository: Optional[str] = None + tags: List[str] = field(default_factory=list) + latest_version: Optional[NodeVersion] = None + + +@dataclass +class PublishNodeVersionResponse: + node_version: NodeVersion + signedUrl: str + + +@dataclass +class URLs: + homepage: str = "" + documentation: str = "" + repository: str = "" + issues: str = "" + + +@dataclass +class Model: + location: str + model_url: str + + +@dataclass +class ComfyConfig: + publisher_id: str = "" + display_name: str = "" + icon: str = "" + models: List[Model] = field(default_factory=list) + includes: List[str] = field(default_factory=list) + + +@dataclass +class License: + file: str = "" + text: str = "" + + +@dataclass +class ProjectConfig: + name: str = "" + description: str = "" + version: str = "1.0.0" + requires_python: str = ">= 3.9" + dependencies: List[str] = field(default_factory=list) + license: License = field(default_factory=License) + urls: URLs = field(default_factory=URLs) + + +@dataclass +class PyProjectConfig: + project: ProjectConfig = field(default_factory=ProjectConfig) + tool_comfy: ComfyConfig = field(default_factory=ComfyConfig) diff --git a/requirements.txt b/requirements.txt index 3e2991563..f51c80e54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ spandrel soundfile av>=14.2.0 pydantic~=2.0 +tomlkit