diff --git a/__init__.py b/__init__.py index 10cdf1c..8283e84 100644 --- a/__init__.py +++ b/__init__.py @@ -13,18 +13,10 @@ sys.path.append(ext_dir) try: import black except ImportError: - print("Unable to import requirements for ComfyUI-SaveAsScript.") - print("Installing...") - - import importlib - - spec = importlib.util.spec_from_file_location( - "impact_install", os.path.join(os.path.dirname(__file__), "install.py") - ) - impact_install = importlib.util.module_from_spec(spec) - spec.loader.exec_module(impact_install) - - print("Successfully installed. Hopefully, at least.") + raise ImportError( + "ComfyUI-to-Python-Extension requires the project dependencies to be installed. " + f"Run 'uv sync' in {ext_dir} with Python 3.12+ before loading this extension." + ) from None # Prevent reimporting of custom nodes os.environ["RUNNING_IN_COMFYUI"] = "TRUE" diff --git a/js/save-as-script.js b/js/save-as-script.js index 9de431f..19175fc 100644 --- a/js/save-as-script.js +++ b/js/save-as-script.js @@ -1,6 +1,9 @@ import { api } from "../../scripts/api.js"; import { app } from "../../scripts/app.js"; +const DEFAULT_SCRIPT_FILENAME = "workflow_api.py"; +const DEFAULT_WORKFLOW_NAME = "workflow_api.json"; + function $el(tag, options = {}) { const element = document.createElement(tag); const { parent, style, ...props } = options; @@ -39,15 +42,12 @@ const extension = { }); }, savePythonScript() { - var filename = prompt("Save script as:"); - if(filename === undefined || filename === null || filename === "") { - return - } - + const filename = DEFAULT_SCRIPT_FILENAME; + app.graphToPrompt().then(async (p) => { const frontendWorkflow = p.workflow ?? app.graph.serialize(); const json = JSON.stringify({ - name: filename + ".json", + name: DEFAULT_WORKFLOW_NAME, workflow: JSON.stringify(p.output, null, 2), frontend_workflow: JSON.stringify(frontendWorkflow, null, 2), }, null, 2); // convert the data to a JSON string @@ -55,10 +55,6 @@ const extension = { if(response.status == 200) { const blob = new Blob([await response.text()], {type: "text/python;charset=utf-8"}); const url = URL.createObjectURL(blob); - if(!filename.endsWith(".py")) { - filename += ".py"; - } - const a = $el("a", { href: url, download: filename, diff --git a/pyproject.toml b/pyproject.toml index b100249..1e07500 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "comfyui-to-python-extension" description = "This custom node allows you to generate pure python code from your ComfyUI workflow with the click of a button. Great for rapid experimentation or production deployment." version = "2.0.0" license = { text = "MIT License" } +requires-python = ">=3.12" dependencies = ["black"] [project.urls] diff --git a/tests/test_project_contracts.py b/tests/test_project_contracts.py new file mode 100644 index 0000000..5632583 --- /dev/null +++ b/tests/test_project_contracts.py @@ -0,0 +1,33 @@ +import tomllib +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +class ProjectContractsTest(unittest.TestCase): + def test_project_declares_supported_python_floor(self): + pyproject = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) + + self.assertEqual(pyproject["project"]["requires-python"], ">=3.12") + + def test_readme_documents_python_support_and_default_save_filename(self): + readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + + self.assertIn("This project supports Python 3.12 and newer.", readme) + self.assertIn("default filename `workflow_api.py`", readme) + + def test_extension_import_path_requires_uv_sync_instead_of_running_install_py(self): + init_text = (REPO_ROOT / "__init__.py").read_text(encoding="utf-8") + + self.assertIn("Run 'uv sync'", init_text) + self.assertNotIn("spec_from_file_location", init_text) + self.assertNotIn("Successfully installed. Hopefully, at least.", init_text) + + def test_frontend_save_flow_uses_deterministic_filename_without_prompt(self): + save_as_script = (REPO_ROOT / "js" / "save-as-script.js").read_text(encoding="utf-8") + + self.assertIn('const DEFAULT_SCRIPT_FILENAME = "workflow_api.py";', save_as_script) + self.assertNotIn("prompt(", save_as_script) +